diff --git a/.gitattributes b/.gitattributes index 023c3e019a152805e1e23c976f66b8fbb96d4256..1c307b0c28278ec6d9aed3eca761354787b1a724 100644 --- a/.gitattributes +++ b/.gitattributes @@ -204,3 +204,4 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_ .venv/lib/python3.11/site-packages/vllm/__pycache__/config.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.11/site-packages/vllm/__pycache__/utils.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text .venv/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text +.venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text diff --git a/.venv/lib/python3.11/site-packages/cffi/__init__.py b/.venv/lib/python3.11/site-packages/cffi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2e35a38c9ce02a703c5b2b4669d3d580776e19e0 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/__init__.py @@ -0,0 +1,14 @@ +__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', + 'FFIError'] + +from .api import FFI +from .error import CDefError, FFIError, VerificationError, VerificationMissing +from .error import PkgConfigError + +__version__ = "1.17.1" +__version_info__ = (1, 17, 1) + +# The verifier module file names are based on the CRC32 of a string that +# contains the following version number. It may be older than __version__ +# if nothing is clearly incompatible. +__version_verifier_modules__ = "0.8.6" diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..806e833c1c57923af6c076816226a7f51c7f2436 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/_imp_emulation.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/_imp_emulation.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14e74f5a80e67ee9eb471075ad3a3544b304c0e5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/_imp_emulation.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/_shimmed_dist_utils.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/_shimmed_dist_utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07510900378e5ec826750bf8a856ac6069a73a5b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/_shimmed_dist_utils.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/api.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/api.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9af5a4751e1791b275d8c2553dd0fd6d434a30e4 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/api.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/backend_ctypes.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/backend_ctypes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03695499e8e35cd98d5aae5f9212830d109f5930 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/backend_ctypes.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/cffi_opcode.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/cffi_opcode.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfe015235ac5a89f3b2465cf24848bd4ef09eb0b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/cffi_opcode.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/commontypes.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/commontypes.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd00e3a8b393bf4ab47412e77a6468c50b55be7d Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/commontypes.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/cparser.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/cparser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74661717aa4bc5a79ba84e93c09c6262fd84706d Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/cparser.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/error.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/error.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a352cbe807a001d89e32d324520294a93319b29 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/error.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/ffiplatform.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/ffiplatform.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d18b353d3906c1922df4b0f7c7e7ff3cfbb3b46 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/ffiplatform.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/lock.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/lock.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4774dafa235fd730bf85dbfb27619704cbc09e4c Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/lock.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/model.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/model.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4fa426511b7b97bc6cae69417371b58184010c3 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/model.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/pkgconfig.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/pkgconfig.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a33f54b6eb94c3defe0a912633e68528756dc520 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/pkgconfig.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/recompiler.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/recompiler.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd0d6d8477c7ddea7dc17a1bd108d0bcb002e527 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/recompiler.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/setuptools_ext.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/setuptools_ext.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e937815d993d350e50e0b2e0e4a374b700c11e59 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/setuptools_ext.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_cpy.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_cpy.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a2cc4ecb6894401b5c6fab6758ab1538ae8c97c Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_cpy.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_gen.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_gen.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c56008eea3b1aab94cbeba4dd84abcc74c0cd7e8 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/vengine_gen.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/__pycache__/verifier.cpython-311.pyc b/.venv/lib/python3.11/site-packages/cffi/__pycache__/verifier.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ac4b6ba5a3a85439408cad4282c5e2abb638573 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/cffi/__pycache__/verifier.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/cffi/_cffi_include.h b/.venv/lib/python3.11/site-packages/cffi/_cffi_include.h new file mode 100644 index 0000000000000000000000000000000000000000..908a1d7343f1869bc8818ca8e786f2c94a4732d2 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/_cffi_include.h @@ -0,0 +1,389 @@ +#define _CFFI_ + +/* We try to define Py_LIMITED_API before including Python.h. + + Mess: we can only define it if Py_DEBUG, Py_TRACE_REFS and + Py_REF_DEBUG are not defined. This is a best-effort approximation: + we can learn about Py_DEBUG from pyconfig.h, but it is unclear if + the same works for the other two macros. Py_DEBUG implies them, + but not the other way around. + + The implementation is messy (issue #350): on Windows, with _MSC_VER, + we have to define Py_LIMITED_API even before including pyconfig.h. + In that case, we guess what pyconfig.h will do to the macros above, + and check our guess after the #include. + + Note that on Windows, with CPython 3.x, you need >= 3.5 and virtualenv + version >= 16.0.0. With older versions of either, you don't get a + copy of PYTHON3.DLL in the virtualenv. We can't check the version of + CPython *before* we even include pyconfig.h. ffi.set_source() puts + a ``#define _CFFI_NO_LIMITED_API'' at the start of this file if it is + running on Windows < 3.5, as an attempt at fixing it, but that's + arguably wrong because it may not be the target version of Python. + Still better than nothing I guess. As another workaround, you can + remove the definition of Py_LIMITED_API here. + + See also 'py_limited_api' in cffi/setuptools_ext.py. +*/ +#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API) +# ifdef _MSC_VER +# if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# define Py_LIMITED_API +# endif +# include + /* sanity-check: Py_LIMITED_API will cause crashes if any of these + are also defined. Normally, the Python file PC/pyconfig.h does not + cause any of these to be defined, with the exception that _DEBUG + causes Py_DEBUG. Double-check that. */ +# ifdef Py_LIMITED_API +# if defined(Py_DEBUG) +# error "pyconfig.h unexpectedly defines Py_DEBUG, but Py_LIMITED_API is set" +# endif +# if defined(Py_TRACE_REFS) +# error "pyconfig.h unexpectedly defines Py_TRACE_REFS, but Py_LIMITED_API is set" +# endif +# if defined(Py_REF_DEBUG) +# error "pyconfig.h unexpectedly defines Py_REF_DEBUG, but Py_LIMITED_API is set" +# endif +# endif +# else +# include +# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# define Py_LIMITED_API +# endif +# endif +#endif + +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include "parse_c_type.h" + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#ifdef __GNUC__ +# define _CFFI_UNUSED_FN __attribute__((unused)) +#else +# define _CFFI_UNUSED_FN /* nothing */ +#endif + +#ifdef __cplusplus +# ifndef _Bool + typedef bool _Bool; /* semi-hackish: C++ has no _Bool; bool is builtin */ +# endif +#endif + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, struct _cffi_ctypedescr *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + not used any more +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((_cffi_wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(_cffi_wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(struct _cffi_ctypedescr *, \ + PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[24]) +#define _CFFI_CPIDX 25 +#define _cffi_call_python \ + ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX]) +#define _cffi_to_c_wchar3216_t \ + ((int(*)(PyObject *))_cffi_exports[26]) +#define _cffi_from_c_wchar3216_t \ + ((PyObject *(*)(int))_cffi_exports[27]) +#define _CFFI_NUM_EXPORTS 28 + +struct _cffi_ctypedescr; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; + +#define _cffi_type(index) ( \ + assert((((uintptr_t)_cffi_types[index]) & 1) == 0), \ + (struct _cffi_ctypedescr *)_cffi_types[index]) + +static PyObject *_cffi_init(const char *module_name, Py_ssize_t version, + const struct _cffi_type_context_s *ctx) +{ + PyObject *module, *o_arg, *new_module; + void *raw[] = { + (void *)module_name, + (void *)version, + (void *)_cffi_exports, + (void *)ctx, + }; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + o_arg = PyLong_FromVoidPtr((void *)raw); + if (o_arg == NULL) + goto failure; + + new_module = PyObject_CallMethod( + module, (char *)"_init_cffi_1_0_external_module", (char *)"O", o_arg); + + Py_DECREF(o_arg); + Py_DECREF(module); + return new_module; + + failure: + Py_XDECREF(module); + return NULL; +} + + +#ifdef HAVE_WCHAR_H +typedef wchar_t _cffi_wchar_t; +#else +typedef uint16_t _cffi_wchar_t; /* same random pick as _cffi_backend.c */ +#endif + +_CFFI_UNUSED_FN static uint16_t _cffi_to_c_char16_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 2) + return (uint16_t)_cffi_to_c_wchar_t(o); + else + return (uint16_t)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char16_t(uint16_t x) +{ + if (sizeof(_cffi_wchar_t) == 2) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +_CFFI_UNUSED_FN static int _cffi_to_c_char32_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 4) + return (int)_cffi_to_c_wchar_t(o); + else + return (int)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char32_t(unsigned int x) +{ + if (sizeof(_cffi_wchar_t) == 4) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +_CFFI_UNUSED_FN static int +_cffi_convert_array_argument(struct _cffi_ctypedescr *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +_CFFI_UNUSED_FN static void +_cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +/********** end CPython-specific section **********/ +#else +_CFFI_UNUSED_FN +static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *); +# define _cffi_call_python _cffi_call_python_org +#endif + + +#define _cffi_array_len(array) (sizeof(array) / sizeof((array)[0])) + +#define _cffi_prim_int(size, sign) \ + ((size) == 1 ? ((sign) ? _CFFI_PRIM_INT8 : _CFFI_PRIM_UINT8) : \ + (size) == 2 ? ((sign) ? _CFFI_PRIM_INT16 : _CFFI_PRIM_UINT16) : \ + (size) == 4 ? ((sign) ? _CFFI_PRIM_INT32 : _CFFI_PRIM_UINT32) : \ + (size) == 8 ? ((sign) ? _CFFI_PRIM_INT64 : _CFFI_PRIM_UINT64) : \ + _CFFI__UNKNOWN_PRIM) + +#define _cffi_prim_float(size) \ + ((size) == sizeof(float) ? _CFFI_PRIM_FLOAT : \ + (size) == sizeof(double) ? _CFFI_PRIM_DOUBLE : \ + (size) == sizeof(long double) ? _CFFI__UNKNOWN_LONG_DOUBLE : \ + _CFFI__UNKNOWN_FLOAT_PRIM) + +#define _cffi_check_int(got, got_nonpos, expected) \ + ((got_nonpos) == (expected <= 0) && \ + (got) == (unsigned long long)expected) + +#ifdef MS_WIN32 +# define _cffi_stdcall __stdcall +#else +# define _cffi_stdcall /* nothing */ +#endif + +#ifdef __cplusplus +} +#endif diff --git a/.venv/lib/python3.11/site-packages/cffi/_imp_emulation.py b/.venv/lib/python3.11/site-packages/cffi/_imp_emulation.py new file mode 100644 index 0000000000000000000000000000000000000000..136abdddf9d1276095e6f6724298ac19811c136a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/_imp_emulation.py @@ -0,0 +1,83 @@ + +try: + # this works on Python < 3.12 + from imp import * + +except ImportError: + # this is a limited emulation for Python >= 3.12. + # Note that this is used only for tests or for the old ffi.verify(). + # This is copied from the source code of Python 3.11. + + from _imp import (acquire_lock, release_lock, + is_builtin, is_frozen) + + from importlib._bootstrap import _load + + from importlib import machinery + import os + import sys + import tokenize + + SEARCH_ERROR = 0 + PY_SOURCE = 1 + PY_COMPILED = 2 + C_EXTENSION = 3 + PY_RESOURCE = 4 + PKG_DIRECTORY = 5 + C_BUILTIN = 6 + PY_FROZEN = 7 + PY_CODERESOURCE = 8 + IMP_HOOK = 9 + + def get_suffixes(): + extensions = [(s, 'rb', C_EXTENSION) + for s in machinery.EXTENSION_SUFFIXES] + source = [(s, 'r', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] + bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES] + return extensions + source + bytecode + + def find_module(name, path=None): + if not isinstance(name, str): + raise TypeError("'name' must be a str, not {}".format(type(name))) + elif not isinstance(path, (type(None), list)): + # Backwards-compatibility + raise RuntimeError("'path' must be None or a list, " + "not {}".format(type(path))) + + if path is None: + if is_builtin(name): + return None, None, ('', '', C_BUILTIN) + elif is_frozen(name): + return None, None, ('', '', PY_FROZEN) + else: + path = sys.path + + for entry in path: + package_directory = os.path.join(entry, name) + for suffix in ['.py', machinery.BYTECODE_SUFFIXES[0]]: + package_file_name = '__init__' + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return None, package_directory, ('', '', PKG_DIRECTORY) + for suffix, mode, type_ in get_suffixes(): + file_name = name + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + break + else: + continue + break # Break out of outer loop when breaking out of inner loop. + else: + raise ImportError(name, name=name) + + encoding = None + if 'b' not in mode: + with open(file_path, 'rb') as file: + encoding = tokenize.detect_encoding(file.readline)[0] + file = open(file_path, mode, encoding=encoding) + return file, file_path, (suffix, mode, type_) + + def load_dynamic(name, path, file=None): + loader = machinery.ExtensionFileLoader(name, path) + spec = machinery.ModuleSpec(name=name, loader=loader, origin=path) + return _load(spec) diff --git a/.venv/lib/python3.11/site-packages/cffi/_shimmed_dist_utils.py b/.venv/lib/python3.11/site-packages/cffi/_shimmed_dist_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d23128189fc121bff3f86b19330b0ac5ce0a45 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/_shimmed_dist_utils.py @@ -0,0 +1,45 @@ +""" +Temporary shim module to indirect the bits of distutils we need from setuptools/distutils while providing useful +error messages beyond `No module named 'distutils' on Python >= 3.12, or when setuptools' vendored distutils is broken. + +This is a compromise to avoid a hard-dep on setuptools for Python >= 3.12, since many users don't need runtime compilation support from CFFI. +""" +import sys + +try: + # import setuptools first; this is the most robust way to ensure its embedded distutils is available + # (the .pth shim should usually work, but this is even more robust) + import setuptools +except Exception as ex: + if sys.version_info >= (3, 12): + # Python 3.12 has no built-in distutils to fall back on, so any import problem is fatal + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. The setuptools module is missing or non-functional.") from ex + + # silently ignore on older Pythons (support fallback to stdlib distutils where available) +else: + del setuptools + +try: + # bring in just the bits of distutils we need, whether they really came from setuptools or stdlib-embedded distutils + from distutils import log, sysconfig + from distutils.ccompiler import CCompiler + from distutils.command.build_ext import build_ext + from distutils.core import Distribution, Extension + from distutils.dir_util import mkpath + from distutils.errors import DistutilsSetupError, CompileError, LinkError + from distutils.log import set_threshold, set_verbosity + + if sys.platform == 'win32': + try: + # FUTURE: msvc9compiler module was removed in setuptools 74; consider removing, as it's only used by an ancient patch in `recompiler` + from distutils.msvc9compiler import MSVCCompiler + except ImportError: + MSVCCompiler = None +except Exception as ex: + if sys.version_info >= (3, 12): + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. Please install the setuptools package.") from ex + + # anything older, just let the underlying distutils import error fly + raise Exception("This CFFI feature requires distutils. Please install the distutils or setuptools package.") from ex + +del sys diff --git a/.venv/lib/python3.11/site-packages/cffi/api.py b/.venv/lib/python3.11/site-packages/cffi/api.py new file mode 100644 index 0000000000000000000000000000000000000000..5a474f3da9288e51df2ab93f7c40372955986265 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/api.py @@ -0,0 +1,967 @@ +import sys, types +from .lock import allocate_lock +from .error import CDefError +from . import model + +try: + callable +except NameError: + # Python 3.1 + from collections import Callable + callable = lambda x: isinstance(x, Callable) + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +_unspecified = object() + + + +class FFI(object): + r''' + The main top-level class that you instantiate once, or once per module. + + Example usage: + + ffi = FFI() + ffi.cdef(""" + int printf(const char *, ...); + """) + + C = ffi.dlopen(None) # standard library + -or- + C = ffi.verify() # use a C compiler: verify the decl above is right + + C.printf("hello, %s!\n", ffi.new("char[]", "world")) + ''' + + def __init__(self, backend=None): + """Create an FFI instance. The 'backend' argument is used to + select a non-default backend, mostly for tests. + """ + if backend is None: + # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with + # _cffi_backend.so compiled. + import _cffi_backend as backend + from . import __version__ + if backend.__version__ != __version__: + # bad version! Try to be as explicit as possible. + if hasattr(backend, '__file__'): + # CPython + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. When we import the top-level '_cffi_backend' extension module, we get version %s, located in %r. The two versions should be equal; check your installation." % ( + __version__, __file__, + backend.__version__, backend.__file__)) + else: + # PyPy + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. This interpreter comes with a built-in '_cffi_backend' module, which is version %s. The two versions should be equal; check your installation." % ( + __version__, __file__, backend.__version__)) + # (If you insist you can also try to pass the option + # 'backend=backend_ctypes.CTypesBackend()', but don't + # rely on it! It's probably not going to work well.) + + from . import cparser + self._backend = backend + self._lock = allocate_lock() + self._parser = cparser.Parser() + self._cached_btypes = {} + self._parsed_types = types.ModuleType('parsed_types').__dict__ + self._new_types = types.ModuleType('new_types').__dict__ + self._function_caches = [] + self._libraries = [] + self._cdefsources = [] + self._included_ffis = [] + self._windows_unicode = None + self._init_once_cache = {} + self._cdef_version = None + self._embedding = None + self._typecache = model.get_typecache(backend) + if hasattr(backend, 'set_ffi'): + backend.set_ffi(self) + for name in list(backend.__dict__): + if name.startswith('RTLD_'): + setattr(self, name, getattr(backend, name)) + # + with self._lock: + self.BVoidP = self._get_cached_btype(model.voidp_type) + self.BCharA = self._get_cached_btype(model.char_array_type) + if isinstance(backend, types.ModuleType): + # _cffi_backend: attach these constants to the class + if not hasattr(FFI, 'NULL'): + FFI.NULL = self.cast(self.BVoidP, 0) + FFI.CData, FFI.CType = backend._get_types() + else: + # ctypes backend: attach these constants to the instance + self.NULL = self.cast(self.BVoidP, 0) + self.CData, self.CType = backend._get_types() + self.buffer = backend.buffer + + def cdef(self, csource, override=False, packed=False, pack=None): + """Parse the given C source. This registers all declared functions, + types, and global variables. The functions and global variables can + then be accessed via either 'ffi.dlopen()' or 'ffi.verify()'. + The types can be used in 'ffi.new()' and other functions. + If 'packed' is specified as True, all structs declared inside this + cdef are packed, i.e. laid out without any field alignment at all. + Alternatively, 'pack' can be a small integer, and requests for + alignment greater than that are ignored (pack=1 is equivalent to + packed=True). + """ + self._cdef(csource, override=override, packed=packed, pack=pack) + + def embedding_api(self, csource, packed=False, pack=None): + self._cdef(csource, packed=packed, pack=pack, dllexport=True) + if self._embedding is None: + self._embedding = '' + + def _cdef(self, csource, override=False, **options): + if not isinstance(csource, str): # unicode, on Python 2 + if not isinstance(csource, basestring): + raise TypeError("cdef() argument must be a string") + csource = csource.encode('ascii') + with self._lock: + self._cdef_version = object() + self._parser.parse(csource, override=override, **options) + self._cdefsources.append(csource) + if override: + for cache in self._function_caches: + cache.clear() + finishlist = self._parser._recomplete + if finishlist: + self._parser._recomplete = [] + for tp in finishlist: + tp.finish_backend_type(self, finishlist) + + def dlopen(self, name, flags=0): + """Load and return a dynamic library identified by 'name'. + The standard C library can be loaded by passing None. + Note that functions and types declared by 'ffi.cdef()' are not + linked to a particular library, just like C headers; in the + library we only look for the actual (untyped) symbols. + """ + if not (isinstance(name, basestring) or + name is None or + isinstance(name, self.CData)): + raise TypeError("dlopen(name): name must be a file name, None, " + "or an already-opened 'void *' handle") + with self._lock: + lib, function_cache = _make_ffi_library(self, name, flags) + self._function_caches.append(function_cache) + self._libraries.append(lib) + return lib + + def dlclose(self, lib): + """Close a library obtained with ffi.dlopen(). After this call, + access to functions or variables from the library will fail + (possibly with a segmentation fault). + """ + type(lib).__cffi_close__(lib) + + def _typeof_locked(self, cdecl): + # call me with the lock! + key = cdecl + if key in self._parsed_types: + return self._parsed_types[key] + # + if not isinstance(cdecl, str): # unicode, on Python 2 + cdecl = cdecl.encode('ascii') + # + type = self._parser.parse_type(cdecl) + really_a_function_type = type.is_raw_function + if really_a_function_type: + type = type.as_function_pointer() + btype = self._get_cached_btype(type) + result = btype, really_a_function_type + self._parsed_types[key] = result + return result + + def _typeof(self, cdecl, consider_function_as_funcptr=False): + # string -> ctype object + try: + result = self._parsed_types[cdecl] + except KeyError: + with self._lock: + result = self._typeof_locked(cdecl) + # + btype, really_a_function_type = result + if really_a_function_type and not consider_function_as_funcptr: + raise CDefError("the type %r is a function type, not a " + "pointer-to-function type" % (cdecl,)) + return btype + + def typeof(self, cdecl): + """Parse the C type given as a string and return the + corresponding object. + It can also be used on 'cdata' instance to get its C type. + """ + if isinstance(cdecl, basestring): + return self._typeof(cdecl) + if isinstance(cdecl, self.CData): + return self._backend.typeof(cdecl) + if isinstance(cdecl, types.BuiltinFunctionType): + res = _builtin_function_type(cdecl) + if res is not None: + return res + if (isinstance(cdecl, types.FunctionType) + and hasattr(cdecl, '_cffi_base_type')): + with self._lock: + return self._get_cached_btype(cdecl._cffi_base_type) + raise TypeError(type(cdecl)) + + def sizeof(self, cdecl): + """Return the size in bytes of the argument. It can be a + string naming a C type, or a 'cdata' instance. + """ + if isinstance(cdecl, basestring): + BType = self._typeof(cdecl) + return self._backend.sizeof(BType) + else: + return self._backend.sizeof(cdecl) + + def alignof(self, cdecl): + """Return the natural alignment size in bytes of the C type + given as a string. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.alignof(cdecl) + + def offsetof(self, cdecl, *fields_or_indexes): + """Return the offset of the named field inside the given + structure or array, which must be given as a C type name. + You can give several field names in case of nested structures. + You can also give numeric values which correspond to array + items, in case of an array type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._typeoffsetof(cdecl, *fields_or_indexes)[1] + + def new(self, cdecl, init=None): + """Allocate an instance according to the specified C type and + return a pointer to it. The specified C type must be either a + pointer or an array: ``new('X *')`` allocates an X and returns + a pointer to it, whereas ``new('X[n]')`` allocates an array of + n X'es and returns an array referencing it (which works + mostly like a pointer, like in C). You can also use + ``new('X[]', n)`` to allocate an array of a non-constant + length n. + + The memory is initialized following the rules of declaring a + global variable in C: by default it is zero-initialized, but + an explicit initializer can be given which can be used to + fill all or part of the memory. + + When the returned object goes out of scope, the memory + is freed. In other words the returned object has + ownership of the value of type 'cdecl' that it points to. This + means that the raw data can be used as long as this object is + kept alive, but must not be used for a longer time. Be careful + about that when copying the pointer to the memory somewhere + else, e.g. into another structure. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.newp(cdecl, init) + + def new_allocator(self, alloc=None, free=None, + should_clear_after_alloc=True): + """Return a new allocator, i.e. a function that behaves like ffi.new() + but uses the provided low-level 'alloc' and 'free' functions. + + 'alloc' is called with the size as argument. If it returns NULL, a + MemoryError is raised. 'free' is called with the result of 'alloc' + as argument. Both can be either Python function or directly C + functions. If 'free' is None, then no free function is called. + If both 'alloc' and 'free' are None, the default is used. + + If 'should_clear_after_alloc' is set to False, then the memory + returned by 'alloc' is assumed to be already cleared (or you are + fine with garbage); otherwise CFFI will clear it. + """ + compiled_ffi = self._backend.FFI() + allocator = compiled_ffi.new_allocator(alloc, free, + should_clear_after_alloc) + def allocate(cdecl, init=None): + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return allocator(cdecl, init) + return allocate + + def cast(self, cdecl, source): + """Similar to a C cast: returns an instance of the named C + type initialized with the given 'source'. The source is + casted between integers or pointers of any type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.cast(cdecl, source) + + def string(self, cdata, maxlen=-1): + """Return a Python string (or unicode string) from the 'cdata'. + If 'cdata' is a pointer or array of characters or bytes, returns + the null-terminated string. The returned string extends until + the first null character, or at most 'maxlen' characters. If + 'cdata' is an array then 'maxlen' defaults to its length. + + If 'cdata' is a pointer or array of wchar_t, returns a unicode + string following the same rules. + + If 'cdata' is a single character or byte or a wchar_t, returns + it as a string or unicode string. + + If 'cdata' is an enum, returns the value of the enumerator as a + string, or 'NUMBER' if the value is out of range. + """ + return self._backend.string(cdata, maxlen) + + def unpack(self, cdata, length): + """Unpack an array of C data of the given length, + returning a Python string/unicode/list. + + If 'cdata' is a pointer to 'char', returns a byte string. + It does not stop at the first null. This is equivalent to: + ffi.buffer(cdata, length)[:] + + If 'cdata' is a pointer to 'wchar_t', returns a unicode string. + 'length' is measured in wchar_t's; it is not the size in bytes. + + If 'cdata' is a pointer to anything else, returns a list of + 'length' items. This is a faster equivalent to: + [cdata[i] for i in range(length)] + """ + return self._backend.unpack(cdata, length) + + #def buffer(self, cdata, size=-1): + # """Return a read-write buffer object that references the raw C data + # pointed to by the given 'cdata'. The 'cdata' must be a pointer or + # an array. Can be passed to functions expecting a buffer, or directly + # manipulated with: + # + # buf[:] get a copy of it in a regular string, or + # buf[idx] as a single character + # buf[:] = ... + # buf[idx] = ... change the content + # """ + # note that 'buffer' is a type, set on this instance by __init__ + + def from_buffer(self, cdecl, python_buffer=_unspecified, + require_writable=False): + """Return a cdata of the given type pointing to the data of the + given Python object, which must support the buffer interface. + Note that this is not meant to be used on the built-in types + str or unicode (you can build 'char[]' arrays explicitly) + but only on objects containing large quantities of raw data + in some other format, like 'array.array' or numpy arrays. + + The first argument is optional and default to 'char[]'. + """ + if python_buffer is _unspecified: + cdecl, python_buffer = self.BCharA, cdecl + elif isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.from_buffer(cdecl, python_buffer, + require_writable) + + def memmove(self, dest, src, n): + """ffi.memmove(dest, src, n) copies n bytes of memory from src to dest. + + Like the C function memmove(), the memory areas may overlap; + apart from that it behaves like the C function memcpy(). + + 'src' can be any cdata ptr or array, or any Python buffer object. + 'dest' can be any cdata ptr or array, or a writable Python buffer + object. The size to copy, 'n', is always measured in bytes. + + Unlike other methods, this one supports all Python buffer including + byte strings and bytearrays---but it still does not support + non-contiguous buffers. + """ + return self._backend.memmove(dest, src, n) + + def callback(self, cdecl, python_callable=None, error=None, onerror=None): + """Return a callback object or a decorator making such a + callback object. 'cdecl' must name a C function pointer type. + The callback invokes the specified 'python_callable' (which may + be provided either directly or via a decorator). Important: the + callback object must be manually kept alive for as long as the + callback may be invoked from the C level. + """ + def callback_decorator_wrap(python_callable): + if not callable(python_callable): + raise TypeError("the 'python_callable' argument " + "is not callable") + return self._backend.callback(cdecl, python_callable, + error, onerror) + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl, consider_function_as_funcptr=True) + if python_callable is None: + return callback_decorator_wrap # decorator mode + else: + return callback_decorator_wrap(python_callable) # direct mode + + def getctype(self, cdecl, replace_with=''): + """Return a string giving the C type 'cdecl', which may be itself + a string or a object. If 'replace_with' is given, it gives + extra text to append (or insert for more complicated C types), like + a variable name, or '*' to get actually the C type 'pointer-to-cdecl'. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + replace_with = replace_with.strip() + if (replace_with.startswith('*') + and '&[' in self._backend.getcname(cdecl, '&')): + replace_with = '(%s)' % replace_with + elif replace_with and not replace_with[0] in '[(': + replace_with = ' ' + replace_with + return self._backend.getcname(cdecl, replace_with) + + def gc(self, cdata, destructor, size=0): + """Return a new cdata object that points to the same + data. Later, when this new cdata object is garbage-collected, + 'destructor(old_cdata_object)' will be called. + + The optional 'size' gives an estimate of the size, used to + trigger the garbage collection more eagerly. So far only used + on PyPy. It tells the GC that the returned object keeps alive + roughly 'size' bytes of external memory. + """ + return self._backend.gcp(cdata, destructor, size) + + def _get_cached_btype(self, type): + assert self._lock.acquire(False) is False + # call me with the lock! + try: + BType = self._cached_btypes[type] + except KeyError: + finishlist = [] + BType = type.get_cached_btype(self, finishlist) + for type in finishlist: + type.finish_backend_type(self, finishlist) + return BType + + def verify(self, source='', tmpdir=None, **kwargs): + """Verify that the current ffi signatures compile on this + machine, and return a dynamic library object. The dynamic + library can be used to call functions and access global + variables declared in this 'ffi'. The library is compiled + by the C compiler: it gives you C-level API compatibility + (including calling macros). This is unlike 'ffi.dlopen()', + which requires binary compatibility in the signatures. + """ + from .verifier import Verifier, _caller_dir_pycache + # + # If set_unicode(True) was called, insert the UNICODE and + # _UNICODE macro declarations + if self._windows_unicode: + self._apply_windows_unicode(kwargs) + # + # Set the tmpdir here, and not in Verifier.__init__: it picks + # up the caller's directory, which we want to be the caller of + # ffi.verify(), as opposed to the caller of Veritier(). + tmpdir = tmpdir or _caller_dir_pycache() + # + # Make a Verifier() and use it to load the library. + self.verifier = Verifier(self, source, tmpdir, **kwargs) + lib = self.verifier.load_library() + # + # Save the loaded library for keep-alive purposes, even + # if the caller doesn't keep it alive itself (it should). + self._libraries.append(lib) + return lib + + def _get_errno(self): + return self._backend.get_errno() + def _set_errno(self, errno): + self._backend.set_errno(errno) + errno = property(_get_errno, _set_errno, None, + "the value of 'errno' from/to the C calls") + + def getwinerror(self, code=-1): + return self._backend.getwinerror(code) + + def _pointer_to(self, ctype): + with self._lock: + return model.pointer_cache(self, ctype) + + def addressof(self, cdata, *fields_or_indexes): + """Return the address of a . + If 'fields_or_indexes' are given, returns the address of that + field or array item in the structure or array, recursively in + case of nested structures. + """ + try: + ctype = self._backend.typeof(cdata) + except TypeError: + if '__addressof__' in type(cdata).__dict__: + return type(cdata).__addressof__(cdata, *fields_or_indexes) + raise + if fields_or_indexes: + ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes) + else: + if ctype.kind == "pointer": + raise TypeError("addressof(pointer)") + offset = 0 + ctypeptr = self._pointer_to(ctype) + return self._backend.rawaddressof(ctypeptr, cdata, offset) + + def _typeoffsetof(self, ctype, field_or_index, *fields_or_indexes): + ctype, offset = self._backend.typeoffsetof(ctype, field_or_index) + for field1 in fields_or_indexes: + ctype, offset1 = self._backend.typeoffsetof(ctype, field1, 1) + offset += offset1 + return ctype, offset + + def include(self, ffi_to_include): + """Includes the typedefs, structs, unions and enums defined + in another FFI instance. Usage is similar to a #include in C, + where a part of the program might include types defined in + another part for its own usage. Note that the include() + method has no effect on functions, constants and global + variables, which must anyway be accessed directly from the + lib object returned by the original FFI instance. + """ + if not isinstance(ffi_to_include, FFI): + raise TypeError("ffi.include() expects an argument that is also of" + " type cffi.FFI, not %r" % ( + type(ffi_to_include).__name__,)) + if ffi_to_include is self: + raise ValueError("self.include(self)") + with ffi_to_include._lock: + with self._lock: + self._parser.include(ffi_to_include._parser) + self._cdefsources.append('[') + self._cdefsources.extend(ffi_to_include._cdefsources) + self._cdefsources.append(']') + self._included_ffis.append(ffi_to_include) + + def new_handle(self, x): + return self._backend.newp_handle(self.BVoidP, x) + + def from_handle(self, x): + return self._backend.from_handle(x) + + def release(self, x): + self._backend.release(x) + + def set_unicode(self, enabled_flag): + """Windows: if 'enabled_flag' is True, enable the UNICODE and + _UNICODE defines in C, and declare the types like TCHAR and LPTCSTR + to be (pointers to) wchar_t. If 'enabled_flag' is False, + declare these types to be (pointers to) plain 8-bit characters. + This is mostly for backward compatibility; you usually want True. + """ + if self._windows_unicode is not None: + raise ValueError("set_unicode() can only be called once") + enabled_flag = bool(enabled_flag) + if enabled_flag: + self.cdef("typedef wchar_t TBYTE;" + "typedef wchar_t TCHAR;" + "typedef const wchar_t *LPCTSTR;" + "typedef const wchar_t *PCTSTR;" + "typedef wchar_t *LPTSTR;" + "typedef wchar_t *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + else: + self.cdef("typedef char TBYTE;" + "typedef char TCHAR;" + "typedef const char *LPCTSTR;" + "typedef const char *PCTSTR;" + "typedef char *LPTSTR;" + "typedef char *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + self._windows_unicode = enabled_flag + + def _apply_windows_unicode(self, kwds): + defmacros = kwds.get('define_macros', ()) + if not isinstance(defmacros, (list, tuple)): + raise TypeError("'define_macros' must be a list or tuple") + defmacros = list(defmacros) + [('UNICODE', '1'), + ('_UNICODE', '1')] + kwds['define_macros'] = defmacros + + def _apply_embedding_fix(self, kwds): + # must include an argument like "-lpython2.7" for the compiler + def ensure(key, value): + lst = kwds.setdefault(key, []) + if value not in lst: + lst.append(value) + # + if '__pypy__' in sys.builtin_module_names: + import os + if sys.platform == "win32": + # we need 'libpypy-c.lib'. Current distributions of + # pypy (>= 4.1) contain it as 'libs/python27.lib'. + pythonlib = "python{0[0]}{0[1]}".format(sys.version_info) + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'libs')) + else: + # we need 'libpypy-c.{so,dylib}', which should be by + # default located in 'sys.prefix/bin' for installed + # systems. + if sys.version_info < (3,): + pythonlib = "pypy-c" + else: + pythonlib = "pypy3-c" + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'bin')) + # On uninstalled pypy's, the libpypy-c is typically found in + # .../pypy/goal/. + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'pypy', 'goal')) + else: + if sys.platform == "win32": + template = "python%d%d" + if hasattr(sys, 'gettotalrefcount'): + template += '_d' + else: + try: + import sysconfig + except ImportError: # 2.6 + from cffi._shimmed_dist_utils import sysconfig + template = "python%d.%d" + if sysconfig.get_config_var('DEBUG_EXT'): + template += sysconfig.get_config_var('DEBUG_EXT') + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + if hasattr(sys, 'abiflags'): + pythonlib += sys.abiflags + ensure('libraries', pythonlib) + if sys.platform == "win32": + ensure('extra_link_args', '/MANIFEST') + + def set_source(self, module_name, source, source_extension='.c', **kwds): + import os + if hasattr(self, '_assigned_source'): + raise ValueError("set_source() cannot be called several times " + "per ffi object") + if not isinstance(module_name, basestring): + raise TypeError("'module_name' must be a string") + if os.sep in module_name or (os.altsep and os.altsep in module_name): + raise ValueError("'module_name' must not contain '/': use a dotted " + "name to make a 'package.module' location") + self._assigned_source = (str(module_name), source, + source_extension, kwds) + + def set_source_pkgconfig(self, module_name, pkgconfig_libs, source, + source_extension='.c', **kwds): + from . import pkgconfig + if not isinstance(pkgconfig_libs, list): + raise TypeError("the pkgconfig_libs argument must be a list " + "of package names") + kwds2 = pkgconfig.flags_from_pkgconfig(pkgconfig_libs) + pkgconfig.merge_flags(kwds, kwds2) + self.set_source(module_name, source, source_extension, **kwds) + + def distutils_extension(self, tmpdir='build', verbose=True): + from cffi._shimmed_dist_utils import mkpath + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + if hasattr(self, 'verifier'): # fallback, 'tmpdir' ignored + return self.verifier.get_extension() + raise ValueError("set_source() must be called before" + " distutils_extension()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("distutils_extension() is only for C extension " + "modules, not for dlopen()-style pure Python " + "modules") + mkpath(tmpdir) + ext, updated = recompile(self, module_name, + source, tmpdir=tmpdir, extradir=tmpdir, + source_extension=source_extension, + call_c_compiler=False, **kwds) + if verbose: + if updated: + sys.stderr.write("regenerated: %r\n" % (ext.sources[0],)) + else: + sys.stderr.write("not modified: %r\n" % (ext.sources[0],)) + return ext + + def emit_c_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("emit_c_code() is only for C extension modules, " + "not for dlopen()-style pure Python modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def emit_python_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is not None: + raise TypeError("emit_python_code() is only for dlopen()-style " + "pure Python modules, not for C extension modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def compile(self, tmpdir='.', verbose=0, target=None, debug=None): + """The 'target' argument gives the final file name of the + compiled DLL. Use '*' to force distutils' choice, suitable for + regular CPython C API modules. Use a file name ending in '.*' + to ask for the system's default extension for dynamic libraries + (.so/.dll/.dylib). + + The default is '*' when building a non-embedded C API extension, + and (module_name + '.*') when building an embedded library. + """ + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before compile()") + module_name, source, source_extension, kwds = self._assigned_source + return recompile(self, module_name, source, tmpdir=tmpdir, + target=target, source_extension=source_extension, + compiler_verbose=verbose, debug=debug, **kwds) + + def init_once(self, func, tag): + # Read _init_once_cache[tag], which is either (False, lock) if + # we're calling the function now in some thread, or (True, result). + # Don't call setdefault() in most cases, to avoid allocating and + # immediately freeing a lock; but still use setdefaut() to avoid + # races. + try: + x = self._init_once_cache[tag] + except KeyError: + x = self._init_once_cache.setdefault(tag, (False, allocate_lock())) + # Common case: we got (True, result), so we return the result. + if x[0]: + return x[1] + # Else, it's a lock. Acquire it to serialize the following tests. + with x[1]: + # Read again from _init_once_cache the current status. + x = self._init_once_cache[tag] + if x[0]: + return x[1] + # Call the function and store the result back. + result = func() + self._init_once_cache[tag] = (True, result) + return result + + def embedding_init_code(self, pysource): + if self._embedding: + raise ValueError("embedding_init_code() can only be called once") + # fix 'pysource' before it gets dumped into the C file: + # - remove empty lines at the beginning, so it starts at "line 1" + # - dedent, if all non-empty lines are indented + # - check for SyntaxErrors + import re + match = re.match(r'\s*\n', pysource) + if match: + pysource = pysource[match.end():] + lines = pysource.splitlines() or [''] + prefix = re.match(r'\s*', lines[0]).group() + for i in range(1, len(lines)): + line = lines[i] + if line.rstrip(): + while not line.startswith(prefix): + prefix = prefix[:-1] + i = len(prefix) + lines = [line[i:]+'\n' for line in lines] + pysource = ''.join(lines) + # + compile(pysource, "cffi_init", "exec") + # + self._embedding = pysource + + def def_extern(self, *args, **kwds): + raise ValueError("ffi.def_extern() is only available on API-mode FFI " + "objects") + + def list_types(self): + """Returns the user type names known to this FFI instance. + This returns a tuple containing three lists of names: + (typedef_names, names_of_structs, names_of_unions) + """ + typedefs = [] + structs = [] + unions = [] + for key in self._parser._declarations: + if key.startswith('typedef '): + typedefs.append(key[8:]) + elif key.startswith('struct '): + structs.append(key[7:]) + elif key.startswith('union '): + unions.append(key[6:]) + typedefs.sort() + structs.sort() + unions.sort() + return (typedefs, structs, unions) + + +def _load_backend_lib(backend, name, flags): + import os + if not isinstance(name, basestring): + if sys.platform != "win32" or name is not None: + return backend.load_library(name, flags) + name = "c" # Windows: load_library(None) fails, but this works + # on Python 2 (backward compatibility hack only) + first_error = None + if '.' in name or '/' in name or os.sep in name: + try: + return backend.load_library(name, flags) + except OSError as e: + first_error = e + import ctypes.util + path = ctypes.util.find_library(name) + if path is None: + if name == "c" and sys.platform == "win32" and sys.version_info >= (3,): + raise OSError("dlopen(None) cannot work on Windows for Python 3 " + "(see http://bugs.python.org/issue23606)") + msg = ("ctypes.util.find_library() did not manage " + "to locate a library called %r" % (name,)) + if first_error is not None: + msg = "%s. Additionally, %s" % (first_error, msg) + raise OSError(msg) + return backend.load_library(path, flags) + +def _make_ffi_library(ffi, libname, flags): + backend = ffi._backend + backendlib = _load_backend_lib(backend, libname, flags) + # + def accessor_function(name): + key = 'function ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + value = backendlib.load_function(BType, name) + library.__dict__[name] = value + # + def accessor_variable(name): + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + read_variable = backendlib.read_variable + write_variable = backendlib.write_variable + setattr(FFILibrary, name, property( + lambda self: read_variable(BType, name), + lambda self, value: write_variable(BType, name, value))) + # + def addressof_var(name): + try: + return addr_variables[name] + except KeyError: + with ffi._lock: + if name not in addr_variables: + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + if BType.kind != 'array': + BType = model.pointer_cache(ffi, BType) + p = backendlib.load_function(BType, name) + addr_variables[name] = p + return addr_variables[name] + # + def accessor_constant(name): + raise NotImplementedError("non-integer constant '%s' cannot be " + "accessed from a dlopen() library" % (name,)) + # + def accessor_int_constant(name): + library.__dict__[name] = ffi._parser._int_constants[name] + # + accessors = {} + accessors_version = [False] + addr_variables = {} + # + def update_accessors(): + if accessors_version[0] is ffi._cdef_version: + return + # + for key, (tp, _) in ffi._parser._declarations.items(): + if not isinstance(tp, model.EnumType): + tag, name = key.split(' ', 1) + if tag == 'function': + accessors[name] = accessor_function + elif tag == 'variable': + accessors[name] = accessor_variable + elif tag == 'constant': + accessors[name] = accessor_constant + else: + for i, enumname in enumerate(tp.enumerators): + def accessor_enum(name, tp=tp, i=i): + tp.check_not_partial() + library.__dict__[name] = tp.enumvalues[i] + accessors[enumname] = accessor_enum + for name in ffi._parser._int_constants: + accessors.setdefault(name, accessor_int_constant) + accessors_version[0] = ffi._cdef_version + # + def make_accessor(name): + with ffi._lock: + if name in library.__dict__ or name in FFILibrary.__dict__: + return # added by another thread while waiting for the lock + if name not in accessors: + update_accessors() + if name not in accessors: + raise AttributeError(name) + accessors[name](name) + # + class FFILibrary(object): + def __getattr__(self, name): + make_accessor(name) + return getattr(self, name) + def __setattr__(self, name, value): + try: + property = getattr(self.__class__, name) + except AttributeError: + make_accessor(name) + setattr(self, name, value) + else: + property.__set__(self, value) + def __dir__(self): + with ffi._lock: + update_accessors() + return accessors.keys() + def __addressof__(self, name): + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + make_accessor(name) + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + raise AttributeError("cffi library has no function or " + "global variable named '%s'" % (name,)) + def __cffi_close__(self): + backendlib.close_lib() + self.__dict__.clear() + # + if isinstance(libname, basestring): + try: + if not isinstance(libname, str): # unicode, on Python 2 + libname = libname.encode('utf-8') + FFILibrary.__name__ = 'FFILibrary_%s' % libname + except UnicodeError: + pass + library = FFILibrary() + return library, library.__dict__ + +def _builtin_function_type(func): + # a hack to make at least ffi.typeof(builtin_function) work, + # if the builtin function was obtained by 'vengine_cpy'. + import sys + try: + module = sys.modules[func.__module__] + ffi = module._cffi_original_ffi + types_of_builtin_funcs = module._cffi_types_of_builtin_funcs + tp = types_of_builtin_funcs[func] + except (KeyError, AttributeError, TypeError): + return None + else: + with ffi._lock: + return ffi._get_cached_btype(tp) diff --git a/.venv/lib/python3.11/site-packages/cffi/cparser.py b/.venv/lib/python3.11/site-packages/cffi/cparser.py new file mode 100644 index 0000000000000000000000000000000000000000..eee83caffbd387ad0fda3868e87c6fb394cc54ee --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/cparser.py @@ -0,0 +1,1015 @@ +from . import model +from .commontypes import COMMON_TYPES, resolve_common_type +from .error import FFIError, CDefError +try: + from . import _pycparser as pycparser +except ImportError: + import pycparser +import weakref, re, sys + +try: + if sys.version_info < (3,): + import thread as _thread + else: + import _thread + lock = _thread.allocate_lock() +except ImportError: + lock = None + +def _workaround_for_static_import_finders(): + # Issue #392: packaging tools like cx_Freeze can not find these + # because pycparser uses exec dynamic import. This is an obscure + # workaround. This function is never called. + import pycparser.yacctab + import pycparser.lextab + +CDEF_SOURCE_STRING = "" +_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", + re.DOTALL | re.MULTILINE) +_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" + r"\b((?:[^\n\\]|\\.)*?)$", + re.DOTALL | re.MULTILINE) +_r_line_directive = re.compile(r"^[ \t]*#[ \t]*(?:line|\d+)\b.*$", re.MULTILINE) +_r_partial_enum = re.compile(r"=\s*\.\.\.\s*[,}]|\.\.\.\s*\}") +_r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") +_r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") +_r_words = re.compile(r"\w+|\S") +_parser_cache = None +_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) +_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") +_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") +_r_cdecl = re.compile(r"\b__cdecl\b") +_r_extern_python = re.compile(r'\bextern\s*"' + r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') +_r_star_const_space = re.compile( # matches "* const " + r"[*]\s*((const|volatile|restrict)\b\s*)+") +_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" + r"\.\.\.") +_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") + +def _get_parser(): + global _parser_cache + if _parser_cache is None: + _parser_cache = pycparser.CParser() + return _parser_cache + +def _workaround_for_old_pycparser(csource): + # Workaround for a pycparser issue (fixed between pycparser 2.10 and + # 2.14): "char*const***" gives us a wrong syntax tree, the same as + # for "char***(*const)". This means we can't tell the difference + # afterwards. But "char(*const(***))" gives us the right syntax + # tree. The issue only occurs if there are several stars in + # sequence with no parenthesis inbetween, just possibly qualifiers. + # Attempt to fix it by adding some parentheses in the source: each + # time we see "* const" or "* const *", we add an opening + # parenthesis before each star---the hard part is figuring out where + # to close them. + parts = [] + while True: + match = _r_star_const_space.search(csource) + if not match: + break + #print repr(''.join(parts)+csource), '=>', + parts.append(csource[:match.start()]) + parts.append('('); closing = ')' + parts.append(match.group()) # e.g. "* const " + endpos = match.end() + if csource.startswith('*', endpos): + parts.append('('); closing += ')' + level = 0 + i = endpos + while i < len(csource): + c = csource[i] + if c == '(': + level += 1 + elif c == ')': + if level == 0: + break + level -= 1 + elif c in ',;=': + if level == 0: + break + i += 1 + csource = csource[endpos:i] + closing + csource[i:] + #print repr(''.join(parts)+csource) + parts.append(csource) + return ''.join(parts) + +def _preprocess_extern_python(csource): + # input: `extern "Python" int foo(int);` or + # `extern "Python" { int foo(int); }` + # output: + # void __cffi_extern_python_start; + # int foo(int); + # void __cffi_extern_python_stop; + # + # input: `extern "Python+C" int foo(int);` + # output: + # void __cffi_extern_python_plus_c_start; + # int foo(int); + # void __cffi_extern_python_stop; + parts = [] + while True: + match = _r_extern_python.search(csource) + if not match: + break + endpos = match.end() - 1 + #print + #print ''.join(parts)+csource + #print '=>' + parts.append(csource[:match.start()]) + if 'C' in match.group(1): + parts.append('void __cffi_extern_python_plus_c_start; ') + else: + parts.append('void __cffi_extern_python_start; ') + if csource[endpos] == '{': + # grouping variant + closing = csource.find('}', endpos) + if closing < 0: + raise CDefError("'extern \"Python\" {': no '}' found") + if csource.find('{', endpos + 1, closing) >= 0: + raise NotImplementedError("cannot use { } inside a block " + "'extern \"Python\" { ... }'") + parts.append(csource[endpos+1:closing]) + csource = csource[closing+1:] + else: + # non-grouping variant + semicolon = csource.find(';', endpos) + if semicolon < 0: + raise CDefError("'extern \"Python\": no ';' found") + parts.append(csource[endpos:semicolon+1]) + csource = csource[semicolon+1:] + parts.append(' void __cffi_extern_python_stop;') + #print ''.join(parts)+csource + #print + parts.append(csource) + return ''.join(parts) + +def _warn_for_string_literal(csource): + if '"' not in csource: + return + for line in csource.splitlines(): + if '"' in line and not line.lstrip().startswith('#'): + import warnings + warnings.warn("String literal found in cdef() or type source. " + "String literals are ignored here, but you should " + "remove them anyway because some character sequences " + "confuse pre-parsing.") + break + +def _warn_for_non_extern_non_static_global_variable(decl): + if not decl.storage: + import warnings + warnings.warn("Global variable '%s' in cdef(): for consistency " + "with C it should have a storage class specifier " + "(usually 'extern')" % (decl.name,)) + +def _remove_line_directives(csource): + # _r_line_directive matches whole lines, without the final \n, if they + # start with '#line' with some spacing allowed, or '#NUMBER'. This + # function stores them away and replaces them with exactly the string + # '#line@N', where N is the index in the list 'line_directives'. + line_directives = [] + def replace(m): + i = len(line_directives) + line_directives.append(m.group()) + return '#line@%d' % i + csource = _r_line_directive.sub(replace, csource) + return csource, line_directives + +def _put_back_line_directives(csource, line_directives): + def replace(m): + s = m.group() + if not s.startswith('#line@'): + raise AssertionError("unexpected #line directive " + "(should have been processed and removed") + return line_directives[int(s[6:])] + return _r_line_directive.sub(replace, csource) + +def _preprocess(csource): + # First, remove the lines of the form '#line N "filename"' because + # the "filename" part could confuse the rest + csource, line_directives = _remove_line_directives(csource) + # Remove comments. NOTE: this only work because the cdef() section + # should not contain any string literals (except in line directives)! + def replace_keeping_newlines(m): + return ' ' + m.group().count('\n') * '\n' + csource = _r_comment.sub(replace_keeping_newlines, csource) + # Remove the "#define FOO x" lines + macros = {} + for match in _r_define.finditer(csource): + macroname, macrovalue = match.groups() + macrovalue = macrovalue.replace('\\\n', '').strip() + macros[macroname] = macrovalue + csource = _r_define.sub('', csource) + # + if pycparser.__version__ < '2.14': + csource = _workaround_for_old_pycparser(csource) + # + # BIG HACK: replace WINAPI or __stdcall with "volatile const". + # It doesn't make sense for the return type of a function to be + # "volatile volatile const", so we abuse it to detect __stdcall... + # Hack number 2 is that "int(volatile *fptr)();" is not valid C + # syntax, so we place the "volatile" before the opening parenthesis. + csource = _r_stdcall2.sub(' volatile volatile const(', csource) + csource = _r_stdcall1.sub(' volatile volatile const ', csource) + csource = _r_cdecl.sub(' ', csource) + # + # Replace `extern "Python"` with start/end markers + csource = _preprocess_extern_python(csource) + # + # Now there should not be any string literal left; warn if we get one + _warn_for_string_literal(csource) + # + # Replace "[...]" with "[__dotdotdotarray__]" + csource = _r_partial_array.sub('[__dotdotdotarray__]', csource) + # + # Replace "...}" with "__dotdotdotNUM__}". This construction should + # occur only at the end of enums; at the end of structs we have "...;}" + # and at the end of vararg functions "...);". Also replace "=...[,}]" + # with ",__dotdotdotNUM__[,}]": this occurs in the enums too, when + # giving an unknown value. + matches = list(_r_partial_enum.finditer(csource)) + for number, match in enumerate(reversed(matches)): + p = match.start() + if csource[p] == '=': + p2 = csource.find('...', p, match.end()) + assert p2 > p + csource = '%s,__dotdotdot%d__ %s' % (csource[:p], number, + csource[p2+3:]) + else: + assert csource[p:p+3] == '...' + csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, + csource[p+3:]) + # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" + csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) + # Replace "float ..." or "double..." with "__dotdotdotfloat__" + csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) + # Replace all remaining "..." with the same name, "__dotdotdot__", + # which is declared with a typedef for the purpose of C parsing. + csource = csource.replace('...', ' __dotdotdot__ ') + # Finally, put back the line directives + csource = _put_back_line_directives(csource, line_directives) + return csource, macros + +def _common_type_names(csource): + # Look in the source for what looks like usages of types from the + # list of common types. A "usage" is approximated here as the + # appearance of the word, minus a "definition" of the type, which + # is the last word in a "typedef" statement. Approximative only + # but should be fine for all the common types. + look_for_words = set(COMMON_TYPES) + look_for_words.add(';') + look_for_words.add(',') + look_for_words.add('(') + look_for_words.add(')') + look_for_words.add('typedef') + words_used = set() + is_typedef = False + paren = 0 + previous_word = '' + for word in _r_words.findall(csource): + if word in look_for_words: + if word == ';': + if is_typedef: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + is_typedef = False + elif word == 'typedef': + is_typedef = True + paren = 0 + elif word == '(': + paren += 1 + elif word == ')': + paren -= 1 + elif word == ',': + if is_typedef and paren == 0: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + else: # word in COMMON_TYPES + words_used.add(word) + previous_word = word + return words_used + + +class Parser(object): + + def __init__(self): + self._declarations = {} + self._included_declarations = set() + self._anonymous_counter = 0 + self._structnode2type = weakref.WeakKeyDictionary() + self._options = {} + self._int_constants = {} + self._recomplete = [] + self._uses_new_feature = None + + def _parse(self, csource): + csource, macros = _preprocess(csource) + # XXX: for more efficiency we would need to poke into the + # internals of CParser... the following registers the + # typedefs, because their presence or absence influences the + # parsing itself (but what they are typedef'ed to plays no role) + ctn = _common_type_names(csource) + typenames = [] + for name in sorted(self._declarations): + if name.startswith('typedef '): + name = name[8:] + typenames.append(name) + ctn.discard(name) + typenames += sorted(ctn) + # + csourcelines = [] + csourcelines.append('# 1 ""') + for typename in typenames: + csourcelines.append('typedef int %s;' % typename) + csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' + ' __dotdotdot__;') + # this forces pycparser to consider the following in the file + # called from line 1 + csourcelines.append('# 1 "%s"' % (CDEF_SOURCE_STRING,)) + csourcelines.append(csource) + csourcelines.append('') # see test_missing_newline_bug + fullcsource = '\n'.join(csourcelines) + if lock is not None: + lock.acquire() # pycparser is not thread-safe... + try: + ast = _get_parser().parse(fullcsource) + except pycparser.c_parser.ParseError as e: + self.convert_pycparser_error(e, csource) + finally: + if lock is not None: + lock.release() + # csource will be used to find buggy source text + return ast, macros, csource + + def _convert_pycparser_error(self, e, csource): + # xxx look for ":NUM:" at the start of str(e) + # and interpret that as a line number. This will not work if + # the user gives explicit ``# NUM "FILE"`` directives. + line = None + msg = str(e) + match = re.match(r"%s:(\d+):" % (CDEF_SOURCE_STRING,), msg) + if match: + linenum = int(match.group(1), 10) + csourcelines = csource.splitlines() + if 1 <= linenum <= len(csourcelines): + line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) + if line: + msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) + else: + msg = 'parse error\n%s' % (msg,) + raise CDefError(msg) + + def parse(self, csource, override=False, packed=False, pack=None, + dllexport=False): + if packed: + if packed != True: + raise ValueError("'packed' should be False or True; use " + "'pack' to give another value") + if pack: + raise ValueError("cannot give both 'pack' and 'packed'") + pack = 1 + elif pack: + if pack & (pack - 1): + raise ValueError("'pack' must be a power of two, not %r" % + (pack,)) + else: + pack = 0 + prev_options = self._options + try: + self._options = {'override': override, + 'packed': pack, + 'dllexport': dllexport} + self._internal_parse(csource) + finally: + self._options = prev_options + + def _internal_parse(self, csource): + ast, macros, csource = self._parse(csource) + # add the macros + self._process_macros(macros) + # find the first "__dotdotdot__" and use that as a separator + # between the repeated typedefs and the real csource + iterator = iter(ast.ext) + for decl in iterator: + if decl.name == '__dotdotdot__': + break + else: + assert 0 + current_decl = None + # + try: + self._inside_extern_python = '__cffi_extern_python_stop' + for decl in iterator: + current_decl = decl + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise CDefError("typedef does not declare any name", + decl) + quals = 0 + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and + decl.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_type(decl) + elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and + isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and + isinstance(decl.type.type.type, + pycparser.c_ast.IdentifierType) and + decl.type.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_ptr_type(decl) + else: + realtype, quals = self._get_type_and_quals( + decl.type, name=decl.name, partial_length_ok=True, + typedef_example="*(%s *)0" % (decl.name,)) + self._declare('typedef ' + decl.name, realtype, quals=quals) + elif decl.__class__.__name__ == 'Pragma': + # skip pragma, only in pycparser 2.15 + import warnings + warnings.warn( + "#pragma in cdef() are entirely ignored. " + "They should be removed for now, otherwise your " + "code might behave differently in a future version " + "of CFFI if #pragma support gets added. Note that " + "'#pragma pack' needs to be replaced with the " + "'packed' keyword argument to cdef().") + else: + raise CDefError("unexpected <%s>: this construct is valid " + "C but not valid in cdef()" % + decl.__class__.__name__, decl) + except CDefError as e: + if len(e.args) == 1: + e.args = e.args + (current_decl,) + raise + except FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + if self._int_constants[key] == val: + return # ignore identical double declarations + raise FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _add_integer_constant(self, name, int_str): + int_str = int_str.lower().rstrip("ul") + neg = int_str.startswith('-') + if neg: + int_str = int_str[1:] + # "010" is not valid oct in py3 + if (int_str.startswith("0") and int_str != '0' + and not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + pyvalue = int(int_str, 0) + if neg: + pyvalue = -pyvalue + self._add_constants(name, pyvalue) + self._declare('macro ' + name, pyvalue) + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + if _r_int_literal.match(value): + self._add_integer_constant(key, value) + elif value == '...': + self._declare('macro ' + key, value) + else: + raise CDefError( + 'only supports one of the following syntax:\n' + ' #define %s ... (literally dot-dot-dot)\n' + ' #define %s NUMBER (with NUMBER an integer' + ' constant, decimal/hex/octal)\n' + 'got:\n' + ' #define %s %s' + % (key, key, key, value)) + + def _declare_function(self, tp, quals, decl): + tp = self._get_type_pointer(tp, quals) + if self._options.get('dllexport'): + tag = 'dllexport_python ' + elif self._inside_extern_python == '__cffi_extern_python_start': + tag = 'extern_python ' + elif self._inside_extern_python == '__cffi_extern_python_plus_c_start': + tag = 'extern_python_plus_c ' + else: + tag = 'function ' + self._declare(tag + decl.name, tp) + + def _parse_decl(self, decl): + node = decl.type + if isinstance(node, pycparser.c_ast.FuncDecl): + tp, quals = self._get_type_and_quals(node, name=decl.name) + assert isinstance(tp, model.RawFunctionType) + self._declare_function(tp, quals, decl) + else: + if isinstance(node, pycparser.c_ast.Struct): + self._get_struct_union_enum_type('struct', node) + elif isinstance(node, pycparser.c_ast.Union): + self._get_struct_union_enum_type('union', node) + elif isinstance(node, pycparser.c_ast.Enum): + self._get_struct_union_enum_type('enum', node) + elif not decl.name: + raise CDefError("construct does not declare any variable", + decl) + # + if decl.name: + tp, quals = self._get_type_and_quals(node, + partial_length_ok=True) + if tp.is_raw_function: + self._declare_function(tp, quals, decl) + elif (tp.is_integer_type() and + hasattr(decl, 'init') and + hasattr(decl.init, 'value') and + _r_int_literal.match(decl.init.value)): + self._add_integer_constant(decl.name, decl.init.value) + elif (tp.is_integer_type() and + isinstance(decl.init, pycparser.c_ast.UnaryOp) and + decl.init.op == '-' and + hasattr(decl.init.expr, 'value') and + _r_int_literal.match(decl.init.expr.value)): + self._add_integer_constant(decl.name, + '-' + decl.init.expr.value) + elif (tp is model.void_type and + decl.name.startswith('__cffi_extern_python_')): + # hack: `extern "Python"` in the C source is replaced + # with "void __cffi_extern_python_start;" and + # "void __cffi_extern_python_stop;" + self._inside_extern_python = decl.name + else: + if self._inside_extern_python !='__cffi_extern_python_stop': + raise CDefError( + "cannot declare constants or " + "variables with 'extern \"Python\"'") + if (quals & model.Q_CONST) and not tp.is_array_type: + self._declare('constant ' + decl.name, tp, quals=quals) + else: + _warn_for_non_extern_non_static_global_variable(decl) + self._declare('variable ' + decl.name, tp, quals=quals) + + def parse_type(self, cdecl): + return self.parse_type_and_quals(cdecl)[0] + + def parse_type_and_quals(self, cdecl): + ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] + assert not macros + exprnode = ast.ext[-1].type.args.params[0] + if isinstance(exprnode, pycparser.c_ast.ID): + raise CDefError("unknown identifier '%s'" % (exprnode.name,)) + return self._get_type_and_quals(exprnode.type) + + def _declare(self, name, obj, included=False, quals=0): + if name in self._declarations: + prevobj, prevquals = self._declarations[name] + if prevobj is obj and prevquals == quals: + return + if not self._options.get('override'): + raise FFIError( + "multiple declarations of %s (for interactive usage, " + "try cdef(xx, override=True))" % (name,)) + assert '__dotdotdot__' not in name.split() + self._declarations[name] = (obj, quals) + if included: + self._included_declarations.add(obj) + + def _extract_quals(self, type): + quals = 0 + if isinstance(type, (pycparser.c_ast.TypeDecl, + pycparser.c_ast.PtrDecl)): + if 'const' in type.quals: + quals |= model.Q_CONST + if 'volatile' in type.quals: + quals |= model.Q_VOLATILE + if 'restrict' in type.quals: + quals |= model.Q_RESTRICT + return quals + + def _get_type_pointer(self, type, quals, declname=None): + if isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname, quals) + return model.PointerType(type, quals) + + def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False, + typedef_example=None): + # first, dereference typedefs, if we have it already parsed, we're good + if (isinstance(typenode, pycparser.c_ast.TypeDecl) and + isinstance(typenode.type, pycparser.c_ast.IdentifierType) and + len(typenode.type.names) == 1 and + ('typedef ' + typenode.type.names[0]) in self._declarations): + tp, quals = self._declarations['typedef ' + typenode.type.names[0]] + quals |= self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.ArrayDecl): + # array type + if typenode.dim is None: + length = None + else: + length = self._parse_constant( + typenode.dim, partial_length_ok=partial_length_ok) + # a hack: in 'typedef int foo_t[...][...];', don't use '...' as + # the length but use directly the C expression that would be + # generated by recompiler.py. This lets the typedef be used in + # many more places within recompiler.py + if typedef_example is not None: + if length == '...': + length = '_cffi_array_len(%s)' % (typedef_example,) + typedef_example = "*" + typedef_example + # + tp, quals = self._get_type_and_quals(typenode.type, + partial_length_ok=partial_length_ok, + typedef_example=typedef_example) + return model.ArrayType(tp, length), quals + # + if isinstance(typenode, pycparser.c_ast.PtrDecl): + # pointer type + itemtype, itemquals = self._get_type_and_quals(typenode.type) + tp = self._get_type_pointer(itemtype, itemquals, declname=name) + quals = self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.TypeDecl): + quals = self._extract_quals(typenode) + type = typenode.type + if isinstance(type, pycparser.c_ast.IdentifierType): + # assume a primitive type. get it from .names, but reduce + # synonyms to a single chosen combination + names = list(type.names) + if names != ['signed', 'char']: # keep this unmodified + prefixes = {} + while names: + name = names[0] + if name in ('short', 'long', 'signed', 'unsigned'): + prefixes[name] = prefixes.get(name, 0) + 1 + del names[0] + else: + break + # ignore the 'signed' prefix below, and reorder the others + newnames = [] + for prefix in ('unsigned', 'short', 'long'): + for i in range(prefixes.get(prefix, 0)): + newnames.append(prefix) + if not names: + names = ['int'] # implicitly + if names == ['int']: # but kill it if 'short' or 'long' + if 'short' in prefixes or 'long' in prefixes: + names = [] + names = newnames + names + ident = ' '.join(names) + if ident == 'void': + return model.void_type, quals + if ident == '__dotdotdot__': + raise FFIError(':%d: bad usage of "..."' % + typenode.coord.line) + tp0, quals0 = resolve_common_type(self, ident) + return tp0, (quals | quals0) + # + if isinstance(type, pycparser.c_ast.Struct): + # 'struct foobar' + tp = self._get_struct_union_enum_type('struct', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Union): + # 'union foobar' + tp = self._get_struct_union_enum_type('union', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Enum): + # 'enum foobar' + tp = self._get_struct_union_enum_type('enum', type, name) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.FuncDecl): + # a function type + return self._parse_function_type(typenode, name), 0 + # + # nested anonymous structs or unions end up here + if isinstance(typenode, pycparser.c_ast.Struct): + return self._get_struct_union_enum_type('struct', typenode, name, + nested=True), 0 + if isinstance(typenode, pycparser.c_ast.Union): + return self._get_struct_union_enum_type('union', typenode, name, + nested=True), 0 + # + raise FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) + + def _parse_function_type(self, typenode, funcname=None): + params = list(getattr(typenode.args, 'params', [])) + for i, arg in enumerate(params): + if not hasattr(arg, 'type'): + raise CDefError("%s arg %d: unknown type '%s'" + " (if you meant to use the old C syntax of giving" + " untyped arguments, it is not supported)" + % (funcname or 'in expression', i + 1, + getattr(arg, 'name', '?'))) + ellipsis = ( + len(params) > 0 and + isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and + isinstance(params[-1].type.type, + pycparser.c_ast.IdentifierType) and + params[-1].type.type.names == ['__dotdotdot__']) + if ellipsis: + params.pop() + if not params: + raise CDefError( + "%s: a function with only '(...)' as argument" + " is not correct C" % (funcname or 'in expression')) + args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) + for argdeclnode in params] + if not ellipsis and args == [model.void_type]: + args = [] + result, quals = self._get_type_and_quals(typenode.type) + # the 'quals' on the result type are ignored. HACK: we absure them + # to detect __stdcall functions: we textually replace "__stdcall" + # with "volatile volatile const" above. + abi = None + if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway + if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: + abi = '__stdcall' + return model.RawFunctionType(tuple(args), result, ellipsis, abi) + + def _as_func_arg(self, type, quals): + if isinstance(type, model.ArrayType): + return model.PointerType(type.item, quals) + elif isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + else: + return type + + def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): + # First, a level of caching on the exact 'type' node of the AST. + # This is obscure, but needed because pycparser "unrolls" declarations + # such as "typedef struct { } foo_t, *foo_p" and we end up with + # an AST that is not a tree, but a DAG, with the "type" node of the + # two branches foo_t and foo_p of the trees being the same node. + # It's a bit silly but detecting "DAG-ness" in the AST tree seems + # to be the only way to distinguish this case from two independent + # structs. See test_struct_with_two_usages. + try: + return self._structnode2type[type] + except KeyError: + pass + # + # Note that this must handle parsing "struct foo" any number of + # times and always return the same StructType object. Additionally, + # one of these times (not necessarily the first), the fields of + # the struct can be specified with "struct foo { ...fields... }". + # If no name is given, then we have to create a new anonymous struct + # with no caching; in this case, the fields are either specified + # right now or never. + # + force_name = name + name = type.name + # + # get the type or create it if needed + if name is None: + # 'force_name' is used to guess a more readable name for + # anonymous structs, for the common case "typedef struct { } foo". + if force_name is not None: + explicit_name = '$%s' % force_name + else: + self._anonymous_counter += 1 + explicit_name = '$%d' % self._anonymous_counter + tp = None + else: + explicit_name = name + key = '%s %s' % (kind, name) + tp, _ = self._declarations.get(key, (None, None)) + # + if tp is None: + if kind == 'struct': + tp = model.StructType(explicit_name, None, None, None) + elif kind == 'union': + tp = model.UnionType(explicit_name, None, None, None) + elif kind == 'enum': + if explicit_name == '__dotdotdot__': + raise CDefError("Enums cannot be declared with ...") + tp = self._build_enum_type(explicit_name, type.values) + else: + raise AssertionError("kind = %r" % (kind,)) + if name is not None: + self._declare(key, tp) + else: + if kind == 'enum' and type.values is not None: + raise NotImplementedError( + "enum %s: the '{}' declaration should appear on the first " + "time the enum is mentioned, not later" % explicit_name) + if not tp.forcename: + tp.force_the_name(force_name) + if tp.forcename and '$' in tp.name: + self._declare('anonymous %s' % tp.forcename, tp) + # + self._structnode2type[type] = tp + # + # enums: done here + if kind == 'enum': + return tp + # + # is there a 'type.decls'? If yes, then this is the place in the + # C sources that declare the fields. If no, then just return the + # existing type, possibly still incomplete. + if type.decls is None: + return tp + # + if tp.fldnames is not None: + raise CDefError("duplicate declaration of struct %s" % name) + fldnames = [] + fldtypes = [] + fldbitsize = [] + fldquals = [] + for decl in type.decls: + if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and + ''.join(decl.type.names) == '__dotdotdot__'): + # XXX pycparser is inconsistent: 'names' should be a list + # of strings, but is sometimes just one string. Use + # str.join() as a way to cope with both. + self._make_partial(tp, nested) + continue + if decl.bitsize is None: + bitsize = -1 + else: + bitsize = self._parse_constant(decl.bitsize) + self._partial_length = False + type, fqual = self._get_type_and_quals(decl.type, + partial_length_ok=True) + if self._partial_length: + self._make_partial(tp, nested) + if isinstance(type, model.StructType) and type.partial: + self._make_partial(tp, nested) + fldnames.append(decl.name or '') + fldtypes.append(type) + fldbitsize.append(bitsize) + fldquals.append(fqual) + tp.fldnames = tuple(fldnames) + tp.fldtypes = tuple(fldtypes) + tp.fldbitsize = tuple(fldbitsize) + tp.fldquals = tuple(fldquals) + if fldbitsize != [-1] * len(fldbitsize): + if isinstance(tp, model.StructType) and tp.partial: + raise NotImplementedError("%s: using both bitfields and '...;'" + % (tp,)) + tp.packed = self._options.get('packed') + if tp.completed: # must be re-completed: it is not opaque any more + tp.completed = 0 + self._recomplete.append(tp) + return tp + + def _make_partial(self, tp, nested): + if not isinstance(tp, model.StructOrUnion): + raise CDefError("%s cannot be partial" % (tp,)) + if not tp.has_c_name() and not nested: + raise NotImplementedError("%s is partial but has no C name" %(tp,)) + tp.partial = True + + def _parse_constant(self, exprnode, partial_length_ok=False): + # for now, limited to expressions that are an immediate number + # or positive/negative number + if isinstance(exprnode, pycparser.c_ast.Constant): + s = exprnode.value + if '0' <= s[0] <= '9': + s = s.rstrip('uUlL') + try: + if s.startswith('0'): + return int(s, 8) + else: + return int(s, 10) + except ValueError: + if len(s) > 1: + if s.lower()[0:2] == '0x': + return int(s, 16) + elif s.lower()[0:2] == '0b': + return int(s, 2) + raise CDefError("invalid constant %r" % (s,)) + elif s[0] == "'" and s[-1] == "'" and ( + len(s) == 3 or (len(s) == 4 and s[1] == "\\")): + return ord(s[-2]) + else: + raise CDefError("invalid constant %r" % (s,)) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '+'): + return self._parse_constant(exprnode.expr) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '-'): + return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] + # + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name == '__dotdotdotarray__'): + if partial_length_ok: + self._partial_length = True + return '...' + raise FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) + # + if isinstance(exprnode, pycparser.c_ast.BinaryOp): + left = self._parse_constant(exprnode.left) + right = self._parse_constant(exprnode.right) + if exprnode.op == '+': + return left + right + elif exprnode.op == '-': + return left - right + elif exprnode.op == '*': + return left * right + elif exprnode.op == '/': + return self._c_div(left, right) + elif exprnode.op == '%': + return left - self._c_div(left, right) * right + elif exprnode.op == '<<': + return left << right + elif exprnode.op == '>>': + return left >> right + elif exprnode.op == '&': + return left & right + elif exprnode.op == '|': + return left | right + elif exprnode.op == '^': + return left ^ right + # + raise FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) + + def _c_div(self, a, b): + result = a // b + if ((a < 0) ^ (b < 0)) and (a % b) != 0: + result += 1 + return result + + def _build_enum_type(self, explicit_name, decls): + if decls is not None: + partial = False + enumerators = [] + enumvalues = [] + nextenumvalue = 0 + for enum in decls.enumerators: + if _r_enum_dotdotdot.match(enum.name): + partial = True + continue + if enum.value is not None: + nextenumvalue = self._parse_constant(enum.value) + enumerators.append(enum.name) + enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) + nextenumvalue += 1 + enumerators = tuple(enumerators) + enumvalues = tuple(enumvalues) + tp = model.EnumType(explicit_name, enumerators, enumvalues) + tp.partial = partial + else: # opaque enum + tp = model.EnumType(explicit_name, (), ()) + return tp + + def include(self, other): + for name, (tp, quals) in other._declarations.items(): + if name.startswith('anonymous $enum_$'): + continue # fix for test_anonymous_enum_include + kind = name.split(' ', 1)[0] + if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef'): + self._declare(name, tp, included=True, quals=quals) + for k, v in other._int_constants.items(): + self._add_constants(k, v) + + def _get_unknown_type(self, decl): + typenames = decl.type.type.names + if typenames == ['__dotdotdot__']: + return model.unknown_type(decl.name) + + if typenames == ['__dotdotdotint__']: + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef int... %s'" % decl.name + return model.UnknownIntegerType(decl.name) + + if typenames == ['__dotdotdotfloat__']: + # note: not for 'long double' so far + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef float... %s'" % decl.name + return model.UnknownFloatType(decl.name) + + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) + + def _get_unknown_ptr_type(self, decl): + if decl.type.type.type.names == ['__dotdotdot__']: + return model.unknown_ptr_type(decl.name) + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) diff --git a/.venv/lib/python3.11/site-packages/cffi/ffiplatform.py b/.venv/lib/python3.11/site-packages/cffi/ffiplatform.py new file mode 100644 index 0000000000000000000000000000000000000000..adca28f1a480bb04a11977d26457fe8886139043 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/ffiplatform.py @@ -0,0 +1,113 @@ +import sys, os +from .error import VerificationError + + +LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', + 'extra_objects', 'depends'] + +def get_extension(srcfilename, modname, sources=(), **kwds): + from cffi._shimmed_dist_utils import Extension + allsources = [srcfilename] + for src in sources: + allsources.append(os.path.normpath(src)) + return Extension(name=modname, sources=allsources, **kwds) + +def compile(tmpdir, ext, compiler_verbose=0, debug=None): + """Compile a C extension module using distutils.""" + + saved_environ = os.environ.copy() + try: + outputfilename = _build(tmpdir, ext, compiler_verbose, debug) + outputfilename = os.path.abspath(outputfilename) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename + +def _build(tmpdir, ext, compiler_verbose=0, debug=None): + # XXX compact but horrible :-( + from cffi._shimmed_dist_utils import Distribution, CompileError, LinkError, set_threshold, set_verbosity + + dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() + options = dist.get_option_dict('build_ext') + if debug is None: + debug = sys.flags.debug + options['debug'] = ('ffiplatform', debug) + options['force'] = ('ffiplatform', True) + options['build_lib'] = ('ffiplatform', tmpdir) + options['build_temp'] = ('ffiplatform', tmpdir) + # + try: + old_level = set_threshold(0) or 0 + try: + set_verbosity(compiler_verbose) + dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() + finally: + set_threshold(old_level) + except (CompileError, LinkError) as e: + raise VerificationError('%s: %s' % (e.__class__.__name__, e)) + # + return soname + +try: + from os.path import samefile +except ImportError: + def samefile(f1, f2): + return os.path.abspath(f1) == os.path.abspath(f2) + +def maybe_relative_path(path): + if not os.path.isabs(path): + return path # already relative + dir = path + names = [] + while True: + prevdir = dir + dir, name = os.path.split(prevdir) + if dir == prevdir or not dir: + return path # failed to make it relative + names.append(name) + try: + if samefile(dir, os.curdir): + names.reverse() + return os.path.join(*names) + except OSError: + pass + +# ____________________________________________________________ + +try: + int_or_long = (int, long) + import cStringIO +except NameError: + int_or_long = int # Python 3 + import io as cStringIO + +def _flatten(x, f): + if isinstance(x, str): + f.write('%ds%s' % (len(x), x)) + elif isinstance(x, dict): + keys = sorted(x.keys()) + f.write('%dd' % len(keys)) + for key in keys: + _flatten(key, f) + _flatten(x[key], f) + elif isinstance(x, (list, tuple)): + f.write('%dl' % len(x)) + for value in x: + _flatten(value, f) + elif isinstance(x, int_or_long): + f.write('%di' % (x,)) + else: + raise TypeError( + "the keywords to verify() contains unsupported object %r" % (x,)) + +def flatten(x): + f = cStringIO.StringIO() + _flatten(x, f) + return f.getvalue() diff --git a/.venv/lib/python3.11/site-packages/cffi/model.py b/.venv/lib/python3.11/site-packages/cffi/model.py new file mode 100644 index 0000000000000000000000000000000000000000..e5f4cae3e84c73cd09980dabd2ec571d455fe0c1 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/model.py @@ -0,0 +1,618 @@ +import types +import weakref + +from .lock import allocate_lock +from .error import CDefError, VerificationError, VerificationMissing + +# type qualifiers +Q_CONST = 0x01 +Q_RESTRICT = 0x02 +Q_VOLATILE = 0x04 + +def qualify(quals, replace_with): + if quals & Q_CONST: + replace_with = ' const ' + replace_with.lstrip() + if quals & Q_VOLATILE: + replace_with = ' volatile ' + replace_with.lstrip() + if quals & Q_RESTRICT: + # It seems that __restrict is supported by gcc and msvc. + # If you hit some different compiler, add a #define in + # _cffi_include.h for it (and in its copies, documented there) + replace_with = ' __restrict ' + replace_with.lstrip() + return replace_with + + +class BaseTypeByIdentity(object): + is_array_type = False + is_raw_function = False + + def get_c_name(self, replace_with='', context='a C file', quals=0): + result = self.c_name_with_marker + assert result.count('&') == 1 + # some logic duplication with ffi.getctype()... :-( + replace_with = replace_with.strip() + if replace_with: + if replace_with.startswith('*') and '&[' in result: + replace_with = '(%s)' % replace_with + elif not replace_with[0] in '[(': + replace_with = ' ' + replace_with + replace_with = qualify(quals, replace_with) + result = result.replace('&', replace_with) + if '$' in result: + raise VerificationError( + "cannot generate '%s' in %s: unknown type name" + % (self._get_c_name(), context)) + return result + + def _get_c_name(self): + return self.c_name_with_marker.replace('&', '') + + def has_c_name(self): + return '$' not in self._get_c_name() + + def is_integer_type(self): + return False + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + try: + BType = ffi._cached_btypes[self] + except KeyError: + BType = self.build_backend_type(ffi, finishlist) + BType2 = ffi._cached_btypes.setdefault(self, BType) + assert BType2 is BType + return BType + + def __repr__(self): + return '<%s>' % (self._get_c_name(),) + + def _get_items(self): + return [(name, getattr(self, name)) for name in self._attrs_] + + +class BaseType(BaseTypeByIdentity): + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self._get_items() == other._get_items()) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.__class__, tuple(self._get_items()))) + + +class VoidType(BaseType): + _attrs_ = () + + def __init__(self): + self.c_name_with_marker = 'void&' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_void_type') + +void_type = VoidType() + + +class BasePrimitiveType(BaseType): + def is_complex_type(self): + return False + + +class PrimitiveType(BasePrimitiveType): + _attrs_ = ('name',) + + ALL_PRIMITIVE_TYPES = { + 'char': 'c', + 'short': 'i', + 'int': 'i', + 'long': 'i', + 'long long': 'i', + 'signed char': 'i', + 'unsigned char': 'i', + 'unsigned short': 'i', + 'unsigned int': 'i', + 'unsigned long': 'i', + 'unsigned long long': 'i', + 'float': 'f', + 'double': 'f', + 'long double': 'f', + '_cffi_float_complex_t': 'j', + '_cffi_double_complex_t': 'j', + '_Bool': 'i', + # the following types are not primitive in the C sense + 'wchar_t': 'c', + 'char16_t': 'c', + 'char32_t': 'c', + 'int8_t': 'i', + 'uint8_t': 'i', + 'int16_t': 'i', + 'uint16_t': 'i', + 'int32_t': 'i', + 'uint32_t': 'i', + 'int64_t': 'i', + 'uint64_t': 'i', + 'int_least8_t': 'i', + 'uint_least8_t': 'i', + 'int_least16_t': 'i', + 'uint_least16_t': 'i', + 'int_least32_t': 'i', + 'uint_least32_t': 'i', + 'int_least64_t': 'i', + 'uint_least64_t': 'i', + 'int_fast8_t': 'i', + 'uint_fast8_t': 'i', + 'int_fast16_t': 'i', + 'uint_fast16_t': 'i', + 'int_fast32_t': 'i', + 'uint_fast32_t': 'i', + 'int_fast64_t': 'i', + 'uint_fast64_t': 'i', + 'intptr_t': 'i', + 'uintptr_t': 'i', + 'intmax_t': 'i', + 'uintmax_t': 'i', + 'ptrdiff_t': 'i', + 'size_t': 'i', + 'ssize_t': 'i', + } + + def __init__(self, name): + assert name in self.ALL_PRIMITIVE_TYPES + self.name = name + self.c_name_with_marker = name + '&' + + def is_char_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'c' + def is_integer_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'i' + def is_float_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'f' + def is_complex_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'j' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_primitive_type', self.name) + + +class UnknownIntegerType(BasePrimitiveType): + _attrs_ = ('name',) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def is_integer_type(self): + return True + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("integer type '%s' can only be used after " + "compilation" % self.name) + +class UnknownFloatType(BasePrimitiveType): + _attrs_ = ('name', ) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("float type '%s' can only be used after " + "compilation" % self.name) + + +class BaseFunctionType(BaseType): + _attrs_ = ('args', 'result', 'ellipsis', 'abi') + + def __init__(self, args, result, ellipsis, abi=None): + self.args = args + self.result = result + self.ellipsis = ellipsis + self.abi = abi + # + reprargs = [arg._get_c_name() for arg in self.args] + if self.ellipsis: + reprargs.append('...') + reprargs = reprargs or ['void'] + replace_with = self._base_pattern % (', '.join(reprargs),) + if abi is not None: + replace_with = replace_with[:1] + abi + ' ' + replace_with[1:] + self.c_name_with_marker = ( + self.result.c_name_with_marker.replace('&', replace_with)) + + +class RawFunctionType(BaseFunctionType): + # Corresponds to a C type like 'int(int)', which is the C type of + # a function, but not a pointer-to-function. The backend has no + # notion of such a type; it's used temporarily by parsing. + _base_pattern = '(&)(%s)' + is_raw_function = True + + def build_backend_type(self, ffi, finishlist): + raise CDefError("cannot render the type %r: it is a function " + "type, not a pointer-to-function type" % (self,)) + + def as_function_pointer(self): + return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi) + + +class FunctionPtrType(BaseFunctionType): + _base_pattern = '(*&)(%s)' + + def build_backend_type(self, ffi, finishlist): + result = self.result.get_cached_btype(ffi, finishlist) + args = [] + for tp in self.args: + args.append(tp.get_cached_btype(ffi, finishlist)) + abi_args = () + if self.abi == "__stdcall": + if not self.ellipsis: # __stdcall ignored for variadic funcs + try: + abi_args = (ffi._backend.FFI_STDCALL,) + except AttributeError: + pass + return global_cache(self, ffi, 'new_function_type', + tuple(args), result, self.ellipsis, *abi_args) + + def as_raw_function(self): + return RawFunctionType(self.args, self.result, self.ellipsis, self.abi) + + +class PointerType(BaseType): + _attrs_ = ('totype', 'quals') + + def __init__(self, totype, quals=0): + self.totype = totype + self.quals = quals + extra = " *&" + if totype.is_array_type: + extra = "(%s)" % (extra.lstrip(),) + extra = qualify(quals, extra) + self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra) + + def build_backend_type(self, ffi, finishlist): + BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True) + return global_cache(self, ffi, 'new_pointer_type', BItem) + +voidp_type = PointerType(void_type) + +def ConstPointerType(totype): + return PointerType(totype, Q_CONST) + +const_voidp_type = ConstPointerType(void_type) + + +class NamedPointerType(PointerType): + _attrs_ = ('totype', 'name') + + def __init__(self, totype, name, quals=0): + PointerType.__init__(self, totype, quals) + self.name = name + self.c_name_with_marker = name + '&' + + +class ArrayType(BaseType): + _attrs_ = ('item', 'length') + is_array_type = True + + def __init__(self, item, length): + self.item = item + self.length = length + # + if length is None: + brackets = '&[]' + elif length == '...': + brackets = '&[/*...*/]' + else: + brackets = '&[%s]' % length + self.c_name_with_marker = ( + self.item.c_name_with_marker.replace('&', brackets)) + + def length_is_unknown(self): + return isinstance(self.length, str) + + def resolve_length(self, newlength): + return ArrayType(self.item, newlength) + + def build_backend_type(self, ffi, finishlist): + if self.length_is_unknown(): + raise CDefError("cannot render the type %r: unknown length" % + (self,)) + self.item.get_cached_btype(ffi, finishlist) # force the item BType + BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist) + return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length) + +char_array_type = ArrayType(PrimitiveType('char'), None) + + +class StructOrUnionOrEnum(BaseTypeByIdentity): + _attrs_ = ('name',) + forcename = None + + def build_c_name_with_marker(self): + name = self.forcename or '%s %s' % (self.kind, self.name) + self.c_name_with_marker = name + '&' + + def force_the_name(self, forcename): + self.forcename = forcename + self.build_c_name_with_marker() + + def get_official_name(self): + assert self.c_name_with_marker.endswith('&') + return self.c_name_with_marker[:-1] + + +class StructOrUnion(StructOrUnionOrEnum): + fixedlayout = None + completed = 0 + partial = False + packed = 0 + + def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None): + self.name = name + self.fldnames = fldnames + self.fldtypes = fldtypes + self.fldbitsize = fldbitsize + self.fldquals = fldquals + self.build_c_name_with_marker() + + def anonymous_struct_fields(self): + if self.fldtypes is not None: + for name, type in zip(self.fldnames, self.fldtypes): + if name == '' and isinstance(type, StructOrUnion): + yield type + + def enumfields(self, expand_anonymous_struct_union=True): + fldquals = self.fldquals + if fldquals is None: + fldquals = (0,) * len(self.fldnames) + for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes, + self.fldbitsize, fldquals): + if (name == '' and isinstance(type, StructOrUnion) + and expand_anonymous_struct_union): + # nested anonymous struct/union + for result in type.enumfields(): + yield result + else: + yield (name, type, bitsize, quals) + + def force_flatten(self): + # force the struct or union to have a declaration that lists + # directly all fields returned by enumfields(), flattening + # nested anonymous structs/unions. + names = [] + types = [] + bitsizes = [] + fldquals = [] + for name, type, bitsize, quals in self.enumfields(): + names.append(name) + types.append(type) + bitsizes.append(bitsize) + fldquals.append(quals) + self.fldnames = tuple(names) + self.fldtypes = tuple(types) + self.fldbitsize = tuple(bitsizes) + self.fldquals = tuple(fldquals) + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist, + can_delay) + if not can_delay: + self.finish_backend_type(ffi, finishlist) + return BType + + def finish_backend_type(self, ffi, finishlist): + if self.completed: + if self.completed != 2: + raise NotImplementedError("recursive structure declaration " + "for '%s'" % (self.name,)) + return + BType = ffi._cached_btypes[self] + # + self.completed = 1 + # + if self.fldtypes is None: + pass # not completing it: it's an opaque struct + # + elif self.fixedlayout is None: + fldtypes = [tp.get_cached_btype(ffi, finishlist) + for tp in self.fldtypes] + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize)) + extra_flags = () + if self.packed: + if self.packed == 1: + extra_flags = (8,) # SF_PACKED + else: + extra_flags = (0, self.packed) + ffi._backend.complete_struct_or_union(BType, lst, self, + -1, -1, *extra_flags) + # + else: + fldtypes = [] + fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout + for i in range(len(self.fldnames)): + fsize = fieldsize[i] + ftype = self.fldtypes[i] + # + if isinstance(ftype, ArrayType) and ftype.length_is_unknown(): + # fix the length to match the total size + BItemType = ftype.item.get_cached_btype(ffi, finishlist) + nlen, nrest = divmod(fsize, ffi.sizeof(BItemType)) + if nrest != 0: + self._verification_error( + "field '%s.%s' has a bogus size?" % ( + self.name, self.fldnames[i] or '{}')) + ftype = ftype.resolve_length(nlen) + self.fldtypes = (self.fldtypes[:i] + (ftype,) + + self.fldtypes[i+1:]) + # + BFieldType = ftype.get_cached_btype(ffi, finishlist) + if isinstance(ftype, ArrayType) and ftype.length is None: + assert fsize == 0 + else: + bitemsize = ffi.sizeof(BFieldType) + if bitemsize != fsize: + self._verification_error( + "field '%s.%s' is declared as %d bytes, but is " + "really %d bytes" % (self.name, + self.fldnames[i] or '{}', + bitemsize, fsize)) + fldtypes.append(BFieldType) + # + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs)) + ffi._backend.complete_struct_or_union(BType, lst, self, + totalsize, totalalignment) + self.completed = 2 + + def _verification_error(self, msg): + raise VerificationError(msg) + + def check_not_partial(self): + if self.partial and self.fixedlayout is None: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + finishlist.append(self) + # + return global_cache(self, ffi, 'new_%s_type' % self.kind, + self.get_official_name(), key=self) + + +class StructType(StructOrUnion): + kind = 'struct' + + +class UnionType(StructOrUnion): + kind = 'union' + + +class EnumType(StructOrUnionOrEnum): + kind = 'enum' + partial = False + partial_resolved = False + + def __init__(self, name, enumerators, enumvalues, baseinttype=None): + self.name = name + self.enumerators = enumerators + self.enumvalues = enumvalues + self.baseinttype = baseinttype + self.build_c_name_with_marker() + + def force_the_name(self, forcename): + StructOrUnionOrEnum.force_the_name(self, forcename) + if self.forcename is None: + name = self.get_official_name() + self.forcename = '$' + name.replace(' ', '_') + + def check_not_partial(self): + if self.partial and not self.partial_resolved: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + base_btype = self.build_baseinttype(ffi, finishlist) + return global_cache(self, ffi, 'new_enum_type', + self.get_official_name(), + self.enumerators, self.enumvalues, + base_btype, key=self) + + def build_baseinttype(self, ffi, finishlist): + if self.baseinttype is not None: + return self.baseinttype.get_cached_btype(ffi, finishlist) + # + if self.enumvalues: + smallest_value = min(self.enumvalues) + largest_value = max(self.enumvalues) + else: + import warnings + try: + # XXX! The goal is to ensure that the warnings.warn() + # will not suppress the warning. We want to get it + # several times if we reach this point several times. + __warningregistry__.clear() + except NameError: + pass + warnings.warn("%r has no values explicitly defined; " + "guessing that it is equivalent to 'unsigned int'" + % self._get_c_name()) + smallest_value = largest_value = 0 + if smallest_value < 0: # needs a signed type + sign = 1 + candidate1 = PrimitiveType("int") + candidate2 = PrimitiveType("long") + else: + sign = 0 + candidate1 = PrimitiveType("unsigned int") + candidate2 = PrimitiveType("unsigned long") + btype1 = candidate1.get_cached_btype(ffi, finishlist) + btype2 = candidate2.get_cached_btype(ffi, finishlist) + size1 = ffi.sizeof(btype1) + size2 = ffi.sizeof(btype2) + if (smallest_value >= ((-1) << (8*size1-1)) and + largest_value < (1 << (8*size1-sign))): + return btype1 + if (smallest_value >= ((-1) << (8*size2-1)) and + largest_value < (1 << (8*size2-sign))): + return btype2 + raise CDefError("%s values don't all fit into either 'long' " + "or 'unsigned long'" % self._get_c_name()) + +def unknown_type(name, structname=None): + if structname is None: + structname = '$%s' % name + tp = StructType(structname, None, None, None) + tp.force_the_name(name) + tp.origin = "unknown_type" + return tp + +def unknown_ptr_type(name, structname=None): + if structname is None: + structname = '$$%s' % name + tp = StructType(structname, None, None, None) + return NamedPointerType(tp, name) + + +global_lock = allocate_lock() +_typecache_cffi_backend = weakref.WeakValueDictionary() + +def get_typecache(backend): + # returns _typecache_cffi_backend if backend is the _cffi_backend + # module, or type(backend).__typecache if backend is an instance of + # CTypesBackend (or some FakeBackend class during tests) + if isinstance(backend, types.ModuleType): + return _typecache_cffi_backend + with global_lock: + if not hasattr(type(backend), '__typecache'): + type(backend).__typecache = weakref.WeakValueDictionary() + return type(backend).__typecache + +def global_cache(srctype, ffi, funcname, *args, **kwds): + key = kwds.pop('key', (funcname, args)) + assert not kwds + try: + return ffi._typecache[key] + except KeyError: + pass + try: + res = getattr(ffi._backend, funcname)(*args) + except NotImplementedError as e: + raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e)) + # note that setdefault() on WeakValueDictionary is not atomic + # and contains a rare bug (http://bugs.python.org/issue19542); + # we have to use a lock and do it ourselves + cache = ffi._typecache + with global_lock: + res1 = cache.get(key) + if res1 is None: + cache[key] = res + return res + else: + return res1 + +def pointer_cache(ffi, BType): + return global_cache('?', ffi, 'new_pointer_type', BType) + +def attach_exception_info(e, name): + if e.args and type(e.args[0]) is str: + e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:] diff --git a/.venv/lib/python3.11/site-packages/cffi/parse_c_type.h b/.venv/lib/python3.11/site-packages/cffi/parse_c_type.h new file mode 100644 index 0000000000000000000000000000000000000000..84e4ef85659eb63e6453d8af9f024f1866182342 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/parse_c_type.h @@ -0,0 +1,181 @@ + +/* This part is from file 'cffi/parse_c_type.h'. It is copied at the + beginning of C sources generated by CFFI's ffi.set_source(). */ + +typedef void *_cffi_opcode_t; + +#define _CFFI_OP(opcode, arg) (_cffi_opcode_t)(opcode | (((uintptr_t)(arg)) << 8)) +#define _CFFI_GETOP(cffi_opcode) ((unsigned char)(uintptr_t)cffi_opcode) +#define _CFFI_GETARG(cffi_opcode) (((intptr_t)cffi_opcode) >> 8) + +#define _CFFI_OP_PRIMITIVE 1 +#define _CFFI_OP_POINTER 3 +#define _CFFI_OP_ARRAY 5 +#define _CFFI_OP_OPEN_ARRAY 7 +#define _CFFI_OP_STRUCT_UNION 9 +#define _CFFI_OP_ENUM 11 +#define _CFFI_OP_FUNCTION 13 +#define _CFFI_OP_FUNCTION_END 15 +#define _CFFI_OP_NOOP 17 +#define _CFFI_OP_BITFIELD 19 +#define _CFFI_OP_TYPENAME 21 +#define _CFFI_OP_CPYTHON_BLTN_V 23 // varargs +#define _CFFI_OP_CPYTHON_BLTN_N 25 // noargs +#define _CFFI_OP_CPYTHON_BLTN_O 27 // O (i.e. a single arg) +#define _CFFI_OP_CONSTANT 29 +#define _CFFI_OP_CONSTANT_INT 31 +#define _CFFI_OP_GLOBAL_VAR 33 +#define _CFFI_OP_DLOPEN_FUNC 35 +#define _CFFI_OP_DLOPEN_CONST 37 +#define _CFFI_OP_GLOBAL_VAR_F 39 +#define _CFFI_OP_EXTERN_PYTHON 41 + +#define _CFFI_PRIM_VOID 0 +#define _CFFI_PRIM_BOOL 1 +#define _CFFI_PRIM_CHAR 2 +#define _CFFI_PRIM_SCHAR 3 +#define _CFFI_PRIM_UCHAR 4 +#define _CFFI_PRIM_SHORT 5 +#define _CFFI_PRIM_USHORT 6 +#define _CFFI_PRIM_INT 7 +#define _CFFI_PRIM_UINT 8 +#define _CFFI_PRIM_LONG 9 +#define _CFFI_PRIM_ULONG 10 +#define _CFFI_PRIM_LONGLONG 11 +#define _CFFI_PRIM_ULONGLONG 12 +#define _CFFI_PRIM_FLOAT 13 +#define _CFFI_PRIM_DOUBLE 14 +#define _CFFI_PRIM_LONGDOUBLE 15 + +#define _CFFI_PRIM_WCHAR 16 +#define _CFFI_PRIM_INT8 17 +#define _CFFI_PRIM_UINT8 18 +#define _CFFI_PRIM_INT16 19 +#define _CFFI_PRIM_UINT16 20 +#define _CFFI_PRIM_INT32 21 +#define _CFFI_PRIM_UINT32 22 +#define _CFFI_PRIM_INT64 23 +#define _CFFI_PRIM_UINT64 24 +#define _CFFI_PRIM_INTPTR 25 +#define _CFFI_PRIM_UINTPTR 26 +#define _CFFI_PRIM_PTRDIFF 27 +#define _CFFI_PRIM_SIZE 28 +#define _CFFI_PRIM_SSIZE 29 +#define _CFFI_PRIM_INT_LEAST8 30 +#define _CFFI_PRIM_UINT_LEAST8 31 +#define _CFFI_PRIM_INT_LEAST16 32 +#define _CFFI_PRIM_UINT_LEAST16 33 +#define _CFFI_PRIM_INT_LEAST32 34 +#define _CFFI_PRIM_UINT_LEAST32 35 +#define _CFFI_PRIM_INT_LEAST64 36 +#define _CFFI_PRIM_UINT_LEAST64 37 +#define _CFFI_PRIM_INT_FAST8 38 +#define _CFFI_PRIM_UINT_FAST8 39 +#define _CFFI_PRIM_INT_FAST16 40 +#define _CFFI_PRIM_UINT_FAST16 41 +#define _CFFI_PRIM_INT_FAST32 42 +#define _CFFI_PRIM_UINT_FAST32 43 +#define _CFFI_PRIM_INT_FAST64 44 +#define _CFFI_PRIM_UINT_FAST64 45 +#define _CFFI_PRIM_INTMAX 46 +#define _CFFI_PRIM_UINTMAX 47 +#define _CFFI_PRIM_FLOATCOMPLEX 48 +#define _CFFI_PRIM_DOUBLECOMPLEX 49 +#define _CFFI_PRIM_CHAR16 50 +#define _CFFI_PRIM_CHAR32 51 + +#define _CFFI__NUM_PRIM 52 +#define _CFFI__UNKNOWN_PRIM (-1) +#define _CFFI__UNKNOWN_FLOAT_PRIM (-2) +#define _CFFI__UNKNOWN_LONG_DOUBLE (-3) + +#define _CFFI__IO_FILE_STRUCT (-1) + + +struct _cffi_global_s { + const char *name; + void *address; + _cffi_opcode_t type_op; + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function +}; + +struct _cffi_getconst_s { + unsigned long long value; + const struct _cffi_type_context_s *ctx; + int gindex; +}; + +struct _cffi_struct_union_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_STRUCT_UNION + int flags; // _CFFI_F_* flags below + size_t size; + int alignment; + int first_field_index; // -> _cffi_fields array + int num_fields; +}; +#define _CFFI_F_UNION 0x01 // is a union, not a struct +#define _CFFI_F_CHECK_FIELDS 0x02 // complain if fields are not in the + // "standard layout" or if some are missing +#define _CFFI_F_PACKED 0x04 // for CHECK_FIELDS, assume a packed struct +#define _CFFI_F_EXTERNAL 0x08 // in some other ffi.include() +#define _CFFI_F_OPAQUE 0x10 // opaque + +struct _cffi_field_s { + const char *name; + size_t field_offset; + size_t field_size; + _cffi_opcode_t field_type_op; +}; + +struct _cffi_enum_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_ENUM + int type_prim; // _CFFI_PRIM_xxx + const char *enumerators; // comma-delimited string +}; + +struct _cffi_typename_s { + const char *name; + int type_index; /* if opaque, points to a possibly artificial + OP_STRUCT which is itself opaque */ +}; + +struct _cffi_type_context_s { + _cffi_opcode_t *types; + const struct _cffi_global_s *globals; + const struct _cffi_field_s *fields; + const struct _cffi_struct_union_s *struct_unions; + const struct _cffi_enum_s *enums; + const struct _cffi_typename_s *typenames; + int num_globals; + int num_struct_unions; + int num_enums; + int num_typenames; + const char *const *includes; + int num_types; + int flags; /* future extension */ +}; + +struct _cffi_parse_info_s { + const struct _cffi_type_context_s *ctx; + _cffi_opcode_t *output; + unsigned int output_size; + size_t error_location; + const char *error_message; +}; + +struct _cffi_externpy_s { + const char *name; + size_t size_of_result; + void *reserved1, *reserved2; +}; + +#ifdef _CFFI_INTERNAL +static int parse_c_type(struct _cffi_parse_info_s *info, const char *input); +static int search_in_globals(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +static int search_in_struct_unions(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +#endif diff --git a/.venv/lib/python3.11/site-packages/cffi/pkgconfig.py b/.venv/lib/python3.11/site-packages/cffi/pkgconfig.py new file mode 100644 index 0000000000000000000000000000000000000000..5c93f15a60e6f904b2dd108d6e22044a5890bcb4 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/pkgconfig.py @@ -0,0 +1,121 @@ +# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi +import sys, os, subprocess + +from .error import PkgConfigError + + +def merge_flags(cfg1, cfg2): + """Merge values from cffi config flags cfg2 to cf1 + + Example: + merge_flags({"libraries": ["one"]}, {"libraries": ["two"]}) + {"libraries": ["one", "two"]} + """ + for key, value in cfg2.items(): + if key not in cfg1: + cfg1[key] = value + else: + if not isinstance(cfg1[key], list): + raise TypeError("cfg1[%r] should be a list of strings" % (key,)) + if not isinstance(value, list): + raise TypeError("cfg2[%r] should be a list of strings" % (key,)) + cfg1[key].extend(value) + return cfg1 + + +def call(libname, flag, encoding=sys.getfilesystemencoding()): + """Calls pkg-config and returns the output if found + """ + a = ["pkg-config", "--print-errors"] + a.append(flag) + a.append(libname) + try: + pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except EnvironmentError as e: + raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),)) + + bout, berr = pc.communicate() + if pc.returncode != 0: + try: + berr = berr.decode(encoding) + except Exception: + pass + raise PkgConfigError(berr.strip()) + + if sys.version_info >= (3,) and not isinstance(bout, str): # Python 3.x + try: + bout = bout.decode(encoding) + except UnicodeDecodeError: + raise PkgConfigError("pkg-config %s %s returned bytes that cannot " + "be decoded with encoding %r:\n%r" % + (flag, libname, encoding, bout)) + + if os.altsep != '\\' and '\\' in bout: + raise PkgConfigError("pkg-config %s %s returned an unsupported " + "backslash-escaped output:\n%r" % + (flag, libname, bout)) + return bout + + +def flags_from_pkgconfig(libs): + r"""Return compiler line flags for FFI.set_source based on pkg-config output + + Usage + ... + ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"]) + + If pkg-config is installed on build machine, then arguments include_dirs, + library_dirs, libraries, define_macros, extra_compile_args and + extra_link_args are extended with an output of pkg-config for libfoo and + libbar. + + Raises PkgConfigError in case the pkg-config call fails. + """ + + def get_include_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-I")] + + def get_library_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-L")] + + def get_libraries(string): + return [x[2:] for x in string.split() if x.startswith("-l")] + + # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils + def get_macros(string): + def _macro(x): + x = x[2:] # drop "-D" + if '=' in x: + return tuple(x.split("=", 1)) # "-Dfoo=bar" => ("foo", "bar") + else: + return (x, None) # "-Dfoo" => ("foo", None) + return [_macro(x) for x in string.split() if x.startswith("-D")] + + def get_other_cflags(string): + return [x for x in string.split() if not x.startswith("-I") and + not x.startswith("-D")] + + def get_other_libs(string): + return [x for x in string.split() if not x.startswith("-L") and + not x.startswith("-l")] + + # return kwargs for given libname + def kwargs(libname): + fse = sys.getfilesystemencoding() + all_cflags = call(libname, "--cflags") + all_libs = call(libname, "--libs") + return { + "include_dirs": get_include_dirs(all_cflags), + "library_dirs": get_library_dirs(all_libs), + "libraries": get_libraries(all_libs), + "define_macros": get_macros(all_cflags), + "extra_compile_args": get_other_cflags(all_cflags), + "extra_link_args": get_other_libs(all_libs), + } + + # merge all arguments together + ret = {} + for libname in libs: + lib_flags = kwargs(libname) + merge_flags(ret, lib_flags) + return ret diff --git a/.venv/lib/python3.11/site-packages/cffi/vengine_cpy.py b/.venv/lib/python3.11/site-packages/cffi/vengine_cpy.py new file mode 100644 index 0000000000000000000000000000000000000000..eb0b6f70e499159819ed01a392205752c1b29936 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/vengine_cpy.py @@ -0,0 +1,1084 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys +from . import model +from .error import VerificationError +from . import _imp_emulation as imp + + +class VCPythonEngine(object): + _class_key = 'x' + _gen_python_module = True + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self._struct_pending_verification = {} + self._types_of_builtin_functions = {} + + def patch_extension_kwds(self, kwds): + pass + + def find_module(self, module_name, path, so_suffixes): + try: + f, filename, descr = imp.find_module(module_name, path) + except ImportError: + return None + if f is not None: + f.close() + # Note that after a setuptools installation, there are both .py + # and .so files with the same basename. The code here relies on + # imp.find_module() locating the .so in priority. + if descr[0] not in so_suffixes: + return None + return filename + + def collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _do_collect_type(self, tp): + if ((not isinstance(tp, model.PrimitiveType) + or tp.name == 'long double') + and tp not in self._typesdict): + num = len(self._typesdict) + self._typesdict[tp] = num + + def write_source_to_f(self): + self.collect_types() + # + # The new module will have a _cffi_setup() function that receives + # objects from the ffi world, and that calls some setup code in + # the module. This setup code is split in several independent + # functions, e.g. one per constant. The functions are "chained" + # by ending in a tail call to each other. + # + # This is further split in two chained lists, depending on if we + # can do it at import-time or if we must wait for _cffi_setup() to + # provide us with the objects. This is needed because we + # need the values of the enum constants in order to build the + # that we may have to pass to _cffi_setup(). + # + # The following two 'chained_list_constants' items contains + # the head of these two chained lists, as a string that gives the + # call to do, if any. + self._chained_list_constants = ['((void)lib,0)', '((void)lib,0)'] + # + prnt = self._prnt + # first paste some standard set of lines that are mostly '#define' + prnt(cffimod_header) + prnt() + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate("decl") + # + # implement the function _cffi_setup_custom() as calling the + # head of the chained list. + self._generate_setup_custom() + prnt() + # + # produce the method table, including the entries for the + # generated Python->C function wrappers, which are done + # by generate_cpy_function_method(). + prnt('static PyMethodDef _cffi_methods[] = {') + self._generate("method") + prnt(' {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL},') + prnt(' {NULL, NULL, 0, NULL} /* Sentinel */') + prnt('};') + prnt() + # + # standard init. + modname = self.verifier.get_module_name() + constants = self._chained_list_constants[False] + prnt('#if PY_MAJOR_VERSION >= 3') + prnt() + prnt('static struct PyModuleDef _cffi_module_def = {') + prnt(' PyModuleDef_HEAD_INIT,') + prnt(' "%s",' % modname) + prnt(' NULL,') + prnt(' -1,') + prnt(' _cffi_methods,') + prnt(' NULL, NULL, NULL, NULL') + prnt('};') + prnt() + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = PyModule_Create(&_cffi_module_def);') + prnt(' if (lib == NULL)') + prnt(' return NULL;') + prnt(' if (%s < 0 || _cffi_init() < 0) {' % (constants,)) + prnt(' Py_DECREF(lib);') + prnt(' return NULL;') + prnt(' }') + prnt(' return lib;') + prnt('}') + prnt() + prnt('#else') + prnt() + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = Py_InitModule("%s", _cffi_methods);' % modname) + prnt(' if (lib == NULL)') + prnt(' return;') + prnt(' if (%s < 0 || _cffi_init() < 0)' % (constants,)) + prnt(' return;') + prnt(' return;') + prnt('}') + prnt() + prnt('#endif') + + def load_library(self, flags=None): + # XXX review all usages of 'self' here! + # import it as a new extension module + imp.acquire_lock() + try: + if hasattr(sys, "getdlopenflags"): + previous_flags = sys.getdlopenflags() + try: + if hasattr(sys, "setdlopenflags") and flags is not None: + sys.setdlopenflags(flags) + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise VerificationError(error) + finally: + if hasattr(sys, "setdlopenflags"): + sys.setdlopenflags(previous_flags) + finally: + imp.release_lock() + # + # call loading_cpy_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # the C code will need the objects. Collect them in + # order in a list. + revmapping = dict([(value, key) + for (key, value) in self._typesdict.items()]) + lst = [revmapping[i] for i in range(len(revmapping))] + lst = list(map(self.ffi._get_cached_btype, lst)) + # + # build the FFILibrary class and instance and call _cffi_setup(). + # this will set up some fields like '_cffi_types', and only then + # it will invoke the chained list of functions that will really + # build (notably) the constant objects, as if they are + # pointers, and store them as attributes on the 'library' object. + class FFILibrary(object): + _cffi_python_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + list(self.__dict__) + library = FFILibrary() + if module._cffi_setup(lst, VerificationError, library): + import warnings + warnings.warn("reimporting %r might overwrite older definitions" + % (self.verifier.get_module_name())) + # + # finally, call the loaded_cpy_xxx() functions. This will perform + # the final adjustments, like copying the Python->C wrapper + # functions from the module to the 'library' object, and setting + # up the FFILibrary class with properties for the global C variables. + self._load(module, 'loaded', library=library) + module._cffi_original_ffi = self.ffi + module._cffi_types_of_builtin_funcs = self._types_of_builtin_functions + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif tp.is_complex_type(): + raise VerificationError( + "not implemented in verify(): complex types") + else: + converter = '(%s)_cffi_to_c_%s' % (tp.get_c_name(''), + tp.name.replace(' ', '_')) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif isinstance(tp, (model.StructOrUnion, model.EnumType)): + # a struct (not a struct pointer) as a function argument + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars, freelines): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + localvars.add('struct _cffi_freeme_s *large_args_free = NULL') + freelines.add('if (large_args_free != NULL)' + ' _cffi_free_array_arguments(large_args_free);') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' %s = ((size_t)datasize) <= 640 ? ' + 'alloca((size_t)datasize) : NULL;' % (tovar,)) + self._prnt(' if (_cffi_convert_array_argument(_cffi_type(%d), %s, ' + '(char **)&%s,' % (self._gettypenum(tp), fromvar, tovar)) + self._prnt(' datasize, &large_args_free) < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif tp.name != 'long double': + return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs: generates no code so far + + _generate_cpy_typedef_collecttype = _generate_nothing + _generate_cpy_typedef_decl = _generate_nothing + _generate_cpy_typedef_method = _generate_nothing + _loading_cpy_typedef = _loaded_noop + _loaded_cpy_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + self._do_collect_type(tp) + else: + # don't call _do_collect_type(tp) in this common case, + # otherwise test_autofilled_struct_as_argument fails + for type in tp.args: + self._do_collect_type(type) + self._do_collect_type(tp.result) + + def _generate_cpy_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + prnt(' %s;' % type.get_c_name(' x%d' % i, context)) + # + localvars = set() + freelines = set() + for type in tp.args: + self._extra_local_variables(type, localvars, freelines) + for decl in sorted(localvars): + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + prnt(' %s;' % tp.result.get_c_name(' result', context)) + prnt(' PyObject *pyresult;') + else: + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( + 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + prnt(' { %s%s(%s); }' % ( + result_code, name, + ', '.join(['x%d' % i for i in range(len(tp.args))]))) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' pyresult = %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + for freeline in freelines: + prnt(' ' + freeline) + prnt(' return pyresult;') + else: + for freeline in freelines: + prnt(' ' + freeline) + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + prnt() + + def _generate_cpy_function_method(self, tp, name): + if tp.ellipsis: + return + numargs = len(tp.args) + if numargs == 0: + meth = 'METH_NOARGS' + elif numargs == 1: + meth = 'METH_O' + else: + meth = 'METH_VARARGS' + self._prnt(' {"%s", _cffi_f_%s, %s, NULL},' % (name, name, meth)) + + _loading_cpy_function = _loaded_noop + + def _loaded_cpy_function(self, tp, name, module, library): + if tp.ellipsis: + return + func = getattr(module, name) + setattr(library, name, func) + self._types_of_builtin_functions[func] = tp + + # ---------- + # named structs + + _generate_cpy_struct_collecttype = _generate_nothing + def _generate_cpy_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + def _generate_cpy_struct_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'struct', name) + def _loading_cpy_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + def _loaded_cpy_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + _generate_cpy_union_collecttype = _generate_nothing + def _generate_cpy_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + def _generate_cpy_union_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'union', name) + def _loading_cpy_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + def _loaded_cpy_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('static PyObject *') + prnt('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static Py_ssize_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' (void)self; /* unused */') + prnt(' (void)noarg; /* unused */') + prnt(' return _cffi_get_struct_layout(nums);') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _generate_struct_or_union_method(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + self._prnt(' {"%s", %s, METH_NOARGS, NULL},' % (layoutfuncname, + layoutfuncname)) + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + function = getattr(module, layoutfuncname) + layout = function() + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + _generate_cpy_anonymous_collecttype = _generate_nothing + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _generate_cpy_anonymous_method(self, tp, name): + if not isinstance(tp, model.EnumType): + self._generate_struct_or_union_method(tp, '', name) + + def _loading_cpy_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_cpy_enum(tp, name, module) + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_cpy_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_cpy_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + vartp=None, delayed=True, size_too=False, + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + prnt(' PyObject *o;') + prnt(' int res;') + if not is_int: + prnt(' %s;' % (vartp or tp).get_c_name(' i', name)) + else: + assert category == 'const' + # + if check_value is not None: + self._check_int_constant_value(name, check_value) + # + if not is_int: + if category == 'var': + realexpr = '&' + name + else: + realexpr = name + prnt(' i = (%s);' % (realexpr,)) + prnt(' o = %s;' % (self._convert_expr_from_c(tp, 'i', + 'variable type'),)) + assert delayed + else: + prnt(' o = _cffi_from_c_int_const(%s);' % name) + prnt(' if (o == NULL)') + prnt(' return -1;') + if size_too: + prnt(' {') + prnt(' PyObject *o1 = o;') + prnt(' o = Py_BuildValue("On", o1, (Py_ssize_t)sizeof(%s));' + % (name,)) + prnt(' Py_DECREF(o1);') + prnt(' if (o == NULL)') + prnt(' return -1;') + prnt(' }') + prnt(' res = PyObject_SetAttrString(lib, "%s", o);' % name) + prnt(' Py_DECREF(o);') + prnt(' if (res < 0)') + prnt(' return -1;') + prnt(' return %s;' % self._chained_list_constants[delayed]) + self._chained_list_constants[delayed] = funcname + '(lib)' + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + if not is_int: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + _generate_cpy_constant_method = _generate_nothing + _loading_cpy_constant = _loaded_noop + _loaded_cpy_constant = _loaded_noop + + # ---------- + # enums + + def _check_int_constant_value(self, name, value, err_prefix=''): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' snprintf(buf, 63, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' snprintf(buf, 63, "%%lu", (unsigned long)(%s));' % + name) + prnt(' PyErr_Format(_cffi_VerificationError,') + prnt(' "%s%s has the real value %s, not %s",') + prnt(' "%s", "%s", buf, "%d");' % ( + err_prefix, name, value)) + prnt(' return -1;') + prnt(' }') + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_cpy_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator, delayed=False) + return + # + funcname = self._enum_funcname(prefix, name) + prnt = self._prnt + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue, + "enum %s: " % name) + prnt(' return %s;' % self._chained_list_constants[True]) + self._chained_list_constants[True] = funcname + '(lib)' + prnt('}') + prnt() + + _generate_cpy_enum_collecttype = _generate_nothing + _generate_cpy_enum_method = _generate_nothing + + def _loading_cpy_enum(self, tp, name, module): + if tp.partial: + enumvalues = [getattr(module, enumerator) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + + def _loaded_cpy_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + _generate_cpy_macro_collecttype = _generate_nothing + _generate_cpy_macro_method = _generate_nothing + _loading_cpy_macro = _loaded_noop + _loaded_cpy_macro = _loaded_noop + + # ---------- + # global variables + + def _generate_cpy_variable_collecttype(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + else: + tp_ptr = model.PointerType(tp) + self._do_collect_type(tp_ptr) + + def _generate_cpy_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_cpy_const(False, name, tp, vartp=tp_ptr, + size_too = tp.length_is_unknown()) + else: + tp_ptr = model.PointerType(tp) + self._generate_cpy_const(False, name, tp_ptr, category='var') + + _generate_cpy_variable_method = _generate_nothing + _loading_cpy_variable = _loaded_noop + + def _loaded_cpy_variable(self, tp, name, module, library): + value = getattr(library, name) + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length_is_unknown(): + assert isinstance(value, tuple) + (value, size) = value + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + ptr = value + delattr(library, name) + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + + # ---------- + + def _generate_setup_custom(self): + prnt = self._prnt + prnt('static int _cffi_setup_custom(PyObject *lib)') + prnt('{') + prnt(' return %s;' % self._chained_list_constants[True]) + prnt('}') + +cffimod_header = r''' +#include +#include + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_CheckExact +# undef PyCapsule_GetPointer +# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_NUM_EXPORTS 25 + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static int _cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + int was_alive = (_cffi_types != NULL); + (void)self; /* unused */ + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + if (_cffi_setup_custom(library) < 0) + return NULL; + return PyBool_FromLong(was_alive); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static int _cffi_convert_array_argument(CTypeDescrObject *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static void _cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +static int _cffi_init(void) +{ + PyObject *module, *c_api_object = NULL; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + goto failure; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + goto failure; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); + Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ +''' diff --git a/.venv/lib/python3.11/site-packages/cffi/verifier.py b/.venv/lib/python3.11/site-packages/cffi/verifier.py new file mode 100644 index 0000000000000000000000000000000000000000..e392a2b7fdab66662f5a32885cbe865d6c538ebe --- /dev/null +++ b/.venv/lib/python3.11/site-packages/cffi/verifier.py @@ -0,0 +1,306 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os, binascii, shutil, io +from . import __version_verifier_modules__ +from . import ffiplatform +from .error import VerificationError + +if sys.version_info >= (3, 3): + import importlib.machinery + def _extension_suffixes(): + return importlib.machinery.EXTENSION_SUFFIXES[:] +else: + import imp + def _extension_suffixes(): + return [suffix for suffix, _, type in imp.get_suffixes() + if type == imp.C_EXTENSION] + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super(NativeIO, self).write(s) + + +class Verifier(object): + + def __init__(self, ffi, preamble, tmpdir=None, modulename=None, + ext_package=None, tag='', force_generic_engine=False, + source_extension='.c', flags=None, relative_to=None, **kwds): + if ffi._parser._uses_new_feature: + raise VerificationError( + "feature not supported with ffi.verify(), but only " + "with ffi.set_source(): %s" % (ffi._parser._uses_new_feature,)) + self.ffi = ffi + self.preamble = preamble + if not modulename: + flattened_kwds = ffiplatform.flatten(kwds) + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) + self._vengine.patch_extension_kwds(kwds) + self.flags = flags + self.kwds = self.make_relative_to(kwds, relative_to) + # + if modulename: + if tag: + raise TypeError("can't specify both 'modulename' and 'tag'") + else: + key = '\x00'.join(['%d.%d' % sys.version_info[:2], + __version_verifier_modules__, + preamble, flattened_kwds] + + ffi._cdefsources) + if sys.version_info >= (3,): + key = key.encode('utf-8') + k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) + k1 = k1.lstrip('0x').rstrip('L') + k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) + k2 = k2.lstrip('0').rstrip('L') + modulename = '_cffi_%s_%s%s%s' % (tag, self._vengine._class_key, + k1, k2) + suffix = _get_so_suffixes()[0] + self.tmpdir = tmpdir or _caller_dir_pycache() + self.sourcefilename = os.path.join(self.tmpdir, modulename + source_extension) + self.modulefilename = os.path.join(self.tmpdir, modulename + suffix) + self.ext_package = ext_package + self._has_source = False + self._has_module = False + + def write_source(self, file=None): + """Write the C source code. It is produced in 'self.sourcefilename', + which can be tweaked beforehand.""" + with self.ffi._lock: + if self._has_source and file is None: + raise VerificationError( + "source code already written") + self._write_source(file) + + def compile_module(self): + """Write the C source code (if not done already) and compile it. + This produces a dynamic link library in 'self.modulefilename'.""" + with self.ffi._lock: + if self._has_module: + raise VerificationError("module already compiled") + if not self._has_source: + self._write_source() + self._compile_module() + + def load_library(self): + """Get a C module from this Verifier instance. + Returns an instance of a FFILibrary class that behaves like the + objects returned by ffi.dlopen(), but that delegates all + operations to the C module. If necessary, the C code is written + and compiled first. + """ + with self.ffi._lock: + if not self._has_module: + self._locate_module() + if not self._has_module: + if not self._has_source: + self._write_source() + self._compile_module() + return self._load_library() + + def get_module_name(self): + basename = os.path.basename(self.modulefilename) + # kill both the .so extension and the other .'s, as introduced + # by Python 3: 'basename.cpython-33m.so' + basename = basename.split('.', 1)[0] + # and the _d added in Python 2 debug builds --- but try to be + # conservative and not kill a legitimate _d + if basename.endswith('_d') and hasattr(sys, 'gettotalrefcount'): + basename = basename[:-2] + return basename + + def get_extension(self): + if not self._has_source: + with self.ffi._lock: + if not self._has_source: + self._write_source() + sourcename = ffiplatform.maybe_relative_path(self.sourcefilename) + modname = self.get_module_name() + return ffiplatform.get_extension(sourcename, modname, **self.kwds) + + def generates_python_module(self): + return self._vengine._gen_python_module + + def make_relative_to(self, kwds, relative_to): + if relative_to and os.path.dirname(relative_to): + dirname = os.path.dirname(relative_to) + kwds = kwds.copy() + for key in ffiplatform.LIST_OF_FILE_NAMES: + if key in kwds: + lst = kwds[key] + if not isinstance(lst, (list, tuple)): + raise TypeError("keyword '%s' should be a list or tuple" + % (key,)) + lst = [os.path.join(dirname, fn) for fn in lst] + kwds[key] = lst + return kwds + + # ---------- + + def _locate_module(self): + if not os.path.isfile(self.modulefilename): + if self.ext_package: + try: + pkg = __import__(self.ext_package, None, None, ['__doc__']) + except ImportError: + return # cannot import the package itself, give up + # (e.g. it might be called differently before installation) + path = pkg.__path__ + else: + path = None + filename = self._vengine.find_module(self.get_module_name(), path, + _get_so_suffixes()) + if filename is None: + return + self.modulefilename = filename + self._vengine.collect_types() + self._has_module = True + + def _write_source_to(self, file): + self._vengine._f = file + try: + self._vengine.write_source_to_f() + finally: + del self._vengine._f + + def _write_source(self, file=None): + if file is not None: + self._write_source_to(file) + else: + # Write our source file to an in memory file. + f = NativeIO() + self._write_source_to(f) + source_data = f.getvalue() + + # Determine if this matches the current file + if os.path.exists(self.sourcefilename): + with open(self.sourcefilename, "r") as fp: + needs_written = not (fp.read() == source_data) + else: + needs_written = True + + # Actually write the file out if it doesn't match + if needs_written: + _ensure_dir(self.sourcefilename) + with open(self.sourcefilename, "w") as fp: + fp.write(source_data) + + # Set this flag + self._has_source = True + + def _compile_module(self): + # compile this C source + tmpdir = os.path.dirname(self.sourcefilename) + outputfilename = ffiplatform.compile(tmpdir, self.get_extension()) + try: + same = ffiplatform.samefile(outputfilename, self.modulefilename) + except OSError: + same = False + if not same: + _ensure_dir(self.modulefilename) + shutil.move(outputfilename, self.modulefilename) + self._has_module = True + + def _load_library(self): + assert self._has_module + if self.flags is not None: + return self._vengine.load_library(self.flags) + else: + return self._vengine.load_library() + +# ____________________________________________________________ + +_FORCE_GENERIC_ENGINE = False # for tests + +def _locate_engine_class(ffi, force_generic_engine): + if _FORCE_GENERIC_ENGINE: + force_generic_engine = True + if not force_generic_engine: + if '__pypy__' in sys.builtin_module_names: + force_generic_engine = True + else: + try: + import _cffi_backend + except ImportError: + _cffi_backend = '?' + if ffi._backend is not _cffi_backend: + force_generic_engine = True + if force_generic_engine: + from . import vengine_gen + return vengine_gen.VGenericEngine + else: + from . import vengine_cpy + return vengine_cpy.VCPythonEngine + +# ____________________________________________________________ + +_TMPDIR = None + +def _caller_dir_pycache(): + if _TMPDIR: + return _TMPDIR + result = os.environ.get('CFFI_TMPDIR') + if result: + return result + filename = sys._getframe(2).f_code.co_filename + return os.path.abspath(os.path.join(os.path.dirname(filename), + '__pycache__')) + +def set_tmpdir(dirname): + """Set the temporary directory to use instead of __pycache__.""" + global _TMPDIR + _TMPDIR = dirname + +def cleanup_tmpdir(tmpdir=None, keep_so=False): + """Clean up the temporary directory by removing all files in it + called `_cffi_*.{c,so}` as well as the `build` subdirectory.""" + tmpdir = tmpdir or _caller_dir_pycache() + try: + filelist = os.listdir(tmpdir) + except OSError: + return + if keep_so: + suffix = '.c' # only remove .c files + else: + suffix = _get_so_suffixes()[0].lower() + for fn in filelist: + if fn.lower().startswith('_cffi_') and ( + fn.lower().endswith(suffix) or fn.lower().endswith('.c')): + try: + os.unlink(os.path.join(tmpdir, fn)) + except OSError: + pass + clean_dir = [os.path.join(tmpdir, 'build')] + for dir in clean_dir: + try: + for fn in os.listdir(dir): + fn = os.path.join(dir, fn) + if os.path.isdir(fn): + clean_dir.append(fn) + else: + os.unlink(fn) + except OSError: + pass + +def _get_so_suffixes(): + suffixes = _extension_suffixes() + if not suffixes: + # bah, no C_EXTENSION available. Occurs on pypy without cpyext + if sys.platform == 'win32': + suffixes = [".pyd"] + else: + suffixes = [".so"] + + return suffixes + +def _ensure_dir(filename): + dirname = os.path.dirname(filename) + if dirname and not os.path.isdir(dirname): + os.makedirs(dirname) diff --git a/.venv/lib/python3.11/site-packages/diskcache/__pycache__/core.cpython-311.pyc b/.venv/lib/python3.11/site-packages/diskcache/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..673bb5c0126ceaef25fe900ba26154ce67a68f71 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/diskcache/__pycache__/core.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__init__.py b/.venv/lib/python3.11/site-packages/prometheus_client/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..84a7ba82b045dbdd6a255faa6719597951452b2c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/__init__.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +from . import ( + exposition, gc_collector, metrics, metrics_core, platform_collector, + process_collector, registry, +) +from .exposition import ( + CONTENT_TYPE_LATEST, delete_from_gateway, generate_latest, + instance_ip_grouping_key, make_asgi_app, make_wsgi_app, MetricsHandler, + push_to_gateway, pushadd_to_gateway, start_http_server, start_wsgi_server, + write_to_textfile, +) +from .gc_collector import GC_COLLECTOR, GCCollector +from .metrics import ( + Counter, disable_created_metrics, enable_created_metrics, Enum, Gauge, + Histogram, Info, Summary, +) +from .metrics_core import Metric +from .platform_collector import PLATFORM_COLLECTOR, PlatformCollector +from .process_collector import PROCESS_COLLECTOR, ProcessCollector +from .registry import CollectorRegistry, REGISTRY + +__all__ = ( + 'CollectorRegistry', + 'REGISTRY', + 'Metric', + 'Counter', + 'Gauge', + 'Summary', + 'Histogram', + 'Info', + 'Enum', + 'enable_created_metrics', + 'disable_created_metrics', + 'CONTENT_TYPE_LATEST', + 'generate_latest', + 'MetricsHandler', + 'make_wsgi_app', + 'make_asgi_app', + 'start_http_server', + 'start_wsgi_server', + 'write_to_textfile', + 'push_to_gateway', + 'pushadd_to_gateway', + 'delete_from_gateway', + 'instance_ip_grouping_key', + 'ProcessCollector', + 'PROCESS_COLLECTOR', + 'PlatformCollector', + 'PLATFORM_COLLECTOR', + 'GCCollector', + 'GC_COLLECTOR', +) + +if __name__ == '__main__': + c = Counter('cc', 'A counter') + c.inc() + + g = Gauge('gg', 'A gauge') + g.set(17) + + s = Summary('ss', 'A summary', ['a', 'b']) + s.labels('c', 'd').observe(17) + + h = Histogram('hh', 'A histogram') + h.observe(.6) + + start_http_server(8000) + import time + + while True: + time.sleep(1) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/asgi.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/asgi.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d24d6d6979cbd9a1e1517642d20540c1986062fa Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/asgi.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/context_managers.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/context_managers.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d23082bd260b8778f606564b1f52e5f6b8eac452 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/context_managers.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/decorator.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/decorator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f03ea7f0ad933b7d9545d3bedad3818818b10a7 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/decorator.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/exposition.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/exposition.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45c7ff28887743e418ac707ad6ca807677840cb7 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/exposition.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/metrics_core.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/metrics_core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8187df71290d464f17b282306918f4fe42a2b2d5 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/metrics_core.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/mmap_dict.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/mmap_dict.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39844a581bd4f875029c7072a728e9cc10da4a0b Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/mmap_dict.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/parser.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/parser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab83f8e561710a3a341ceb8cd14d6673ba539a63 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/parser.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/platform_collector.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/platform_collector.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69267014cf85e89e9726326093e6713d1e976447 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/platform_collector.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/registry.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/registry.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1147c898403296ce6018e80500ed16cb7e700ee8 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/registry.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/values.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/values.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3931a28c237e19d0d1da530f8c9648a8fd001489 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/values.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/asgi.py b/.venv/lib/python3.11/site-packages/prometheus_client/asgi.py new file mode 100644 index 0000000000000000000000000000000000000000..e1864b8b9f55a09af34d08b48bef16a6a651caf3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/asgi.py @@ -0,0 +1,40 @@ +from typing import Callable +from urllib.parse import parse_qs + +from .exposition import _bake_output +from .registry import CollectorRegistry, REGISTRY + + +def make_asgi_app(registry: CollectorRegistry = REGISTRY, disable_compression: bool = False) -> Callable: + """Create a ASGI app which serves the metrics from a registry.""" + + async def prometheus_app(scope, receive, send): + assert scope.get("type") == "http" + # Prepare parameters + params = parse_qs(scope.get('query_string', b'')) + accept_header = ",".join([ + value.decode("utf8") for (name, value) in scope.get('headers') + if name.decode("utf8").lower() == 'accept' + ]) + accept_encoding_header = ",".join([ + value.decode("utf8") for (name, value) in scope.get('headers') + if name.decode("utf8").lower() == 'accept-encoding' + ]) + # Bake output + status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression) + formatted_headers = [] + for header in headers: + formatted_headers.append(tuple(x.encode('utf8') for x in header)) + # Return output + payload = await receive() + if payload.get("type") == "http.request": + await send( + { + "type": "http.response.start", + "status": int(status.split(' ')[0]), + "headers": formatted_headers, + } + ) + await send({"type": "http.response.body", "body": output}) + + return prometheus_app diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/context_managers.py b/.venv/lib/python3.11/site-packages/prometheus_client/context_managers.py new file mode 100644 index 0000000000000000000000000000000000000000..3988ec22218e96a7bee77971d7ee59fdfc6effcb --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/context_managers.py @@ -0,0 +1,82 @@ +from timeit import default_timer +from types import TracebackType +from typing import ( + Any, Callable, Literal, Optional, Tuple, Type, TYPE_CHECKING, TypeVar, + Union, +) + +from .decorator import decorate + +if TYPE_CHECKING: + from . import Counter + F = TypeVar("F", bound=Callable[..., Any]) + + +class ExceptionCounter: + def __init__(self, counter: "Counter", exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]]) -> None: + self._counter = counter + self._exception = exception + + def __enter__(self) -> None: + pass + + def __exit__(self, typ: Optional[Type[BaseException]], value: Optional[BaseException], traceback: Optional[TracebackType]) -> Literal[False]: + if isinstance(value, self._exception): + self._counter.inc() + return False + + def __call__(self, f: "F") -> "F": + def wrapped(func, *args, **kwargs): + with self: + return func(*args, **kwargs) + + return decorate(f, wrapped) + + +class InprogressTracker: + def __init__(self, gauge): + self._gauge = gauge + + def __enter__(self): + self._gauge.inc() + + def __exit__(self, typ, value, traceback): + self._gauge.dec() + + def __call__(self, f: "F") -> "F": + def wrapped(func, *args, **kwargs): + with self: + return func(*args, **kwargs) + + return decorate(f, wrapped) + + +class Timer: + def __init__(self, metric, callback_name): + self._metric = metric + self._callback_name = callback_name + + def _new_timer(self): + return self.__class__(self._metric, self._callback_name) + + def __enter__(self): + self._start = default_timer() + return self + + def __exit__(self, typ, value, traceback): + # Time can go backwards. + duration = max(default_timer() - self._start, 0) + callback = getattr(self._metric, self._callback_name) + callback(duration) + + def labels(self, *args, **kw): + self._metric = self._metric.labels(*args, **kw) + + def __call__(self, f: "F") -> "F": + def wrapped(func, *args, **kwargs): + # Obtaining new instance of timer every time + # ensures thread safety and reentrancy. + with self._new_timer(): + return func(*args, **kwargs) + + return decorate(f, wrapped) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/core.py b/.venv/lib/python3.11/site-packages/prometheus_client/core.py new file mode 100644 index 0000000000000000000000000000000000000000..ad3a45420f1790d2af8250855276fb5e8c56c16a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/core.py @@ -0,0 +1,32 @@ +from .metrics import Counter, Enum, Gauge, Histogram, Info, Summary +from .metrics_core import ( + CounterMetricFamily, GaugeHistogramMetricFamily, GaugeMetricFamily, + HistogramMetricFamily, InfoMetricFamily, Metric, StateSetMetricFamily, + SummaryMetricFamily, UnknownMetricFamily, UntypedMetricFamily, +) +from .registry import CollectorRegistry, REGISTRY +from .samples import Exemplar, Sample, Timestamp + +__all__ = ( + 'CollectorRegistry', + 'Counter', + 'CounterMetricFamily', + 'Enum', + 'Exemplar', + 'Gauge', + 'GaugeHistogramMetricFamily', + 'GaugeMetricFamily', + 'Histogram', + 'HistogramMetricFamily', + 'Info', + 'InfoMetricFamily', + 'Metric', + 'REGISTRY', + 'Sample', + 'StateSetMetricFamily', + 'Summary', + 'SummaryMetricFamily', + 'Timestamp', + 'UnknownMetricFamily', + 'UntypedMetricFamily', +) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/decorator.py b/.venv/lib/python3.11/site-packages/prometheus_client/decorator.py new file mode 100644 index 0000000000000000000000000000000000000000..1ad2c9770ec1272587e8ff8d512d53ea68eac3d8 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/decorator.py @@ -0,0 +1,427 @@ +# ######################### LICENSE ############################ # + +# Copyright (c) 2005-2016, Michele Simionato +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in bytecode form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +""" +Decorator module, see http://pypi.python.org/pypi/decorator +for the documentation. +""" +from __future__ import print_function + +import collections +import inspect +import itertools +import operator +import re +import sys + +__version__ = '4.0.10' + +if sys.version_info >= (3,): + from inspect import getfullargspec + + + def get_init(cls): + return cls.__init__ +else: + class getfullargspec(object): + "A quick and dirty replacement for getfullargspec for Python 2.X" + + def __init__(self, f): + self.args, self.varargs, self.varkw, self.defaults = \ + inspect.getargspec(f) + self.kwonlyargs = [] + self.kwonlydefaults = None + + def __iter__(self): + yield self.args + yield self.varargs + yield self.varkw + yield self.defaults + + getargspec = inspect.getargspec + + + def get_init(cls): + return cls.__init__.__func__ + +# getargspec has been deprecated in Python 3.5 +ArgSpec = collections.namedtuple( + 'ArgSpec', 'args varargs varkw defaults') + + +def getargspec(f): + """A replacement for inspect.getargspec""" + spec = getfullargspec(f) + return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults) + + +DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') + + +# basic functionality +class FunctionMaker(object): + """ + An object with the ability to create functions with a given signature. + It has attributes name, doc, module, signature, defaults, dict and + methods update and make. + """ + + # Atomic get-and-increment provided by the GIL + _compile_count = itertools.count() + + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + self.module = func.__module__ + if inspect.isfunction(func): + argspec = getfullargspec(func) + self.annotations = getattr(func, '__annotations__', {}) + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', + 'kwonlydefaults'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + if sys.version_info < (3,): # easy way + self.shortsignature = self.signature = ( + inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1]) + else: # Python 3 way + allargs = list(self.args) + allshortargs = list(self.args) + if self.varargs: + allargs.append('*' + self.varargs) + allshortargs.append('*' + self.varargs) + elif self.kwonlyargs: + allargs.append('*') # single star syntax + for a in self.kwonlyargs: + allargs.append('%s=None' % a) + allshortargs.append('%s=%s' % (a, a)) + if self.varkw: + allargs.append('**' + self.varkw) + allshortargs.append('**' + self.varkw) + self.signature = ', '.join(allargs) + self.shortsignature = ', '.join(allshortargs) + self.dict = func.__dict__.copy() + # func=None happens when decorating a caller + if name: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.__defaults__ = getattr(self, 'defaults', ()) + func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) + func.__annotations__ = getattr(self, 'annotations', None) + try: + frame = sys._getframe(3) + except AttributeError: # for IronPython and similar implementations + callermodule = '?' + else: + callermodule = frame.f_globals.get('__name__', '?') + func.__module__ = getattr(self, 'module', callermodule) + func.__dict__.update(kw) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.match(src) + if mo is None: + raise SyntaxError('not a valid function template\n%s' % src) + name = mo.group(1) # extract the function name + names = set([name] + [arg.strip(' *') for arg in + self.shortsignature.split(',')]) + for n in names: + if n in ('_func_', '_call_'): + raise NameError('%s is overridden in\n%s' % (n, src)) + + if not src.endswith('\n'): # add a newline for old Pythons + src += '\n' + + # Ensure each generated function has a unique filename for profilers + # (such as cProfile) that depend on the tuple of (, + # , ) being unique. + filename = '' % (next(self._compile_count),) + try: + code = compile(src, filename, 'single') + exec(code, evaldict) + except: + print('Error in generated code:', file=sys.stderr) + print(src, file=sys.stderr) + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + doc=None, module=None, addsource=True, **attrs): + """ + Create a function from the strings name, signature and body. + evaldict is the evaluation dictionary. If addsource is true an + attribute __source__ is added to the result. The attributes attrs + are added, if any. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] # strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + return self.make('def %(name)s(%(signature)s):\n' + ibody, + evaldict, addsource, **attrs) + + +def decorate(func, caller): + """ + decorate(func, caller) decorates a function using a caller. + """ + evaldict = dict(_call_=caller, _func_=func) + fun = FunctionMaker.create( + func, "return _call_(_func_, %(shortsignature)s)", + evaldict, __wrapped__=func) + if hasattr(func, '__qualname__'): + fun.__qualname__ = func.__qualname__ + return fun + + +def decorator(caller, _func=None): + """decorator(caller) converts a caller function into a decorator""" + if _func is not None: # return a decorated function + # this is obsolete behavior; you should use decorate instead + return decorate(_func, caller) + # else return a decorator function + if inspect.isclass(caller): + name = caller.__name__.lower() + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + elif inspect.isfunction(caller): + if caller.__name__ == '': + name = '_lambda_' + else: + name = caller.__name__ + doc = caller.__doc__ + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + doc = caller.__call__.__doc__ + evaldict = dict(_call_=caller, _decorate_=decorate) + return FunctionMaker.create( + '%s(func)' % name, 'return _decorate_(func, _call_)', + evaldict, doc=doc, module=caller.__module__, + __wrapped__=caller) + + +# ####################### contextmanager ####################### # + +try: # Python >= 3.2 + from contextlib import _GeneratorContextManager +except ImportError: # Python >= 2.5 + from contextlib import GeneratorContextManager as _GeneratorContextManager + + +class ContextManager(_GeneratorContextManager): + def __call__(self, func): + """Context manager decorator""" + return FunctionMaker.create( + func, "with _self_: return _func_(%(shortsignature)s)", + dict(_self_=self, _func_=func), __wrapped__=func) + + +init = getfullargspec(_GeneratorContextManager.__init__) +n_args = len(init.args) +if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g(*a, **k)) + + + ContextManager.__init__ = __init__ +elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 + pass +elif n_args == 4: # (self, gen, args, kwds) Python 3.5 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g, a, k) + + + ContextManager.__init__ = __init__ + +contextmanager = decorator(ContextManager) + + +# ############################ dispatch_on ############################ # + +def append(a, vancestors): + """ + Append ``a`` to the list of the virtual ancestors, unless it is already + included. + """ + add = True + for j, va in enumerate(vancestors): + if issubclass(va, a): + add = False + break + if issubclass(a, va): + vancestors[j] = a + add = False + if add: + vancestors.append(a) + + +# inspired from simplegeneric by P.J. Eby and functools.singledispatch +def dispatch_on(*dispatch_args): + """ + Factory of decorators turning a function into a generic function + dispatching on the given arguments. + """ + assert dispatch_args, 'No dispatch args passed' + dispatch_str = '(%s,)' % ', '.join(dispatch_args) + + def check(arguments, wrong=operator.ne, msg=''): + """Make sure one passes the expected number of arguments""" + if wrong(len(arguments), len(dispatch_args)): + raise TypeError('Expected %d arguments, got %d%s' % + (len(dispatch_args), len(arguments), msg)) + + def gen_func_dec(func): + """Decorator turning a function into a generic function""" + + # first check the dispatch arguments + argset = set(getfullargspec(func).args) + if not set(dispatch_args) <= argset: + raise NameError('Unknown dispatch arguments %s' % dispatch_str) + + typemap = {} + + def vancestors(*types): + """ + Get a list of sets of virtual ancestors for the given types + """ + check(types) + ras = [[] for _ in range(len(dispatch_args))] + for types_ in typemap: + for t, type_, ra in zip(types, types_, ras): + if issubclass(t, type_) and type_ not in t.__mro__: + append(type_, ra) + return [set(ra) for ra in ras] + + def ancestors(*types): + """ + Get a list of virtual MROs, one for each type + """ + check(types) + lists = [] + for t, vas in zip(types, vancestors(*types)): + n_vas = len(vas) + if n_vas > 1: + raise RuntimeError( + 'Ambiguous dispatch for %s: %s' % (t, vas)) + elif n_vas == 1: + va, = vas + mro = type('t', (t, va), {}).__mro__[1:] + else: + mro = t.__mro__ + lists.append(mro[:-1]) # discard t and object + return lists + + def register(*types): + """ + Decorator to register an implementation for the given types + """ + check(types) + + def dec(f): + check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) + typemap[types] = f + return f + + return dec + + def dispatch_info(*types): + """ + An utility to introspect the dispatch algorithm + """ + check(types) + lst = [] + for anc in itertools.product(*ancestors(*types)): + lst.append(tuple(a.__name__ for a in anc)) + return lst + + def _dispatch(dispatch_args, *args, **kw): + types = tuple(type(arg) for arg in dispatch_args) + try: # fast path + f = typemap[types] + except KeyError: + pass + else: + return f(*args, **kw) + combinations = itertools.product(*ancestors(*types)) + next(combinations) # the first one has been already tried + for types_ in combinations: + f = typemap.get(types_) + if f is not None: + return f(*args, **kw) + + # else call the default implementation + return func(*args, **kw) + + return FunctionMaker.create( + func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, + dict(_f_=_dispatch), register=register, default=func, + typemap=typemap, vancestors=vancestors, ancestors=ancestors, + dispatch_info=dispatch_info, __wrapped__=func) + + gen_func_dec.__name__ = 'dispatch_on' + dispatch_str + return gen_func_dec diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/exposition.py b/.venv/lib/python3.11/site-packages/prometheus_client/exposition.py new file mode 100644 index 0000000000000000000000000000000000000000..fab139df653c8f8f428d4b156229b3750a16b9bc --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/exposition.py @@ -0,0 +1,666 @@ +import base64 +from contextlib import closing +import gzip +from http.server import BaseHTTPRequestHandler +import os +import socket +from socketserver import ThreadingMixIn +import ssl +import sys +import threading +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +from urllib.error import HTTPError +from urllib.parse import parse_qs, quote_plus, urlparse +from urllib.request import ( + BaseHandler, build_opener, HTTPHandler, HTTPRedirectHandler, HTTPSHandler, + Request, +) +from wsgiref.simple_server import make_server, WSGIRequestHandler, WSGIServer + +from .openmetrics import exposition as openmetrics +from .registry import CollectorRegistry, REGISTRY +from .utils import floatToGoString + +__all__ = ( + 'CONTENT_TYPE_LATEST', + 'delete_from_gateway', + 'generate_latest', + 'instance_ip_grouping_key', + 'make_asgi_app', + 'make_wsgi_app', + 'MetricsHandler', + 'push_to_gateway', + 'pushadd_to_gateway', + 'start_http_server', + 'start_wsgi_server', + 'write_to_textfile', +) + +CONTENT_TYPE_LATEST = 'text/plain; version=0.0.4; charset=utf-8' +"""Content type of the latest text format""" + + +class _PrometheusRedirectHandler(HTTPRedirectHandler): + """ + Allow additional methods (e.g. PUT) and data forwarding in redirects. + + Use of this class constitute a user's explicit agreement to the + redirect responses the Prometheus client will receive when using it. + You should only use this class if you control or otherwise trust the + redirect behavior involved and are certain it is safe to full transfer + the original request (method and data) to the redirected URL. For + example, if you know there is a cosmetic URL redirect in front of a + local deployment of a Prometheus server, and all redirects are safe, + this is the class to use to handle redirects in that case. + + The standard HTTPRedirectHandler does not forward request data nor + does it allow redirected PUT requests (which Prometheus uses for some + operations, for example `push_to_gateway`) because these cannot + generically guarantee no violations of HTTP RFC 2616 requirements for + the user to explicitly confirm redirects that could have unexpected + side effects (such as rendering a PUT request non-idempotent or + creating multiple resources not named in the original request). + """ + + def redirect_request(self, req, fp, code, msg, headers, newurl): + """ + Apply redirect logic to a request. + + See parent HTTPRedirectHandler.redirect_request for parameter info. + + If the redirect is disallowed, this raises the corresponding HTTP error. + If the redirect can't be determined, return None to allow other handlers + to try. If the redirect is allowed, return the new request. + + This method specialized for the case when (a) the user knows that the + redirect will not cause unacceptable side effects for any request method, + and (b) the user knows that any request data should be passed through to + the redirect. If either condition is not met, this should not be used. + """ + # note that requests being provided by a handler will use get_method to + # indicate the method, by monkeypatching this, instead of setting the + # Request object's method attribute. + m = getattr(req, "method", req.get_method()) + if not (code in (301, 302, 303, 307) and m in ("GET", "HEAD") + or code in (301, 302, 303) and m in ("POST", "PUT")): + raise HTTPError(req.full_url, code, msg, headers, fp) + new_request = Request( + newurl.replace(' ', '%20'), # space escaping in new url if needed. + headers=req.headers, + origin_req_host=req.origin_req_host, + unverifiable=True, + data=req.data, + ) + new_request.method = m + return new_request + + +def _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression): + """Bake output for metrics output.""" + # Choose the correct plain text format of the output. + encoder, content_type = choose_encoder(accept_header) + if 'name[]' in params: + registry = registry.restricted_registry(params['name[]']) + output = encoder(registry) + headers = [('Content-Type', content_type)] + # If gzip encoding required, gzip the output. + if not disable_compression and gzip_accepted(accept_encoding_header): + output = gzip.compress(output) + headers.append(('Content-Encoding', 'gzip')) + return '200 OK', headers, output + + +def make_wsgi_app(registry: CollectorRegistry = REGISTRY, disable_compression: bool = False) -> Callable: + """Create a WSGI app which serves the metrics from a registry.""" + + def prometheus_app(environ, start_response): + # Prepare parameters + accept_header = environ.get('HTTP_ACCEPT') + accept_encoding_header = environ.get('HTTP_ACCEPT_ENCODING') + params = parse_qs(environ.get('QUERY_STRING', '')) + method = environ['REQUEST_METHOD'] + + if method == 'OPTIONS': + status = '200 OK' + headers = [('Allow', 'OPTIONS,GET')] + output = b'' + elif method != 'GET': + status = '405 Method Not Allowed' + headers = [('Allow', 'OPTIONS,GET')] + output = '# HTTP {}: {}; use OPTIONS or GET\n'.format(status, method).encode() + elif environ['PATH_INFO'] == '/favicon.ico': + # Serve empty response for browsers + status = '200 OK' + headers = [('', '')] + output = b'' + else: + # Note: For backwards compatibility, the URI path for GET is not + # constrained to the documented /metrics, but any path is allowed. + # Bake output + status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, disable_compression) + # Return output + start_response(status, headers) + return [output] + + return prometheus_app + + +class _SilentHandler(WSGIRequestHandler): + """WSGI handler that does not log requests.""" + + def log_message(self, format, *args): + """Log nothing.""" + + +class ThreadingWSGIServer(ThreadingMixIn, WSGIServer): + """Thread per request HTTP server.""" + # Make worker threads "fire and forget". Beginning with Python 3.7 this + # prevents a memory leak because ``ThreadingMixIn`` starts to gather all + # non-daemon threads in a list in order to join on them at server close. + daemon_threads = True + + +def _get_best_family(address, port): + """Automatically select address family depending on address""" + # HTTPServer defaults to AF_INET, which will not start properly if + # binding an ipv6 address is requested. + # This function is based on what upstream python did for http.server + # in https://github.com/python/cpython/pull/11767 + infos = socket.getaddrinfo(address, port, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE) + family, _, _, _, sockaddr = next(iter(infos)) + return family, sockaddr[0] + + +def _get_ssl_ctx( + certfile: str, + keyfile: str, + protocol: int, + cafile: Optional[str] = None, + capath: Optional[str] = None, + client_auth_required: bool = False, +) -> ssl.SSLContext: + """Load context supports SSL.""" + ssl_cxt = ssl.SSLContext(protocol=protocol) + + if cafile is not None or capath is not None: + try: + ssl_cxt.load_verify_locations(cafile, capath) + except IOError as exc: + exc_type = type(exc) + msg = str(exc) + raise exc_type(f"Cannot load CA certificate chain from file " + f"{cafile!r} or directory {capath!r}: {msg}") + else: + try: + ssl_cxt.load_default_certs(purpose=ssl.Purpose.CLIENT_AUTH) + except IOError as exc: + exc_type = type(exc) + msg = str(exc) + raise exc_type(f"Cannot load default CA certificate chain: {msg}") + + if client_auth_required: + ssl_cxt.verify_mode = ssl.CERT_REQUIRED + + try: + ssl_cxt.load_cert_chain(certfile=certfile, keyfile=keyfile) + except IOError as exc: + exc_type = type(exc) + msg = str(exc) + raise exc_type(f"Cannot load server certificate file {certfile!r} or " + f"its private key file {keyfile!r}: {msg}") + + return ssl_cxt + + +def start_wsgi_server( + port: int, + addr: str = '0.0.0.0', + registry: CollectorRegistry = REGISTRY, + certfile: Optional[str] = None, + keyfile: Optional[str] = None, + client_cafile: Optional[str] = None, + client_capath: Optional[str] = None, + protocol: int = ssl.PROTOCOL_TLS_SERVER, + client_auth_required: bool = False, +) -> Tuple[WSGIServer, threading.Thread]: + """Starts a WSGI server for prometheus metrics as a daemon thread.""" + + class TmpServer(ThreadingWSGIServer): + """Copy of ThreadingWSGIServer to update address_family locally""" + + TmpServer.address_family, addr = _get_best_family(addr, port) + app = make_wsgi_app(registry) + httpd = make_server(addr, port, app, TmpServer, handler_class=_SilentHandler) + if certfile and keyfile: + context = _get_ssl_ctx(certfile, keyfile, protocol, client_cafile, client_capath, client_auth_required) + httpd.socket = context.wrap_socket(httpd.socket, server_side=True) + t = threading.Thread(target=httpd.serve_forever) + t.daemon = True + t.start() + + return httpd, t + + +start_http_server = start_wsgi_server + + +def generate_latest(registry: CollectorRegistry = REGISTRY) -> bytes: + """Returns the metrics from the registry in latest text format as a string.""" + + def sample_line(line): + if line.labels: + labelstr = '{{{0}}}'.format(','.join( + ['{}="{}"'.format( + k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) + for k, v in sorted(line.labels.items())])) + else: + labelstr = '' + timestamp = '' + if line.timestamp is not None: + # Convert to milliseconds. + timestamp = f' {int(float(line.timestamp) * 1000):d}' + return f'{line.name}{labelstr} {floatToGoString(line.value)}{timestamp}\n' + + output = [] + for metric in registry.collect(): + try: + mname = metric.name + mtype = metric.type + # Munging from OpenMetrics into Prometheus format. + if mtype == 'counter': + mname = mname + '_total' + elif mtype == 'info': + mname = mname + '_info' + mtype = 'gauge' + elif mtype == 'stateset': + mtype = 'gauge' + elif mtype == 'gaugehistogram': + # A gauge histogram is really a gauge, + # but this captures the structure better. + mtype = 'histogram' + elif mtype == 'unknown': + mtype = 'untyped' + + output.append('# HELP {} {}\n'.format( + mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) + output.append(f'# TYPE {mname} {mtype}\n') + + om_samples: Dict[str, List[str]] = {} + for s in metric.samples: + for suffix in ['_created', '_gsum', '_gcount']: + if s.name == metric.name + suffix: + # OpenMetrics specific sample, put in a gauge at the end. + om_samples.setdefault(suffix, []).append(sample_line(s)) + break + else: + output.append(sample_line(s)) + except Exception as exception: + exception.args = (exception.args or ('',)) + (metric,) + raise + + for suffix, lines in sorted(om_samples.items()): + output.append('# HELP {}{} {}\n'.format(metric.name, suffix, + metric.documentation.replace('\\', r'\\').replace('\n', r'\n'))) + output.append(f'# TYPE {metric.name}{suffix} gauge\n') + output.extend(lines) + return ''.join(output).encode('utf-8') + + +def choose_encoder(accept_header: str) -> Tuple[Callable[[CollectorRegistry], bytes], str]: + accept_header = accept_header or '' + for accepted in accept_header.split(','): + if accepted.split(';')[0].strip() == 'application/openmetrics-text': + return (openmetrics.generate_latest, + openmetrics.CONTENT_TYPE_LATEST) + return generate_latest, CONTENT_TYPE_LATEST + + +def gzip_accepted(accept_encoding_header: str) -> bool: + accept_encoding_header = accept_encoding_header or '' + for accepted in accept_encoding_header.split(','): + if accepted.split(';')[0].strip().lower() == 'gzip': + return True + return False + + +class MetricsHandler(BaseHTTPRequestHandler): + """HTTP handler that gives metrics from ``REGISTRY``.""" + registry: CollectorRegistry = REGISTRY + + def do_GET(self) -> None: + # Prepare parameters + registry = self.registry + accept_header = self.headers.get('Accept') + accept_encoding_header = self.headers.get('Accept-Encoding') + params = parse_qs(urlparse(self.path).query) + # Bake output + status, headers, output = _bake_output(registry, accept_header, accept_encoding_header, params, False) + # Return output + self.send_response(int(status.split(' ')[0])) + for header in headers: + self.send_header(*header) + self.end_headers() + self.wfile.write(output) + + def log_message(self, format: str, *args: Any) -> None: + """Log nothing.""" + + @classmethod + def factory(cls, registry: CollectorRegistry) -> type: + """Returns a dynamic MetricsHandler class tied + to the passed registry. + """ + # This implementation relies on MetricsHandler.registry + # (defined above and defaulted to REGISTRY). + + # As we have unicode_literals, we need to create a str() + # object for type(). + cls_name = str(cls.__name__) + MyMetricsHandler = type(cls_name, (cls, object), + {"registry": registry}) + return MyMetricsHandler + + +def write_to_textfile(path: str, registry: CollectorRegistry) -> None: + """Write metrics to the given path. + + This is intended for use with the Node exporter textfile collector. + The path must end in .prom for the textfile collector to process it.""" + tmppath = f'{path}.{os.getpid()}.{threading.current_thread().ident}' + with open(tmppath, 'wb') as f: + f.write(generate_latest(registry)) + + # rename(2) is atomic but fails on Windows if the destination file exists + if os.name == 'nt': + os.replace(tmppath, path) + else: + os.rename(tmppath, path) + + +def _make_handler( + url: str, + method: str, + timeout: Optional[float], + headers: Sequence[Tuple[str, str]], + data: bytes, + base_handler: Union[BaseHandler, type], +) -> Callable[[], None]: + def handle() -> None: + request = Request(url, data=data) + request.get_method = lambda: method # type: ignore + for k, v in headers: + request.add_header(k, v) + resp = build_opener(base_handler).open(request, timeout=timeout) + if resp.code >= 400: + raise OSError(f"error talking to pushgateway: {resp.code} {resp.msg}") + + return handle + + +def default_handler( + url: str, + method: str, + timeout: Optional[float], + headers: List[Tuple[str, str]], + data: bytes, +) -> Callable[[], None]: + """Default handler that implements HTTP/HTTPS connections. + + Used by the push_to_gateway functions. Can be re-used by other handlers.""" + + return _make_handler(url, method, timeout, headers, data, HTTPHandler) + + +def passthrough_redirect_handler( + url: str, + method: str, + timeout: Optional[float], + headers: List[Tuple[str, str]], + data: bytes, +) -> Callable[[], None]: + """ + Handler that automatically trusts redirect responses for all HTTP methods. + + Augments standard HTTPRedirectHandler capability by permitting PUT requests, + preserving the method upon redirect, and passing through all headers and + data from the original request. Only use this handler if you control or + trust the source of redirect responses you encounter when making requests + via the Prometheus client. This handler will simply repeat the identical + request, including same method and data, to the new redirect URL.""" + + return _make_handler(url, method, timeout, headers, data, _PrometheusRedirectHandler) + + +def basic_auth_handler( + url: str, + method: str, + timeout: Optional[float], + headers: List[Tuple[str, str]], + data: bytes, + username: Optional[str] = None, + password: Optional[str] = None, +) -> Callable[[], None]: + """Handler that implements HTTP/HTTPS connections with Basic Auth. + + Sets auth headers using supplied 'username' and 'password', if set. + Used by the push_to_gateway functions. Can be re-used by other handlers.""" + + def handle(): + """Handler that implements HTTP Basic Auth. + """ + if username is not None and password is not None: + auth_value = f'{username}:{password}'.encode() + auth_token = base64.b64encode(auth_value) + auth_header = b'Basic ' + auth_token + headers.append(('Authorization', auth_header)) + default_handler(url, method, timeout, headers, data)() + + return handle + + +def tls_auth_handler( + url: str, + method: str, + timeout: Optional[float], + headers: List[Tuple[str, str]], + data: bytes, + certfile: str, + keyfile: str, + cafile: Optional[str] = None, + protocol: int = ssl.PROTOCOL_TLS_CLIENT, + insecure_skip_verify: bool = False, +) -> Callable[[], None]: + """Handler that implements an HTTPS connection with TLS Auth. + + The default protocol (ssl.PROTOCOL_TLS_CLIENT) will also enable + ssl.CERT_REQUIRED and SSLContext.check_hostname by default. This can be + disabled by setting insecure_skip_verify to True. + + Both this handler and the TLS feature on pushgateay are experimental.""" + context = ssl.SSLContext(protocol=protocol) + if cafile is not None: + context.load_verify_locations(cafile) + else: + context.load_default_certs() + + if insecure_skip_verify: + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + context.load_cert_chain(certfile=certfile, keyfile=keyfile) + handler = HTTPSHandler(context=context) + return _make_handler(url, method, timeout, headers, data, handler) + + +def push_to_gateway( + gateway: str, + job: str, + registry: CollectorRegistry, + grouping_key: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = 30, + handler: Callable = default_handler, +) -> None: + """Push metrics to the given pushgateway. + + `gateway` the url for your push gateway. Either of the form + 'http://pushgateway.local', or 'pushgateway.local'. + Scheme defaults to 'http' if none is provided + `job` is the job label to be attached to all pushed metrics + `registry` is an instance of CollectorRegistry + `grouping_key` please see the pushgateway documentation for details. + Defaults to None + `timeout` is how long push will attempt to connect before giving up. + Defaults to 30s, can be set to None for no timeout. + `handler` is an optional function which can be provided to perform + requests to the 'gateway'. + Defaults to None, in which case an http or https request + will be carried out by a default handler. + If not None, the argument must be a function which accepts + the following arguments: + url, method, timeout, headers, and content + May be used to implement additional functionality not + supported by the built-in default handler (such as SSL + client certicates, and HTTP authentication mechanisms). + 'url' is the URL for the request, the 'gateway' argument + described earlier will form the basis of this URL. + 'method' is the HTTP method which should be used when + carrying out the request. + 'timeout' requests not successfully completed after this + many seconds should be aborted. If timeout is None, then + the handler should not set a timeout. + 'headers' is a list of ("header-name","header-value") tuples + which must be passed to the pushgateway in the form of HTTP + request headers. + The function should raise an exception (e.g. IOError) on + failure. + 'content' is the data which should be used to form the HTTP + Message Body. + + This overwrites all metrics with the same job and grouping_key. + This uses the PUT HTTP method.""" + _use_gateway('PUT', gateway, job, registry, grouping_key, timeout, handler) + + +def pushadd_to_gateway( + gateway: str, + job: str, + registry: Optional[CollectorRegistry], + grouping_key: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = 30, + handler: Callable = default_handler, +) -> None: + """PushAdd metrics to the given pushgateway. + + `gateway` the url for your push gateway. Either of the form + 'http://pushgateway.local', or 'pushgateway.local'. + Scheme defaults to 'http' if none is provided + `job` is the job label to be attached to all pushed metrics + `registry` is an instance of CollectorRegistry + `grouping_key` please see the pushgateway documentation for details. + Defaults to None + `timeout` is how long push will attempt to connect before giving up. + Defaults to 30s, can be set to None for no timeout. + `handler` is an optional function which can be provided to perform + requests to the 'gateway'. + Defaults to None, in which case an http or https request + will be carried out by a default handler. + See the 'prometheus_client.push_to_gateway' documentation + for implementation requirements. + + This replaces metrics with the same name, job and grouping_key. + This uses the POST HTTP method.""" + _use_gateway('POST', gateway, job, registry, grouping_key, timeout, handler) + + +def delete_from_gateway( + gateway: str, + job: str, + grouping_key: Optional[Dict[str, Any]] = None, + timeout: Optional[float] = 30, + handler: Callable = default_handler, +) -> None: + """Delete metrics from the given pushgateway. + + `gateway` the url for your push gateway. Either of the form + 'http://pushgateway.local', or 'pushgateway.local'. + Scheme defaults to 'http' if none is provided + `job` is the job label to be attached to all pushed metrics + `grouping_key` please see the pushgateway documentation for details. + Defaults to None + `timeout` is how long delete will attempt to connect before giving up. + Defaults to 30s, can be set to None for no timeout. + `handler` is an optional function which can be provided to perform + requests to the 'gateway'. + Defaults to None, in which case an http or https request + will be carried out by a default handler. + See the 'prometheus_client.push_to_gateway' documentation + for implementation requirements. + + This deletes metrics with the given job and grouping_key. + This uses the DELETE HTTP method.""" + _use_gateway('DELETE', gateway, job, None, grouping_key, timeout, handler) + + +def _use_gateway( + method: str, + gateway: str, + job: str, + registry: Optional[CollectorRegistry], + grouping_key: Optional[Dict[str, Any]], + timeout: Optional[float], + handler: Callable, +) -> None: + gateway_url = urlparse(gateway) + # See https://bugs.python.org/issue27657 for details on urlparse in py>=3.7.6. + if not gateway_url.scheme or gateway_url.scheme not in ['http', 'https']: + gateway = f'http://{gateway}' + + gateway = gateway.rstrip('/') + url = '{}/metrics/{}/{}'.format(gateway, *_escape_grouping_key("job", job)) + + data = b'' + if method != 'DELETE': + if registry is None: + registry = REGISTRY + data = generate_latest(registry) + + if grouping_key is None: + grouping_key = {} + url += ''.join( + '/{}/{}'.format(*_escape_grouping_key(str(k), str(v))) + for k, v in sorted(grouping_key.items())) + + handler( + url=url, method=method, timeout=timeout, + headers=[('Content-Type', CONTENT_TYPE_LATEST)], data=data, + )() + + +def _escape_grouping_key(k, v): + if v == "": + # Per https://github.com/prometheus/pushgateway/pull/346. + return k + "@base64", "=" + elif '/' in v: + # Added in Pushgateway 0.9.0. + return k + "@base64", base64.urlsafe_b64encode(v.encode("utf-8")).decode("utf-8") + else: + return k, quote_plus(v) + + +def instance_ip_grouping_key() -> Dict[str, Any]: + """Grouping key with instance set to the IP Address of this host.""" + with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s: + if sys.platform == 'darwin': + # This check is done this way only on MacOS devices + # it is done this way because the localhost method does + # not work. + # This method was adapted from this StackOverflow answer: + # https://stackoverflow.com/a/28950776 + s.connect(('10.255.255.255', 1)) + else: + s.connect(('localhost', 0)) + + return {'instance': s.getsockname()[0]} + + +from .asgi import make_asgi_app # noqa diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/gc_collector.py b/.venv/lib/python3.11/site-packages/prometheus_client/gc_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..06e52dfc3c8f4238d9d9bccbbb0a606530405a1d --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/gc_collector.py @@ -0,0 +1,45 @@ +import gc +import platform +from typing import Iterable + +from .metrics_core import CounterMetricFamily, Metric +from .registry import Collector, CollectorRegistry, REGISTRY + + +class GCCollector(Collector): + """Collector for Garbage collection statistics.""" + + def __init__(self, registry: CollectorRegistry = REGISTRY): + if not hasattr(gc, 'get_stats') or platform.python_implementation() != 'CPython': + return + registry.register(self) + + def collect(self) -> Iterable[Metric]: + collected = CounterMetricFamily( + 'python_gc_objects_collected', + 'Objects collected during gc', + labels=['generation'], + ) + uncollectable = CounterMetricFamily( + 'python_gc_objects_uncollectable', + 'Uncollectable objects found during GC', + labels=['generation'], + ) + + collections = CounterMetricFamily( + 'python_gc_collections', + 'Number of times this generation was collected', + labels=['generation'], + ) + + for gen, stat in enumerate(gc.get_stats()): + generation = str(gen) + collected.add_metric([generation], value=stat['collected']) + uncollectable.add_metric([generation], value=stat['uncollectable']) + collections.add_metric([generation], value=stat['collections']) + + return [collected, uncollectable, collections] + + +GC_COLLECTOR = GCCollector() +"""Default GCCollector in default Registry REGISTRY.""" diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/metrics.py b/.venv/lib/python3.11/site-packages/prometheus_client/metrics.py new file mode 100644 index 0000000000000000000000000000000000000000..af512115a626bfa66fb4084c9bed4b5328f4b559 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/metrics.py @@ -0,0 +1,776 @@ +import os +from threading import Lock +import time +import types +from typing import ( + Any, Callable, Dict, Iterable, List, Literal, Optional, Sequence, Tuple, + Type, TypeVar, Union, +) +import warnings + +from . import values # retain this import style for testability +from .context_managers import ExceptionCounter, InprogressTracker, Timer +from .metrics_core import ( + Metric, METRIC_LABEL_NAME_RE, METRIC_NAME_RE, + RESERVED_METRIC_LABEL_NAME_RE, +) +from .registry import Collector, CollectorRegistry, REGISTRY +from .samples import Exemplar, Sample +from .utils import floatToGoString, INF + +T = TypeVar('T', bound='MetricWrapperBase') +F = TypeVar("F", bound=Callable[..., Any]) + + +def _build_full_name(metric_type, name, namespace, subsystem, unit): + full_name = '' + if namespace: + full_name += namespace + '_' + if subsystem: + full_name += subsystem + '_' + full_name += name + if metric_type == 'counter' and full_name.endswith('_total'): + full_name = full_name[:-6] # Munge to OpenMetrics. + if unit and not full_name.endswith("_" + unit): + full_name += "_" + unit + if unit and metric_type in ('info', 'stateset'): + raise ValueError('Metric name is of a type that cannot have a unit: ' + full_name) + return full_name + + +def _validate_labelname(l): + if not METRIC_LABEL_NAME_RE.match(l): + raise ValueError('Invalid label metric name: ' + l) + if RESERVED_METRIC_LABEL_NAME_RE.match(l): + raise ValueError('Reserved label metric name: ' + l) + + +def _validate_labelnames(cls, labelnames): + labelnames = tuple(labelnames) + for l in labelnames: + _validate_labelname(l) + if l in cls._reserved_labelnames: + raise ValueError('Reserved label metric name: ' + l) + return labelnames + + +def _validate_exemplar(exemplar): + runes = 0 + for k, v in exemplar.items(): + _validate_labelname(k) + runes += len(k) + runes += len(v) + if runes > 128: + raise ValueError('Exemplar labels have %d UTF-8 characters, exceeding the limit of 128') + + +def _get_use_created() -> bool: + return os.environ.get("PROMETHEUS_DISABLE_CREATED_SERIES", 'False').lower() not in ('true', '1', 't') + + +_use_created = _get_use_created() + + +def disable_created_metrics(): + """Disable exporting _created metrics on counters, histograms, and summaries.""" + global _use_created + _use_created = False + + +def enable_created_metrics(): + """Enable exporting _created metrics on counters, histograms, and summaries.""" + global _use_created + _use_created = True + + +class MetricWrapperBase(Collector): + _type: Optional[str] = None + _reserved_labelnames: Sequence[str] = () + + def _is_observable(self): + # Whether this metric is observable, i.e. + # * a metric without label names and values, or + # * the child of a labelled metric. + return not self._labelnames or (self._labelnames and self._labelvalues) + + def _raise_if_not_observable(self): + # Functions that mutate the state of the metric, for example incrementing + # a counter, will fail if the metric is not observable, because only if a + # metric is observable will the value be initialized. + if not self._is_observable(): + raise ValueError('%s metric is missing label values' % str(self._type)) + + def _is_parent(self): + return self._labelnames and not self._labelvalues + + def _get_metric(self): + return Metric(self._name, self._documentation, self._type, self._unit) + + def describe(self) -> Iterable[Metric]: + return [self._get_metric()] + + def collect(self) -> Iterable[Metric]: + metric = self._get_metric() + for suffix, labels, value, timestamp, exemplar in self._samples(): + metric.add_sample(self._name + suffix, labels, value, timestamp, exemplar) + return [metric] + + def __str__(self) -> str: + return f"{self._type}:{self._name}" + + def __repr__(self) -> str: + metric_type = type(self) + return f"{metric_type.__module__}.{metric_type.__name__}({self._name})" + + def __init__(self: T, + name: str, + documentation: str, + labelnames: Iterable[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + ) -> None: + self._name = _build_full_name(self._type, name, namespace, subsystem, unit) + self._labelnames = _validate_labelnames(self, labelnames) + self._labelvalues = tuple(_labelvalues or ()) + self._kwargs: Dict[str, Any] = {} + self._documentation = documentation + self._unit = unit + + if not METRIC_NAME_RE.match(self._name): + raise ValueError('Invalid metric name: ' + self._name) + + if self._is_parent(): + # Prepare the fields needed for child metrics. + self._lock = Lock() + self._metrics: Dict[Sequence[str], T] = {} + + if self._is_observable(): + self._metric_init() + + if not self._labelvalues: + # Register the multi-wrapper parent metric, or if a label-less metric, the whole shebang. + if registry: + registry.register(self) + + def labels(self: T, *labelvalues: Any, **labelkwargs: Any) -> T: + """Return the child for the given labelset. + + All metrics can have labels, allowing grouping of related time series. + Taking a counter as an example: + + from prometheus_client import Counter + + c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) + c.labels('get', '/').inc() + c.labels('post', '/submit').inc() + + Labels can also be provided as keyword arguments: + + from prometheus_client import Counter + + c = Counter('my_requests_total', 'HTTP Failures', ['method', 'endpoint']) + c.labels(method='get', endpoint='/').inc() + c.labels(method='post', endpoint='/submit').inc() + + See the best practices on [naming](http://prometheus.io/docs/practices/naming/) + and [labels](http://prometheus.io/docs/practices/instrumentation/#use-labels). + """ + if not self._labelnames: + raise ValueError('No label names were set when constructing %s' % self) + + if self._labelvalues: + raise ValueError('{} already has labels set ({}); can not chain calls to .labels()'.format( + self, + dict(zip(self._labelnames, self._labelvalues)) + )) + + if labelvalues and labelkwargs: + raise ValueError("Can't pass both *args and **kwargs") + + if labelkwargs: + if sorted(labelkwargs) != sorted(self._labelnames): + raise ValueError('Incorrect label names') + labelvalues = tuple(str(labelkwargs[l]) for l in self._labelnames) + else: + if len(labelvalues) != len(self._labelnames): + raise ValueError('Incorrect label count') + labelvalues = tuple(str(l) for l in labelvalues) + with self._lock: + if labelvalues not in self._metrics: + self._metrics[labelvalues] = self.__class__( + self._name, + documentation=self._documentation, + labelnames=self._labelnames, + unit=self._unit, + _labelvalues=labelvalues, + **self._kwargs + ) + return self._metrics[labelvalues] + + def remove(self, *labelvalues: Any) -> None: + if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ: + warnings.warn( + "Removal of labels has not been implemented in multi-process mode yet.", + UserWarning) + + if not self._labelnames: + raise ValueError('No label names were set when constructing %s' % self) + + """Remove the given labelset from the metric.""" + if len(labelvalues) != len(self._labelnames): + raise ValueError('Incorrect label count (expected %d, got %s)' % (len(self._labelnames), labelvalues)) + labelvalues = tuple(str(l) for l in labelvalues) + with self._lock: + del self._metrics[labelvalues] + + def clear(self) -> None: + """Remove all labelsets from the metric""" + if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ: + warnings.warn( + "Clearing labels has not been implemented in multi-process mode yet", + UserWarning) + with self._lock: + self._metrics = {} + + def _samples(self) -> Iterable[Sample]: + if self._is_parent(): + return self._multi_samples() + else: + return self._child_samples() + + def _multi_samples(self) -> Iterable[Sample]: + with self._lock: + metrics = self._metrics.copy() + for labels, metric in metrics.items(): + series_labels = list(zip(self._labelnames, labels)) + for suffix, sample_labels, value, timestamp, exemplar in metric._samples(): + yield Sample(suffix, dict(series_labels + list(sample_labels.items())), value, timestamp, exemplar) + + def _child_samples(self) -> Iterable[Sample]: # pragma: no cover + raise NotImplementedError('_child_samples() must be implemented by %r' % self) + + def _metric_init(self): # pragma: no cover + """ + Initialize the metric object as a child, i.e. when it has labels (if any) set. + + This is factored as a separate function to allow for deferred initialization. + """ + raise NotImplementedError('_metric_init() must be implemented by %r' % self) + + +class Counter(MetricWrapperBase): + """A Counter tracks counts of events or running totals. + + Example use cases for Counters: + - Number of requests processed + - Number of items that were inserted into a queue + - Total amount of data that a system has processed + + Counters can only go up (and be reset when the process restarts). If your use case can go down, + you should use a Gauge instead. + + An example for a Counter: + + from prometheus_client import Counter + + c = Counter('my_failures_total', 'Description of counter') + c.inc() # Increment by 1 + c.inc(1.6) # Increment by given value + + There are utilities to count exceptions raised: + + @c.count_exceptions() + def f(): + pass + + with c.count_exceptions(): + pass + + # Count only one type of exception + with c.count_exceptions(ValueError): + pass + + You can also reset the counter to zero in case your logical "process" restarts + without restarting the actual python process. + + c.reset() + + """ + _type = 'counter' + + def _metric_init(self) -> None: + self._value = values.ValueClass(self._type, self._name, self._name + '_total', self._labelnames, + self._labelvalues, self._documentation) + self._created = time.time() + + def inc(self, amount: float = 1, exemplar: Optional[Dict[str, str]] = None) -> None: + """Increment counter by the given amount.""" + self._raise_if_not_observable() + if amount < 0: + raise ValueError('Counters can only be incremented by non-negative amounts.') + self._value.inc(amount) + if exemplar: + _validate_exemplar(exemplar) + self._value.set_exemplar(Exemplar(exemplar, amount, time.time())) + + def reset(self) -> None: + """Reset the counter to zero. Use this when a logical process restarts without restarting the actual python process.""" + self._value.set(0) + self._created = time.time() + + def count_exceptions(self, exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = Exception) -> ExceptionCounter: + """Count exceptions in a block of code or function. + + Can be used as a function decorator or context manager. + Increments the counter when an exception of the given + type is raised up out of the code. + """ + self._raise_if_not_observable() + return ExceptionCounter(self, exception) + + def _child_samples(self) -> Iterable[Sample]: + sample = Sample('_total', {}, self._value.get(), None, self._value.get_exemplar()) + if _use_created: + return ( + sample, + Sample('_created', {}, self._created, None, None) + ) + return (sample,) + + +class Gauge(MetricWrapperBase): + """Gauge metric, to report instantaneous values. + + Examples of Gauges include: + - Inprogress requests + - Number of items in a queue + - Free memory + - Total memory + - Temperature + + Gauges can go both up and down. + + from prometheus_client import Gauge + + g = Gauge('my_inprogress_requests', 'Description of gauge') + g.inc() # Increment by 1 + g.dec(10) # Decrement by given value + g.set(4.2) # Set to a given value + + There are utilities for common use cases: + + g.set_to_current_time() # Set to current unixtime + + # Increment when entered, decrement when exited. + @g.track_inprogress() + def f(): + pass + + with g.track_inprogress(): + pass + + A Gauge can also take its value from a callback: + + d = Gauge('data_objects', 'Number of objects') + my_dict = {} + d.set_function(lambda: len(my_dict)) + """ + _type = 'gauge' + _MULTIPROC_MODES = frozenset(('all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent')) + _MOST_RECENT_MODES = frozenset(('mostrecent', 'livemostrecent')) + + def __init__(self, + name: str, + documentation: str, + labelnames: Iterable[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + multiprocess_mode: Literal['all', 'liveall', 'min', 'livemin', 'max', 'livemax', 'sum', 'livesum', 'mostrecent', 'livemostrecent'] = 'all', + ): + self._multiprocess_mode = multiprocess_mode + if multiprocess_mode not in self._MULTIPROC_MODES: + raise ValueError('Invalid multiprocess mode: ' + multiprocess_mode) + super().__init__( + name=name, + documentation=documentation, + labelnames=labelnames, + namespace=namespace, + subsystem=subsystem, + unit=unit, + registry=registry, + _labelvalues=_labelvalues, + ) + self._kwargs['multiprocess_mode'] = self._multiprocess_mode + self._is_most_recent = self._multiprocess_mode in self._MOST_RECENT_MODES + + def _metric_init(self) -> None: + self._value = values.ValueClass( + self._type, self._name, self._name, self._labelnames, self._labelvalues, + self._documentation, multiprocess_mode=self._multiprocess_mode + ) + + def inc(self, amount: float = 1) -> None: + """Increment gauge by the given amount.""" + if self._is_most_recent: + raise RuntimeError("inc must not be used with the mostrecent mode") + self._raise_if_not_observable() + self._value.inc(amount) + + def dec(self, amount: float = 1) -> None: + """Decrement gauge by the given amount.""" + if self._is_most_recent: + raise RuntimeError("dec must not be used with the mostrecent mode") + self._raise_if_not_observable() + self._value.inc(-amount) + + def set(self, value: float) -> None: + """Set gauge to the given value.""" + self._raise_if_not_observable() + if self._is_most_recent: + self._value.set(float(value), timestamp=time.time()) + else: + self._value.set(float(value)) + + def set_to_current_time(self) -> None: + """Set gauge to the current unixtime.""" + self.set(time.time()) + + def track_inprogress(self) -> InprogressTracker: + """Track inprogress blocks of code or functions. + + Can be used as a function decorator or context manager. + Increments the gauge when the code is entered, + and decrements when it is exited. + """ + self._raise_if_not_observable() + return InprogressTracker(self) + + def time(self) -> Timer: + """Time a block of code or function, and set the duration in seconds. + + Can be used as a function decorator or context manager. + """ + return Timer(self, 'set') + + def set_function(self, f: Callable[[], float]) -> None: + """Call the provided function to return the Gauge value. + + The function must return a float, and may be called from + multiple threads. All other methods of the Gauge become NOOPs. + """ + + self._raise_if_not_observable() + + def samples(_: Gauge) -> Iterable[Sample]: + return (Sample('', {}, float(f()), None, None),) + + self._child_samples = types.MethodType(samples, self) # type: ignore + + def _child_samples(self) -> Iterable[Sample]: + return (Sample('', {}, self._value.get(), None, None),) + + +class Summary(MetricWrapperBase): + """A Summary tracks the size and number of events. + + Example use cases for Summaries: + - Response latency + - Request size + + Example for a Summary: + + from prometheus_client import Summary + + s = Summary('request_size_bytes', 'Request size (bytes)') + s.observe(512) # Observe 512 (bytes) + + Example for a Summary using time: + + from prometheus_client import Summary + + REQUEST_TIME = Summary('response_latency_seconds', 'Response latency (seconds)') + + @REQUEST_TIME.time() + def create_response(request): + '''A dummy function''' + time.sleep(1) + + Example for using the same Summary object as a context manager: + + with REQUEST_TIME.time(): + pass # Logic to be timed + """ + _type = 'summary' + _reserved_labelnames = ['quantile'] + + def _metric_init(self) -> None: + self._count = values.ValueClass(self._type, self._name, self._name + '_count', self._labelnames, + self._labelvalues, self._documentation) + self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation) + self._created = time.time() + + def observe(self, amount: float) -> None: + """Observe the given amount. + + The amount is usually positive or zero. Negative values are + accepted but prevent current versions of Prometheus from + properly detecting counter resets in the sum of + observations. See + https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations + for details. + """ + self._raise_if_not_observable() + self._count.inc(1) + self._sum.inc(amount) + + def time(self) -> Timer: + """Time a block of code or function, and observe the duration in seconds. + + Can be used as a function decorator or context manager. + """ + return Timer(self, 'observe') + + def _child_samples(self) -> Iterable[Sample]: + samples = [ + Sample('_count', {}, self._count.get(), None, None), + Sample('_sum', {}, self._sum.get(), None, None), + ] + if _use_created: + samples.append(Sample('_created', {}, self._created, None, None)) + return tuple(samples) + + +class Histogram(MetricWrapperBase): + """A Histogram tracks the size and number of events in buckets. + + You can use Histograms for aggregatable calculation of quantiles. + + Example use cases: + - Response latency + - Request size + + Example for a Histogram: + + from prometheus_client import Histogram + + h = Histogram('request_size_bytes', 'Request size (bytes)') + h.observe(512) # Observe 512 (bytes) + + Example for a Histogram using time: + + from prometheus_client import Histogram + + REQUEST_TIME = Histogram('response_latency_seconds', 'Response latency (seconds)') + + @REQUEST_TIME.time() + def create_response(request): + '''A dummy function''' + time.sleep(1) + + Example of using the same Histogram object as a context manager: + + with REQUEST_TIME.time(): + pass # Logic to be timed + + The default buckets are intended to cover a typical web/rpc request from milliseconds to seconds. + They can be overridden by passing `buckets` keyword argument to `Histogram`. + """ + _type = 'histogram' + _reserved_labelnames = ['le'] + DEFAULT_BUCKETS = (.005, .01, .025, .05, .075, .1, .25, .5, .75, 1.0, 2.5, 5.0, 7.5, 10.0, INF) + + def __init__(self, + name: str, + documentation: str, + labelnames: Iterable[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + buckets: Sequence[Union[float, str]] = DEFAULT_BUCKETS, + ): + self._prepare_buckets(buckets) + super().__init__( + name=name, + documentation=documentation, + labelnames=labelnames, + namespace=namespace, + subsystem=subsystem, + unit=unit, + registry=registry, + _labelvalues=_labelvalues, + ) + self._kwargs['buckets'] = buckets + + def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None: + buckets = [float(b) for b in source_buckets] + if buckets != sorted(buckets): + # This is probably an error on the part of the user, + # so raise rather than sorting for them. + raise ValueError('Buckets not in sorted order') + if buckets and buckets[-1] != INF: + buckets.append(INF) + if len(buckets) < 2: + raise ValueError('Must have at least two buckets') + self._upper_bounds = buckets + + def _metric_init(self) -> None: + self._buckets: List[values.ValueClass] = [] + self._created = time.time() + bucket_labelnames = self._labelnames + ('le',) + self._sum = values.ValueClass(self._type, self._name, self._name + '_sum', self._labelnames, self._labelvalues, self._documentation) + for b in self._upper_bounds: + self._buckets.append(values.ValueClass( + self._type, + self._name, + self._name + '_bucket', + bucket_labelnames, + self._labelvalues + (floatToGoString(b),), + self._documentation) + ) + + def observe(self, amount: float, exemplar: Optional[Dict[str, str]] = None) -> None: + """Observe the given amount. + + The amount is usually positive or zero. Negative values are + accepted but prevent current versions of Prometheus from + properly detecting counter resets in the sum of + observations. See + https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations + for details. + """ + self._raise_if_not_observable() + self._sum.inc(amount) + for i, bound in enumerate(self._upper_bounds): + if amount <= bound: + self._buckets[i].inc(1) + if exemplar: + _validate_exemplar(exemplar) + self._buckets[i].set_exemplar(Exemplar(exemplar, amount, time.time())) + break + + def time(self) -> Timer: + """Time a block of code or function, and observe the duration in seconds. + + Can be used as a function decorator or context manager. + """ + return Timer(self, 'observe') + + def _child_samples(self) -> Iterable[Sample]: + samples = [] + acc = 0.0 + for i, bound in enumerate(self._upper_bounds): + acc += self._buckets[i].get() + samples.append(Sample('_bucket', {'le': floatToGoString(bound)}, acc, None, self._buckets[i].get_exemplar())) + samples.append(Sample('_count', {}, acc, None, None)) + if self._upper_bounds[0] >= 0: + samples.append(Sample('_sum', {}, self._sum.get(), None, None)) + if _use_created: + samples.append(Sample('_created', {}, self._created, None, None)) + return tuple(samples) + + +class Info(MetricWrapperBase): + """Info metric, key-value pairs. + + Examples of Info include: + - Build information + - Version information + - Potential target metadata + + Example usage: + from prometheus_client import Info + + i = Info('my_build', 'Description of info') + i.info({'version': '1.2.3', 'buildhost': 'foo@bar'}) + + Info metrics do not work in multiprocess mode. + """ + _type = 'info' + + def _metric_init(self): + self._labelname_set = set(self._labelnames) + self._lock = Lock() + self._value = {} + + def info(self, val: Dict[str, str]) -> None: + """Set info metric.""" + if self._labelname_set.intersection(val.keys()): + raise ValueError('Overlapping labels for Info metric, metric: {} child: {}'.format( + self._labelnames, val)) + if any(i is None for i in val.values()): + raise ValueError('Label value cannot be None') + with self._lock: + self._value = dict(val) + + def _child_samples(self) -> Iterable[Sample]: + with self._lock: + return (Sample('_info', self._value, 1.0, None, None),) + + +class Enum(MetricWrapperBase): + """Enum metric, which of a set of states is true. + + Example usage: + from prometheus_client import Enum + + e = Enum('task_state', 'Description of enum', + states=['starting', 'running', 'stopped']) + e.state('running') + + The first listed state will be the default. + Enum metrics do not work in multiprocess mode. + """ + _type = 'stateset' + + def __init__(self, + name: str, + documentation: str, + labelnames: Sequence[str] = (), + namespace: str = '', + subsystem: str = '', + unit: str = '', + registry: Optional[CollectorRegistry] = REGISTRY, + _labelvalues: Optional[Sequence[str]] = None, + states: Optional[Sequence[str]] = None, + ): + super().__init__( + name=name, + documentation=documentation, + labelnames=labelnames, + namespace=namespace, + subsystem=subsystem, + unit=unit, + registry=registry, + _labelvalues=_labelvalues, + ) + if name in labelnames: + raise ValueError(f'Overlapping labels for Enum metric: {name}') + if not states: + raise ValueError(f'No states provided for Enum metric: {name}') + self._kwargs['states'] = self._states = states + + def _metric_init(self) -> None: + self._value = 0 + self._lock = Lock() + + def state(self, state: str) -> None: + """Set enum metric state.""" + self._raise_if_not_observable() + with self._lock: + self._value = self._states.index(state) + + def _child_samples(self) -> Iterable[Sample]: + with self._lock: + return [ + Sample('', {self._name: s}, 1 if i == self._value else 0, None, None) + for i, s + in enumerate(self._states) + ] diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/metrics_core.py b/.venv/lib/python3.11/site-packages/prometheus_client/metrics_core.py new file mode 100644 index 0000000000000000000000000000000000000000..7226d920674edefc88af310f996393bdf7134ecf --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/metrics_core.py @@ -0,0 +1,418 @@ +import re +from typing import Dict, List, Optional, Sequence, Tuple, Union + +from .samples import Exemplar, Sample, Timestamp + +METRIC_TYPES = ( + 'counter', 'gauge', 'summary', 'histogram', + 'gaugehistogram', 'unknown', 'info', 'stateset', +) +METRIC_NAME_RE = re.compile(r'^[a-zA-Z_:][a-zA-Z0-9_:]*$') +METRIC_LABEL_NAME_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') +RESERVED_METRIC_LABEL_NAME_RE = re.compile(r'^__.*$') + + +class Metric: + """A single metric family and its samples. + + This is intended only for internal use by the instrumentation client. + + Custom collectors should use GaugeMetricFamily, CounterMetricFamily + and SummaryMetricFamily instead. + """ + + def __init__(self, name: str, documentation: str, typ: str, unit: str = ''): + if unit and not name.endswith("_" + unit): + name += "_" + unit + if not METRIC_NAME_RE.match(name): + raise ValueError('Invalid metric name: ' + name) + self.name: str = name + self.documentation: str = documentation + self.unit: str = unit + if typ == 'untyped': + typ = 'unknown' + if typ not in METRIC_TYPES: + raise ValueError('Invalid metric type: ' + typ) + self.type: str = typ + self.samples: List[Sample] = [] + + def add_sample(self, name: str, labels: Dict[str, str], value: float, timestamp: Optional[Union[Timestamp, float]] = None, exemplar: Optional[Exemplar] = None) -> None: + """Add a sample to the metric. + + Internal-only, do not use.""" + self.samples.append(Sample(name, labels, value, timestamp, exemplar)) + + def __eq__(self, other: object) -> bool: + return (isinstance(other, Metric) + and self.name == other.name + and self.documentation == other.documentation + and self.type == other.type + and self.unit == other.unit + and self.samples == other.samples) + + def __repr__(self) -> str: + return "Metric({}, {}, {}, {}, {})".format( + self.name, + self.documentation, + self.type, + self.unit, + self.samples, + ) + + def _restricted_metric(self, names): + """Build a snapshot of a metric with samples restricted to a given set of names.""" + samples = [s for s in self.samples if s[0] in names] + if samples: + m = Metric(self.name, self.documentation, self.type) + m.samples = samples + return m + return None + + +class UnknownMetricFamily(Metric): + """A single unknown metric and its samples. + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + unit: str = '', + ): + Metric.__init__(self, name, documentation, 'unknown', unit) + if labels is not None and value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if value is not None: + self.add_metric([], value) + + def add_metric(self, labels: Sequence[str], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None: + """Add a metric to the metric family. + Args: + labels: A list of label values + value: The value of the metric. + """ + self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp)) + + +# For backward compatibility. +UntypedMetricFamily = UnknownMetricFamily + + +class CounterMetricFamily(Metric): + """A single counter and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + created: Optional[float] = None, + unit: str = '', + ): + # Glue code for pre-OpenMetrics metrics. + if name.endswith('_total'): + name = name[:-6] + Metric.__init__(self, name, documentation, 'counter', unit) + if labels is not None and value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if value is not None: + self.add_metric([], value, created) + + def add_metric(self, + labels: Sequence[str], + value: float, + created: Optional[float] = None, + timestamp: Optional[Union[Timestamp, float]] = None, + ) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + value: The value of the metric + created: Optional unix timestamp the child was created at. + """ + self.samples.append(Sample(self.name + '_total', dict(zip(self._labelnames, labels)), value, timestamp)) + if created is not None: + self.samples.append(Sample(self.name + '_created', dict(zip(self._labelnames, labels)), created, timestamp)) + + +class GaugeMetricFamily(Metric): + """A single gauge and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + unit: str = '', + ): + Metric.__init__(self, name, documentation, 'gauge', unit) + if labels is not None and value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if value is not None: + self.add_metric([], value) + + def add_metric(self, labels: Sequence[str], value: float, timestamp: Optional[Union[Timestamp, float]] = None) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + value: A float + """ + self.samples.append(Sample(self.name, dict(zip(self._labelnames, labels)), value, timestamp)) + + +class SummaryMetricFamily(Metric): + """A single summary and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + count_value: Optional[int] = None, + sum_value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + unit: str = '', + ): + Metric.__init__(self, name, documentation, 'summary', unit) + if (sum_value is None) != (count_value is None): + raise ValueError('count_value and sum_value must be provided together.') + if labels is not None and count_value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + # The and clause is necessary only for typing, the above ValueError will raise if only one is set. + if count_value is not None and sum_value is not None: + self.add_metric([], count_value, sum_value) + + def add_metric(self, + labels: Sequence[str], + count_value: int, + sum_value: float, + timestamp: + Optional[Union[float, Timestamp]] = None + ) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + count_value: The count value of the metric. + sum_value: The sum value of the metric. + """ + self.samples.append(Sample(self.name + '_count', dict(zip(self._labelnames, labels)), count_value, timestamp)) + self.samples.append(Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp)) + + +class HistogramMetricFamily(Metric): + """A single histogram and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + buckets: Optional[Sequence[Union[Tuple[str, float], Tuple[str, float, Exemplar]]]] = None, + sum_value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + unit: str = '', + ): + Metric.__init__(self, name, documentation, 'histogram', unit) + if sum_value is not None and buckets is None: + raise ValueError('sum value cannot be provided without buckets.') + if labels is not None and buckets is not None: + raise ValueError('Can only specify at most one of buckets and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if buckets is not None: + self.add_metric([], buckets, sum_value) + + def add_metric(self, + labels: Sequence[str], + buckets: Sequence[Union[Tuple[str, float], Tuple[str, float, Exemplar]]], + sum_value: Optional[float], + timestamp: Optional[Union[Timestamp, float]] = None) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + buckets: A list of lists. + Each inner list can be a pair of bucket name and value, + or a triple of bucket name, value, and exemplar. + The buckets must be sorted, and +Inf present. + sum_value: The sum value of the metric. + """ + for b in buckets: + bucket, value = b[:2] + exemplar = None + if len(b) == 3: + exemplar = b[2] # type: ignore + self.samples.append(Sample( + self.name + '_bucket', + dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), + value, + timestamp, + exemplar, + )) + # Don't include sum and thus count if there's negative buckets. + if float(buckets[0][0]) >= 0 and sum_value is not None: + # +Inf is last and provides the count value. + self.samples.append( + Sample(self.name + '_count', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp)) + self.samples.append( + Sample(self.name + '_sum', dict(zip(self._labelnames, labels)), sum_value, timestamp)) + + + +class GaugeHistogramMetricFamily(Metric): + """A single gauge histogram and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + buckets: Optional[Sequence[Tuple[str, float]]] = None, + gsum_value: Optional[float] = None, + labels: Optional[Sequence[str]] = None, + unit: str = '', + ): + Metric.__init__(self, name, documentation, 'gaugehistogram', unit) + if labels is not None and buckets is not None: + raise ValueError('Can only specify at most one of buckets and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if buckets is not None: + self.add_metric([], buckets, gsum_value) + + def add_metric(self, + labels: Sequence[str], + buckets: Sequence[Tuple[str, float]], + gsum_value: Optional[float], + timestamp: Optional[Union[float, Timestamp]] = None, + ) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + buckets: A list of pairs of bucket names and values. + The buckets must be sorted, and +Inf present. + gsum_value: The sum value of the metric. + """ + for bucket, value in buckets: + self.samples.append(Sample( + self.name + '_bucket', + dict(list(zip(self._labelnames, labels)) + [('le', bucket)]), + value, timestamp)) + # +Inf is last and provides the count value. + self.samples.extend([ + Sample(self.name + '_gcount', dict(zip(self._labelnames, labels)), buckets[-1][1], timestamp), + # TODO: Handle None gsum_value correctly. Currently a None will fail exposition but is allowed here. + Sample(self.name + '_gsum', dict(zip(self._labelnames, labels)), gsum_value, timestamp), # type: ignore + ]) + + +class InfoMetricFamily(Metric): + """A single info and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + value: Optional[Dict[str, str]] = None, + labels: Optional[Sequence[str]] = None, + ): + Metric.__init__(self, name, documentation, 'info') + if labels is not None and value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if value is not None: + self.add_metric([], value) + + def add_metric(self, + labels: Sequence[str], + value: Dict[str, str], + timestamp: Optional[Union[Timestamp, float]] = None, + ) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + value: A dict of labels + """ + self.samples.append(Sample( + self.name + '_info', + dict(dict(zip(self._labelnames, labels)), **value), + 1, + timestamp, + )) + + +class StateSetMetricFamily(Metric): + """A single stateset and its samples. + + For use by custom collectors. + """ + + def __init__(self, + name: str, + documentation: str, + value: Optional[Dict[str, bool]] = None, + labels: Optional[Sequence[str]] = None, + ): + Metric.__init__(self, name, documentation, 'stateset') + if labels is not None and value is not None: + raise ValueError('Can only specify at most one of value and labels.') + if labels is None: + labels = [] + self._labelnames = tuple(labels) + if value is not None: + self.add_metric([], value) + + def add_metric(self, + labels: Sequence[str], + value: Dict[str, bool], + timestamp: Optional[Union[Timestamp, float]] = None, + ) -> None: + """Add a metric to the metric family. + + Args: + labels: A list of label values + value: A dict of string state names to booleans + """ + labels = tuple(labels) + for state, enabled in sorted(value.items()): + v = (1 if enabled else 0) + self.samples.append(Sample( + self.name, + dict(zip(self._labelnames + (self.name,), labels + (state,))), + v, + timestamp, + )) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/mmap_dict.py b/.venv/lib/python3.11/site-packages/prometheus_client/mmap_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..edd895cda9d492b3874597d0613d9eee1f5f48f1 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/mmap_dict.py @@ -0,0 +1,145 @@ +import json +import mmap +import os +import struct +from typing import List + +_INITIAL_MMAP_SIZE = 1 << 16 +_pack_integer_func = struct.Struct(b'i').pack +_pack_two_doubles_func = struct.Struct(b'dd').pack +_unpack_integer = struct.Struct(b'i').unpack_from +_unpack_two_doubles = struct.Struct(b'dd').unpack_from + + +# struct.pack_into has atomicity issues because it will temporarily write 0 into +# the mmap, resulting in false reads to 0 when experiencing a lot of writes. +# Using direct assignment solves this issue. + + +def _pack_two_doubles(data, pos, value, timestamp): + data[pos:pos + 16] = _pack_two_doubles_func(value, timestamp) + + +def _pack_integer(data, pos, value): + data[pos:pos + 4] = _pack_integer_func(value) + + +def _read_all_values(data, used=0): + """Yield (key, value, timestamp, pos). No locking is performed.""" + + if used <= 0: + # If not valid `used` value is passed in, read it from the file. + used = _unpack_integer(data, 0)[0] + + pos = 8 + + while pos < used: + encoded_len = _unpack_integer(data, pos)[0] + # check we are not reading beyond bounds + if encoded_len + pos > used: + raise RuntimeError('Read beyond file size detected, file is corrupted.') + pos += 4 + encoded_key = data[pos:pos + encoded_len] + padded_len = encoded_len + (8 - (encoded_len + 4) % 8) + pos += padded_len + value, timestamp = _unpack_two_doubles(data, pos) + yield encoded_key.decode('utf-8'), value, timestamp, pos + pos += 16 + + +class MmapedDict: + """A dict of doubles, backed by an mmapped file. + + The file starts with a 4 byte int, indicating how much of it is used. + Then 4 bytes of padding. + There's then a number of entries, consisting of a 4 byte int which is the + size of the next field, a utf-8 encoded string key, padding to a 8 byte + alignment, and then a 8 byte float which is the value and a 8 byte float + which is a UNIX timestamp in seconds. + + Not thread safe. + """ + + def __init__(self, filename, read_mode=False): + self._f = open(filename, 'rb' if read_mode else 'a+b') + self._fname = filename + capacity = os.fstat(self._f.fileno()).st_size + if capacity == 0: + self._f.truncate(_INITIAL_MMAP_SIZE) + capacity = _INITIAL_MMAP_SIZE + self._capacity = capacity + self._m = mmap.mmap(self._f.fileno(), self._capacity, + access=mmap.ACCESS_READ if read_mode else mmap.ACCESS_WRITE) + + self._positions = {} + self._used = _unpack_integer(self._m, 0)[0] + if self._used == 0: + self._used = 8 + _pack_integer(self._m, 0, self._used) + else: + if not read_mode: + for key, _, _, pos in self._read_all_values(): + self._positions[key] = pos + + @staticmethod + def read_all_values_from_file(filename): + with open(filename, 'rb') as infp: + # Read the first block of data, including the first 4 bytes which tell us + # how much of the file (which is preallocated to _INITIAL_MMAP_SIZE bytes) is occupied. + data = infp.read(mmap.PAGESIZE) + used = _unpack_integer(data, 0)[0] + if used > len(data): # Then read in the rest, if needed. + data += infp.read(used - len(data)) + return _read_all_values(data, used) + + def _init_value(self, key): + """Initialize a value. Lock must be held by caller.""" + encoded = key.encode('utf-8') + # Pad to be 8-byte aligned. + padded = encoded + (b' ' * (8 - (len(encoded) + 4) % 8)) + value = struct.pack(f'i{len(padded)}sdd'.encode(), len(encoded), padded, 0.0, 0.0) + while self._used + len(value) > self._capacity: + self._capacity *= 2 + self._f.truncate(self._capacity) + self._m = mmap.mmap(self._f.fileno(), self._capacity) + self._m[self._used:self._used + len(value)] = value + + # Update how much space we've used. + self._used += len(value) + _pack_integer(self._m, 0, self._used) + self._positions[key] = self._used - 16 + + def _read_all_values(self): + """Yield (key, value, pos). No locking is performed.""" + return _read_all_values(data=self._m, used=self._used) + + def read_all_values(self): + """Yield (key, value, timestamp). No locking is performed.""" + for k, v, ts, _ in self._read_all_values(): + yield k, v, ts + + def read_value(self, key): + if key not in self._positions: + self._init_value(key) + pos = self._positions[key] + return _unpack_two_doubles(self._m, pos) + + def write_value(self, key, value, timestamp): + if key not in self._positions: + self._init_value(key) + pos = self._positions[key] + _pack_two_doubles(self._m, pos, value, timestamp) + + def close(self): + if self._f: + self._m.close() + self._m = None + self._f.close() + self._f = None + + +def mmap_key(metric_name: str, name: str, labelnames: List[str], labelvalues: List[str], help_text: str) -> str: + """Format a key for use in the mmap file.""" + # ensure labels are in consistent order for identity + labels = dict(zip(labelnames, labelvalues)) + return json.dumps([metric_name, name, labels, help_text], sort_keys=True) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/multiprocess.py b/.venv/lib/python3.11/site-packages/prometheus_client/multiprocess.py new file mode 100644 index 0000000000000000000000000000000000000000..7021b49ae0c9b0ed52387d2751d0fcc76818a575 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/multiprocess.py @@ -0,0 +1,170 @@ +from collections import defaultdict +import glob +import json +import os +import warnings + +from .metrics import Gauge +from .metrics_core import Metric +from .mmap_dict import MmapedDict +from .samples import Sample +from .utils import floatToGoString + +try: # Python3 + FileNotFoundError +except NameError: # Python >= 2.5 + FileNotFoundError = IOError + + +class MultiProcessCollector: + """Collector for files for multi-process mode.""" + + def __init__(self, registry, path=None): + if path is None: + # This deprecation warning can go away in a few releases when removing the compatibility + if 'prometheus_multiproc_dir' in os.environ and 'PROMETHEUS_MULTIPROC_DIR' not in os.environ: + os.environ['PROMETHEUS_MULTIPROC_DIR'] = os.environ['prometheus_multiproc_dir'] + warnings.warn("prometheus_multiproc_dir variable has been deprecated in favor of the upper case naming PROMETHEUS_MULTIPROC_DIR", DeprecationWarning) + path = os.environ.get('PROMETHEUS_MULTIPROC_DIR') + if not path or not os.path.isdir(path): + raise ValueError('env PROMETHEUS_MULTIPROC_DIR is not set or not a directory') + self._path = path + if registry: + registry.register(self) + + @staticmethod + def merge(files, accumulate=True): + """Merge metrics from given mmap files. + + By default, histograms are accumulated, as per prometheus wire format. + But if writing the merged data back to mmap files, use + accumulate=False to avoid compound accumulation. + """ + metrics = MultiProcessCollector._read_metrics(files) + return MultiProcessCollector._accumulate_metrics(metrics, accumulate) + + @staticmethod + def _read_metrics(files): + metrics = {} + key_cache = {} + + def _parse_key(key): + val = key_cache.get(key) + if not val: + metric_name, name, labels, help_text = json.loads(key) + labels_key = tuple(sorted(labels.items())) + val = key_cache[key] = (metric_name, name, labels, labels_key, help_text) + return val + + for f in files: + parts = os.path.basename(f).split('_') + typ = parts[0] + try: + file_values = MmapedDict.read_all_values_from_file(f) + except FileNotFoundError: + if typ == 'gauge' and parts[1].startswith('live'): + # Files for 'live*' gauges can be deleted between the glob of collect + # and now (via a mark_process_dead call) so don't fail if + # the file is missing + continue + raise + for key, value, timestamp, _ in file_values: + metric_name, name, labels, labels_key, help_text = _parse_key(key) + + metric = metrics.get(metric_name) + if metric is None: + metric = Metric(metric_name, help_text, typ) + metrics[metric_name] = metric + + if typ == 'gauge': + pid = parts[2][:-3] + metric._multiprocess_mode = parts[1] + metric.add_sample(name, labels_key + (('pid', pid),), value, timestamp) + else: + # The duplicates and labels are fixed in the next for. + metric.add_sample(name, labels_key, value) + return metrics + + @staticmethod + def _accumulate_metrics(metrics, accumulate): + for metric in metrics.values(): + samples = defaultdict(float) + sample_timestamps = defaultdict(float) + buckets = defaultdict(lambda: defaultdict(float)) + samples_setdefault = samples.setdefault + for s in metric.samples: + name, labels, value, timestamp, exemplar = s + if metric.type == 'gauge': + without_pid_key = (name, tuple(l for l in labels if l[0] != 'pid')) + if metric._multiprocess_mode in ('min', 'livemin'): + current = samples_setdefault(without_pid_key, value) + if value < current: + samples[without_pid_key] = value + elif metric._multiprocess_mode in ('max', 'livemax'): + current = samples_setdefault(without_pid_key, value) + if value > current: + samples[without_pid_key] = value + elif metric._multiprocess_mode in ('sum', 'livesum'): + samples[without_pid_key] += value + elif metric._multiprocess_mode in ('mostrecent', 'livemostrecent'): + current_timestamp = sample_timestamps[without_pid_key] + timestamp = float(timestamp or 0) + if current_timestamp < timestamp: + samples[without_pid_key] = value + sample_timestamps[without_pid_key] = timestamp + else: # all/liveall + samples[(name, labels)] = value + + elif metric.type == 'histogram': + # A for loop with early exit is faster than a genexpr + # or a listcomp that ends up building unnecessary things + for l in labels: + if l[0] == 'le': + bucket_value = float(l[1]) + # _bucket + without_le = tuple(l for l in labels if l[0] != 'le') + buckets[without_le][bucket_value] += value + break + else: # did not find the `le` key + # _sum/_count + samples[(name, labels)] += value + else: + # Counter and Summary. + samples[(name, labels)] += value + + # Accumulate bucket values. + if metric.type == 'histogram': + for labels, values in buckets.items(): + acc = 0.0 + for bucket, value in sorted(values.items()): + sample_key = ( + metric.name + '_bucket', + labels + (('le', floatToGoString(bucket)),), + ) + if accumulate: + acc += value + samples[sample_key] = acc + else: + samples[sample_key] = value + if accumulate: + samples[(metric.name + '_count', labels)] = acc + + # Convert to correct sample format. + metric.samples = [Sample(name_, dict(labels), value) for (name_, labels), value in samples.items()] + return metrics.values() + + def collect(self): + files = glob.glob(os.path.join(self._path, '*.db')) + return self.merge(files, accumulate=True) + + +_LIVE_GAUGE_MULTIPROCESS_MODES = {m for m in Gauge._MULTIPROC_MODES if m.startswith('live')} + + +def mark_process_dead(pid, path=None): + """Do bookkeeping for when one process dies in a multi-process setup.""" + if path is None: + path = os.environ.get('PROMETHEUS_MULTIPROC_DIR', os.environ.get('prometheus_multiproc_dir')) + for mode in _LIVE_GAUGE_MULTIPROCESS_MODES: + for f in glob.glob(os.path.join(path, f'gauge_{mode}_{pid}.db')): + os.remove(f) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__init__.py b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ea918562b835bd9d15178d9b4da1d5588ca09bc Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/exposition.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/exposition.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbc99500e6845228f21db54247b400a87691ea0f Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/exposition.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/parser.cpython-311.pyc b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/parser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bda6fbcc56530490c6ccf5d0da3d3b620ede6a9 Binary files /dev/null and b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/__pycache__/parser.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/exposition.py b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/exposition.py new file mode 100644 index 0000000000000000000000000000000000000000..26f3109fa5edfd414d84224bd218159d6196b3d1 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/exposition.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + + +from ..utils import floatToGoString + +CONTENT_TYPE_LATEST = 'application/openmetrics-text; version=1.0.0; charset=utf-8' +"""Content type of the latest OpenMetrics text format""" + + +def _is_valid_exemplar_metric(metric, sample): + if metric.type == 'counter' and sample.name.endswith('_total'): + return True + if metric.type in ('histogram', 'gaugehistogram') and sample.name.endswith('_bucket'): + return True + return False + + +def generate_latest(registry): + '''Returns the metrics from the registry in latest text format as a string.''' + output = [] + for metric in registry.collect(): + try: + mname = metric.name + output.append('# HELP {} {}\n'.format( + mname, metric.documentation.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"'))) + output.append(f'# TYPE {mname} {metric.type}\n') + if metric.unit: + output.append(f'# UNIT {mname} {metric.unit}\n') + for s in metric.samples: + if s.labels: + labelstr = '{{{0}}}'.format(','.join( + ['{}="{}"'.format( + k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) + for k, v in sorted(s.labels.items())])) + else: + labelstr = '' + if s.exemplar: + if not _is_valid_exemplar_metric(metric, s): + raise ValueError(f"Metric {metric.name} has exemplars, but is not a histogram bucket or counter") + labels = '{{{0}}}'.format(','.join( + ['{}="{}"'.format( + k, v.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"')) + for k, v in sorted(s.exemplar.labels.items())])) + if s.exemplar.timestamp is not None: + exemplarstr = ' # {} {} {}'.format( + labels, + floatToGoString(s.exemplar.value), + s.exemplar.timestamp, + ) + else: + exemplarstr = ' # {} {}'.format( + labels, + floatToGoString(s.exemplar.value), + ) + else: + exemplarstr = '' + timestamp = '' + if s.timestamp is not None: + timestamp = f' {s.timestamp}' + output.append('{}{} {}{}{}\n'.format( + s.name, + labelstr, + floatToGoString(s.value), + timestamp, + exemplarstr, + )) + except Exception as exception: + exception.args = (exception.args or ('',)) + (metric,) + raise + + output.append('# EOF\n') + return ''.join(output).encode('utf-8') diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/parser.py b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..6128a0d314541c67a02cc270ecdbbef570ec1637 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/openmetrics/parser.py @@ -0,0 +1,614 @@ +#!/usr/bin/env python + + +import io as StringIO +import math +import re + +from ..metrics_core import Metric, METRIC_LABEL_NAME_RE +from ..samples import Exemplar, Sample, Timestamp +from ..utils import floatToGoString + + +def text_string_to_metric_families(text): + """Parse Openmetrics text format from a unicode string. + + See text_fd_to_metric_families. + """ + yield from text_fd_to_metric_families(StringIO.StringIO(text)) + + +_CANONICAL_NUMBERS = {float("inf")} + + +def _isUncanonicalNumber(s): + f = float(s) + if f not in _CANONICAL_NUMBERS: + return False # Only the canonical numbers are required to be canonical. + return s != floatToGoString(f) + + +ESCAPE_SEQUENCES = { + '\\\\': '\\', + '\\n': '\n', + '\\"': '"', +} + + +def _replace_escape_sequence(match): + return ESCAPE_SEQUENCES[match.group(0)] + + +ESCAPING_RE = re.compile(r'\\[\\n"]') + + +def _replace_escaping(s): + return ESCAPING_RE.sub(_replace_escape_sequence, s) + + +def _unescape_help(text): + result = [] + slash = False + + for char in text: + if slash: + if char == '\\': + result.append('\\') + elif char == '"': + result.append('"') + elif char == 'n': + result.append('\n') + else: + result.append('\\' + char) + slash = False + else: + if char == '\\': + slash = True + else: + result.append(char) + + if slash: + result.append('\\') + + return ''.join(result) + + +def _parse_value(value): + value = ''.join(value) + if value != value.strip() or '_' in value: + raise ValueError(f"Invalid value: {value!r}") + try: + return int(value) + except ValueError: + return float(value) + + +def _parse_timestamp(timestamp): + timestamp = ''.join(timestamp) + if not timestamp: + return None + if timestamp != timestamp.strip() or '_' in timestamp: + raise ValueError(f"Invalid timestamp: {timestamp!r}") + try: + # Simple int. + return Timestamp(int(timestamp), 0) + except ValueError: + try: + # aaaa.bbbb. Nanosecond resolution supported. + parts = timestamp.split('.', 1) + return Timestamp(int(parts[0]), int(parts[1][:9].ljust(9, "0"))) + except ValueError: + # Float. + ts = float(timestamp) + if math.isnan(ts) or math.isinf(ts): + raise ValueError(f"Invalid timestamp: {timestamp!r}") + return ts + + +def _is_character_escaped(s, charpos): + num_bslashes = 0 + while (charpos > num_bslashes + and s[charpos - 1 - num_bslashes] == '\\'): + num_bslashes += 1 + return num_bslashes % 2 == 1 + + +def _parse_labels_with_state_machine(text): + # The { has already been parsed. + state = 'startoflabelname' + labelname = [] + labelvalue = [] + labels = {} + labels_len = 0 + + for char in text: + if state == 'startoflabelname': + if char == '}': + state = 'endoflabels' + else: + state = 'labelname' + labelname.append(char) + elif state == 'labelname': + if char == '=': + state = 'labelvaluequote' + else: + labelname.append(char) + elif state == 'labelvaluequote': + if char == '"': + state = 'labelvalue' + else: + raise ValueError("Invalid line: " + text) + elif state == 'labelvalue': + if char == '\\': + state = 'labelvalueslash' + elif char == '"': + ln = ''.join(labelname) + if not METRIC_LABEL_NAME_RE.match(ln): + raise ValueError("Invalid line, bad label name: " + text) + if ln in labels: + raise ValueError("Invalid line, duplicate label name: " + text) + labels[ln] = ''.join(labelvalue) + labelname = [] + labelvalue = [] + state = 'endoflabelvalue' + else: + labelvalue.append(char) + elif state == 'endoflabelvalue': + if char == ',': + state = 'labelname' + elif char == '}': + state = 'endoflabels' + else: + raise ValueError("Invalid line: " + text) + elif state == 'labelvalueslash': + state = 'labelvalue' + if char == '\\': + labelvalue.append('\\') + elif char == 'n': + labelvalue.append('\n') + elif char == '"': + labelvalue.append('"') + else: + labelvalue.append('\\' + char) + elif state == 'endoflabels': + if char == ' ': + break + else: + raise ValueError("Invalid line: " + text) + labels_len += 1 + return labels, labels_len + + +def _parse_labels(text): + labels = {} + + # Raise error if we don't have valid labels + if text and "=" not in text: + raise ValueError + + # Copy original labels + sub_labels = text + try: + # Process one label at a time + while sub_labels: + # The label name is before the equal + value_start = sub_labels.index("=") + label_name = sub_labels[:value_start] + sub_labels = sub_labels[value_start + 1:] + + # Check for missing quotes + if not sub_labels or sub_labels[0] != '"': + raise ValueError + + # The first quote is guaranteed to be after the equal + value_substr = sub_labels[1:] + + # Check for extra commas + if not label_name or label_name[0] == ',': + raise ValueError + if not value_substr or value_substr[-1] == ',': + raise ValueError + + # Find the last unescaped quote + i = 0 + while i < len(value_substr): + i = value_substr.index('"', i) + if not _is_character_escaped(value_substr[:i], i): + break + i += 1 + + # The label value is between the first and last quote + quote_end = i + 1 + label_value = sub_labels[1:quote_end] + # Replace escaping if needed + if "\\" in label_value: + label_value = _replace_escaping(label_value) + if not METRIC_LABEL_NAME_RE.match(label_name): + raise ValueError("invalid line, bad label name: " + text) + if label_name in labels: + raise ValueError("invalid line, duplicate label name: " + text) + labels[label_name] = label_value + + # Remove the processed label from the sub-slice for next iteration + sub_labels = sub_labels[quote_end + 1:] + if sub_labels.startswith(","): + next_comma = 1 + else: + next_comma = 0 + sub_labels = sub_labels[next_comma:] + + # Check for missing commas + if sub_labels and next_comma == 0: + raise ValueError + + return labels + + except ValueError: + raise ValueError("Invalid labels: " + text) + + +def _parse_sample(text): + separator = " # " + # Detect the labels in the text + label_start = text.find("{") + if label_start == -1 or separator in text[:label_start]: + # We don't have labels, but there could be an exemplar. + name_end = text.index(" ") + name = text[:name_end] + # Parse the remaining text after the name + remaining_text = text[name_end + 1:] + value, timestamp, exemplar = _parse_remaining_text(remaining_text) + return Sample(name, {}, value, timestamp, exemplar) + # The name is before the labels + name = text[:label_start] + if separator not in text: + # Line doesn't contain an exemplar + # We can use `rindex` to find `label_end` + label_end = text.rindex("}") + label = text[label_start + 1:label_end] + labels = _parse_labels(label) + else: + # Line potentially contains an exemplar + # Fallback to parsing labels with a state machine + labels, labels_len = _parse_labels_with_state_machine(text[label_start + 1:]) + label_end = labels_len + len(name) + # Parsing labels succeeded, continue parsing the remaining text + remaining_text = text[label_end + 2:] + value, timestamp, exemplar = _parse_remaining_text(remaining_text) + return Sample(name, labels, value, timestamp, exemplar) + + +def _parse_remaining_text(text): + split_text = text.split(" ", 1) + val = _parse_value(split_text[0]) + if len(split_text) == 1: + # We don't have timestamp or exemplar + return val, None, None + + timestamp = [] + exemplar_value = [] + exemplar_timestamp = [] + exemplar_labels = None + + state = 'timestamp' + text = split_text[1] + + it = iter(text) + for char in it: + if state == 'timestamp': + if char == '#' and not timestamp: + state = 'exemplarspace' + elif char == ' ': + state = 'exemplarhash' + else: + timestamp.append(char) + elif state == 'exemplarhash': + if char == '#': + state = 'exemplarspace' + else: + raise ValueError("Invalid line: " + text) + elif state == 'exemplarspace': + if char == ' ': + state = 'exemplarstartoflabels' + else: + raise ValueError("Invalid line: " + text) + elif state == 'exemplarstartoflabels': + if char == '{': + label_start, label_end = text.index("{"), text.rindex("}") + exemplar_labels = _parse_labels(text[label_start + 1:label_end]) + state = 'exemplarparsedlabels' + else: + raise ValueError("Invalid line: " + text) + elif state == 'exemplarparsedlabels': + if char == '}': + state = 'exemplarvaluespace' + elif state == 'exemplarvaluespace': + if char == ' ': + state = 'exemplarvalue' + else: + raise ValueError("Invalid line: " + text) + elif state == 'exemplarvalue': + if char == ' ' and not exemplar_value: + raise ValueError("Invalid line: " + text) + elif char == ' ': + state = 'exemplartimestamp' + else: + exemplar_value.append(char) + elif state == 'exemplartimestamp': + exemplar_timestamp.append(char) + + # Trailing space after value. + if state == 'timestamp' and not timestamp: + raise ValueError("Invalid line: " + text) + + # Trailing space after value. + if state == 'exemplartimestamp' and not exemplar_timestamp: + raise ValueError("Invalid line: " + text) + + # Incomplete exemplar. + if state in ['exemplarhash', 'exemplarspace', 'exemplarstartoflabels', 'exemplarparsedlabels']: + raise ValueError("Invalid line: " + text) + + ts = _parse_timestamp(timestamp) + exemplar = None + if exemplar_labels is not None: + exemplar_length = sum(len(k) + len(v) for k, v in exemplar_labels.items()) + if exemplar_length > 128: + raise ValueError("Exemplar labels are too long: " + text) + exemplar = Exemplar( + exemplar_labels, + _parse_value(exemplar_value), + _parse_timestamp(exemplar_timestamp), + ) + + return val, ts, exemplar + + +def _group_for_sample(sample, name, typ): + if typ == 'info': + # We can't distinguish between groups for info metrics. + return {} + if typ == 'summary' and sample.name == name: + d = sample.labels.copy() + del d['quantile'] + return d + if typ == 'stateset': + d = sample.labels.copy() + del d[name] + return d + if typ in ['histogram', 'gaugehistogram'] and sample.name == name + '_bucket': + d = sample.labels.copy() + del d['le'] + return d + return sample.labels + + +def _check_histogram(samples, name): + group = None + timestamp = None + + def do_checks(): + if bucket != float('+Inf'): + raise ValueError("+Inf bucket missing: " + name) + if count is not None and value != count: + raise ValueError("Count does not match +Inf value: " + name) + if has_sum and count is None: + raise ValueError("_count must be present if _sum is present: " + name) + if has_gsum and count is None: + raise ValueError("_gcount must be present if _gsum is present: " + name) + if not (has_sum or has_gsum) and count is not None: + raise ValueError("_sum/_gsum must be present if _count is present: " + name) + if has_negative_buckets and has_sum: + raise ValueError("Cannot have _sum with negative buckets: " + name) + if not has_negative_buckets and has_negative_gsum: + raise ValueError("Cannot have negative _gsum with non-negative buckets: " + name) + + for s in samples: + suffix = s.name[len(name):] + g = _group_for_sample(s, name, 'histogram') + if g != group or s.timestamp != timestamp: + if group is not None: + do_checks() + count = None + bucket = None + has_negative_buckets = False + has_sum = False + has_gsum = False + has_negative_gsum = False + value = 0 + group = g + timestamp = s.timestamp + + if suffix == '_bucket': + b = float(s.labels['le']) + if b < 0: + has_negative_buckets = True + if bucket is not None and b <= bucket: + raise ValueError("Buckets out of order: " + name) + if s.value < value: + raise ValueError("Bucket values out of order: " + name) + bucket = b + value = s.value + elif suffix in ['_count', '_gcount']: + count = s.value + elif suffix in ['_sum']: + has_sum = True + elif suffix in ['_gsum']: + has_gsum = True + if s.value < 0: + has_negative_gsum = True + + if group is not None: + do_checks() + + +def text_fd_to_metric_families(fd): + """Parse Prometheus text format from a file descriptor. + + This is a laxer parser than the main Go parser, + so successful parsing does not imply that the parsed + text meets the specification. + + Yields Metric's. + """ + name = None + allowed_names = [] + eof = False + + seen_names = set() + type_suffixes = { + 'counter': ['_total', '_created'], + 'summary': ['', '_count', '_sum', '_created'], + 'histogram': ['_count', '_sum', '_bucket', '_created'], + 'gaugehistogram': ['_gcount', '_gsum', '_bucket'], + 'info': ['_info'], + } + + def build_metric(name, documentation, typ, unit, samples): + if typ is None: + typ = 'unknown' + for suffix in set(type_suffixes.get(typ, []) + [""]): + if name + suffix in seen_names: + raise ValueError("Clashing name: " + name + suffix) + seen_names.add(name + suffix) + if documentation is None: + documentation = '' + if unit is None: + unit = '' + if unit and not name.endswith("_" + unit): + raise ValueError("Unit does not match metric name: " + name) + if unit and typ in ['info', 'stateset']: + raise ValueError("Units not allowed for this metric type: " + name) + if typ in ['histogram', 'gaugehistogram']: + _check_histogram(samples, name) + metric = Metric(name, documentation, typ, unit) + # TODO: check labelvalues are valid utf8 + metric.samples = samples + return metric + + for line in fd: + if line[-1] == '\n': + line = line[:-1] + + if eof: + raise ValueError("Received line after # EOF: " + line) + + if not line: + raise ValueError("Received blank line") + + if line == '# EOF': + eof = True + elif line.startswith('#'): + parts = line.split(' ', 3) + if len(parts) < 4: + raise ValueError("Invalid line: " + line) + if parts[2] == name and samples: + raise ValueError("Received metadata after samples: " + line) + if parts[2] != name: + if name is not None: + yield build_metric(name, documentation, typ, unit, samples) + # New metric + name = parts[2] + unit = None + typ = None + documentation = None + group = None + seen_groups = set() + group_timestamp = None + group_timestamp_samples = set() + samples = [] + allowed_names = [parts[2]] + + if parts[1] == 'HELP': + if documentation is not None: + raise ValueError("More than one HELP for metric: " + line) + documentation = _unescape_help(parts[3]) + elif parts[1] == 'TYPE': + if typ is not None: + raise ValueError("More than one TYPE for metric: " + line) + typ = parts[3] + if typ == 'untyped': + raise ValueError("Invalid TYPE for metric: " + line) + allowed_names = [name + n for n in type_suffixes.get(typ, [''])] + elif parts[1] == 'UNIT': + if unit is not None: + raise ValueError("More than one UNIT for metric: " + line) + unit = parts[3] + else: + raise ValueError("Invalid line: " + line) + else: + sample = _parse_sample(line) + if sample.name not in allowed_names: + if name is not None: + yield build_metric(name, documentation, typ, unit, samples) + # Start an unknown metric. + name = sample.name + documentation = None + unit = None + typ = 'unknown' + samples = [] + group = None + group_timestamp = None + group_timestamp_samples = set() + seen_groups = set() + allowed_names = [sample.name] + + if typ == 'stateset' and name not in sample.labels: + raise ValueError("Stateset missing label: " + line) + if (name + '_bucket' == sample.name + and (sample.labels.get('le', "NaN") == "NaN" + or _isUncanonicalNumber(sample.labels['le']))): + raise ValueError("Invalid le label: " + line) + if (name + '_bucket' == sample.name + and (not isinstance(sample.value, int) and not sample.value.is_integer())): + raise ValueError("Bucket value must be an integer: " + line) + if ((name + '_count' == sample.name or name + '_gcount' == sample.name) + and (not isinstance(sample.value, int) and not sample.value.is_integer())): + raise ValueError("Count value must be an integer: " + line) + if (typ == 'summary' and name == sample.name + and (not (0 <= float(sample.labels.get('quantile', -1)) <= 1) + or _isUncanonicalNumber(sample.labels['quantile']))): + raise ValueError("Invalid quantile label: " + line) + + g = tuple(sorted(_group_for_sample(sample, name, typ).items())) + if group is not None and g != group and g in seen_groups: + raise ValueError("Invalid metric grouping: " + line) + if group is not None and g == group: + if (sample.timestamp is None) != (group_timestamp is None): + raise ValueError("Mix of timestamp presence within a group: " + line) + if group_timestamp is not None and group_timestamp > sample.timestamp and typ != 'info': + raise ValueError("Timestamps went backwards within a group: " + line) + else: + group_timestamp_samples = set() + + series_id = (sample.name, tuple(sorted(sample.labels.items()))) + if sample.timestamp != group_timestamp or series_id not in group_timestamp_samples: + # Not a duplicate due to timestamp truncation. + samples.append(sample) + group_timestamp_samples.add(series_id) + + group = g + group_timestamp = sample.timestamp + seen_groups.add(g) + + if typ == 'stateset' and sample.value not in [0, 1]: + raise ValueError("Stateset samples can only have values zero and one: " + line) + if typ == 'info' and sample.value != 1: + raise ValueError("Info samples can only have value one: " + line) + if typ == 'summary' and name == sample.name and sample.value < 0: + raise ValueError("Quantile values cannot be negative: " + line) + if sample.name[len(name):] in ['_total', '_sum', '_count', '_bucket', '_gcount', '_gsum'] and math.isnan( + sample.value): + raise ValueError("Counter-like samples cannot be NaN: " + line) + if sample.name[len(name):] in ['_total', '_sum', '_count', '_bucket', '_gcount'] and sample.value < 0: + raise ValueError("Counter-like samples cannot be negative: " + line) + if sample.exemplar and not ( + (typ in ['histogram', 'gaugehistogram'] and sample.name.endswith('_bucket')) + or (typ in ['counter'] and sample.name.endswith('_total'))): + raise ValueError("Invalid line only histogram/gaugehistogram buckets and counters can have exemplars: " + line) + + if name is not None: + yield build_metric(name, documentation, typ, unit, samples) + + if not eof: + raise ValueError("Missing # EOF at end") diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/parser.py b/.venv/lib/python3.11/site-packages/prometheus_client/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..dc8e30df7b67043b5760c84b7bdef5a2fbef592c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/parser.py @@ -0,0 +1,225 @@ +import io as StringIO +import re +from typing import Dict, Iterable, List, Match, Optional, TextIO, Tuple + +from .metrics_core import Metric +from .samples import Sample + + +def text_string_to_metric_families(text: str) -> Iterable[Metric]: + """Parse Prometheus text format from a unicode string. + + See text_fd_to_metric_families. + """ + yield from text_fd_to_metric_families(StringIO.StringIO(text)) + + +ESCAPE_SEQUENCES = { + '\\\\': '\\', + '\\n': '\n', + '\\"': '"', +} + + +def replace_escape_sequence(match: Match[str]) -> str: + return ESCAPE_SEQUENCES[match.group(0)] + + +HELP_ESCAPING_RE = re.compile(r'\\[\\n]') +ESCAPING_RE = re.compile(r'\\[\\n"]') + + +def _replace_help_escaping(s: str) -> str: + return HELP_ESCAPING_RE.sub(replace_escape_sequence, s) + + +def _replace_escaping(s: str) -> str: + return ESCAPING_RE.sub(replace_escape_sequence, s) + + +def _is_character_escaped(s: str, charpos: int) -> bool: + num_bslashes = 0 + while (charpos > num_bslashes + and s[charpos - 1 - num_bslashes] == '\\'): + num_bslashes += 1 + return num_bslashes % 2 == 1 + + +def _parse_labels(labels_string: str) -> Dict[str, str]: + labels: Dict[str, str] = {} + # Return if we don't have valid labels + if "=" not in labels_string: + return labels + + escaping = False + if "\\" in labels_string: + escaping = True + + # Copy original labels + sub_labels = labels_string + try: + # Process one label at a time + while sub_labels: + # The label name is before the equal + value_start = sub_labels.index("=") + label_name = sub_labels[:value_start] + sub_labels = sub_labels[value_start + 1:].lstrip() + # Find the first quote after the equal + quote_start = sub_labels.index('"') + 1 + value_substr = sub_labels[quote_start:] + + # Find the last unescaped quote + i = 0 + while i < len(value_substr): + i = value_substr.index('"', i) + if not _is_character_escaped(value_substr, i): + break + i += 1 + + # The label value is between the first and last quote + quote_end = i + 1 + label_value = sub_labels[quote_start:quote_end] + # Replace escaping if needed + if escaping: + label_value = _replace_escaping(label_value) + labels[label_name.strip()] = label_value + + # Remove the processed label from the sub-slice for next iteration + sub_labels = sub_labels[quote_end + 1:] + next_comma = sub_labels.find(",") + 1 + sub_labels = sub_labels[next_comma:].lstrip() + + return labels + + except ValueError: + raise ValueError("Invalid labels: %s" % labels_string) + + +# If we have multiple values only consider the first +def _parse_value_and_timestamp(s: str) -> Tuple[float, Optional[float]]: + s = s.lstrip() + separator = " " + if separator not in s: + separator = "\t" + values = [value.strip() for value in s.split(separator) if value.strip()] + if not values: + return float(s), None + value = float(values[0]) + timestamp = (float(values[-1]) / 1000) if len(values) > 1 else None + return value, timestamp + + +def _parse_sample(text: str) -> Sample: + # Detect the labels in the text + try: + label_start, label_end = text.index("{"), text.rindex("}") + # The name is before the labels + name = text[:label_start].strip() + # We ignore the starting curly brace + label = text[label_start + 1:label_end] + # The value is after the label end (ignoring curly brace) + value, timestamp = _parse_value_and_timestamp(text[label_end + 1:]) + return Sample(name, _parse_labels(label), value, timestamp) + + # We don't have labels + except ValueError: + # Detect what separator is used + separator = " " + if separator not in text: + separator = "\t" + name_end = text.index(separator) + name = text[:name_end] + # The value is after the name + value, timestamp = _parse_value_and_timestamp(text[name_end:]) + return Sample(name, {}, value, timestamp) + + +def text_fd_to_metric_families(fd: TextIO) -> Iterable[Metric]: + """Parse Prometheus text format from a file descriptor. + + This is a laxer parser than the main Go parser, + so successful parsing does not imply that the parsed + text meets the specification. + + Yields Metric's. + """ + name = '' + documentation = '' + typ = 'untyped' + samples: List[Sample] = [] + allowed_names = [] + + def build_metric(name: str, documentation: str, typ: str, samples: List[Sample]) -> Metric: + # Munge counters into OpenMetrics representation + # used internally. + if typ == 'counter': + if name.endswith('_total'): + name = name[:-6] + else: + new_samples = [] + for s in samples: + new_samples.append(Sample(s[0] + '_total', *s[1:])) + samples = new_samples + metric = Metric(name, documentation, typ) + metric.samples = samples + return metric + + for line in fd: + line = line.strip() + + if line.startswith('#'): + parts = line.split(None, 3) + if len(parts) < 2: + continue + if parts[1] == 'HELP': + if parts[2] != name: + if name != '': + yield build_metric(name, documentation, typ, samples) + # New metric + name = parts[2] + typ = 'untyped' + samples = [] + allowed_names = [parts[2]] + if len(parts) == 4: + documentation = _replace_help_escaping(parts[3]) + else: + documentation = '' + elif parts[1] == 'TYPE': + if parts[2] != name: + if name != '': + yield build_metric(name, documentation, typ, samples) + # New metric + name = parts[2] + documentation = '' + samples = [] + typ = parts[3] + allowed_names = { + 'counter': [''], + 'gauge': [''], + 'summary': ['_count', '_sum', ''], + 'histogram': ['_count', '_sum', '_bucket'], + }.get(typ, ['']) + allowed_names = [name + n for n in allowed_names] + else: + # Ignore other comment tokens + pass + elif line == '': + # Ignore blank lines + pass + else: + sample = _parse_sample(line) + if sample.name not in allowed_names: + if name != '': + yield build_metric(name, documentation, typ, samples) + # New metric, yield immediately as untyped singleton + name = '' + documentation = '' + typ = 'untyped' + samples = [] + allowed_names = [] + yield build_metric(sample[0], documentation, typ, [sample]) + else: + samples.append(sample) + + if name != '': + yield build_metric(name, documentation, typ, samples) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/platform_collector.py b/.venv/lib/python3.11/site-packages/prometheus_client/platform_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..6040fcce622ad229dafdd00ea2c0692504294041 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/platform_collector.py @@ -0,0 +1,59 @@ +import platform as pf +from typing import Any, Iterable, Optional + +from .metrics_core import GaugeMetricFamily, Metric +from .registry import Collector, CollectorRegistry, REGISTRY + + +class PlatformCollector(Collector): + """Collector for python platform information""" + + def __init__(self, + registry: Optional[CollectorRegistry] = REGISTRY, + platform: Optional[Any] = None, + ): + self._platform = pf if platform is None else platform + info = self._info() + system = self._platform.system() + if system == "Java": + info.update(self._java()) + self._metrics = [ + self._add_metric("python_info", "Python platform information", info) + ] + if registry: + registry.register(self) + + def collect(self) -> Iterable[Metric]: + return self._metrics + + @staticmethod + def _add_metric(name, documentation, data): + labels = data.keys() + values = [data[k] for k in labels] + g = GaugeMetricFamily(name, documentation, labels=labels) + g.add_metric(values, 1) + return g + + def _info(self): + major, minor, patchlevel = self._platform.python_version_tuple() + return { + "version": self._platform.python_version(), + "implementation": self._platform.python_implementation(), + "major": major, + "minor": minor, + "patchlevel": patchlevel + } + + def _java(self): + java_version, _, vminfo, osinfo = self._platform.java_ver() + vm_name, vm_release, vm_vendor = vminfo + return { + "jvm_version": java_version, + "jvm_release": vm_release, + "jvm_vendor": vm_vendor, + "jvm_name": vm_name + } + + +PLATFORM_COLLECTOR = PlatformCollector() +"""PlatformCollector in default Registry REGISTRY""" diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/process_collector.py b/.venv/lib/python3.11/site-packages/prometheus_client/process_collector.py new file mode 100644 index 0000000000000000000000000000000000000000..2894e8745020201e9e6e7ffc01466c11b2e096b3 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/process_collector.py @@ -0,0 +1,101 @@ +import os +from typing import Callable, Iterable, Optional, Union + +from .metrics_core import CounterMetricFamily, GaugeMetricFamily, Metric +from .registry import Collector, CollectorRegistry, REGISTRY + +try: + import resource + + _PAGESIZE = resource.getpagesize() +except ImportError: + # Not Unix + _PAGESIZE = 4096 + + +class ProcessCollector(Collector): + """Collector for Standard Exports such as cpu and memory.""" + + def __init__(self, + namespace: str = '', + pid: Callable[[], Union[int, str]] = lambda: 'self', + proc: str = '/proc', + registry: Optional[CollectorRegistry] = REGISTRY): + self._namespace = namespace + self._pid = pid + self._proc = proc + if namespace: + self._prefix = namespace + '_process_' + else: + self._prefix = 'process_' + self._ticks = 100.0 + try: + self._ticks = os.sysconf('SC_CLK_TCK') + except (ValueError, TypeError, AttributeError, OSError): + pass + + self._pagesize = _PAGESIZE + + # This is used to test if we can access /proc. + self._btime = 0 + try: + self._btime = self._boot_time() + except OSError: + pass + if registry: + registry.register(self) + + def _boot_time(self): + with open(os.path.join(self._proc, 'stat'), 'rb') as stat: + for line in stat: + if line.startswith(b'btime '): + return float(line.split()[1]) + + def collect(self) -> Iterable[Metric]: + if not self._btime: + return [] + + pid = os.path.join(self._proc, str(self._pid()).strip()) + + result = [] + try: + with open(os.path.join(pid, 'stat'), 'rb') as stat: + parts = (stat.read().split(b')')[-1].split()) + + vmem = GaugeMetricFamily(self._prefix + 'virtual_memory_bytes', + 'Virtual memory size in bytes.', value=float(parts[20])) + rss = GaugeMetricFamily(self._prefix + 'resident_memory_bytes', 'Resident memory size in bytes.', + value=float(parts[21]) * self._pagesize) + start_time_secs = float(parts[19]) / self._ticks + start_time = GaugeMetricFamily(self._prefix + 'start_time_seconds', + 'Start time of the process since unix epoch in seconds.', + value=start_time_secs + self._btime) + utime = float(parts[11]) / self._ticks + stime = float(parts[12]) / self._ticks + cpu = CounterMetricFamily(self._prefix + 'cpu_seconds_total', + 'Total user and system CPU time spent in seconds.', + value=utime + stime) + result.extend([vmem, rss, start_time, cpu]) + except OSError: + pass + + try: + with open(os.path.join(pid, 'limits'), 'rb') as limits: + for line in limits: + if line.startswith(b'Max open file'): + max_fds = GaugeMetricFamily(self._prefix + 'max_fds', + 'Maximum number of open file descriptors.', + value=float(line.split()[3])) + break + open_fds = GaugeMetricFamily(self._prefix + 'open_fds', + 'Number of open file descriptors.', + len(os.listdir(os.path.join(pid, 'fd')))) + result.extend([open_fds, max_fds]) + except OSError: + pass + + return result + + +PROCESS_COLLECTOR = ProcessCollector() +"""Default ProcessCollector in default Registry REGISTRY.""" diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/py.typed b/.venv/lib/python3.11/site-packages/prometheus_client/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/registry.py b/.venv/lib/python3.11/site-packages/prometheus_client/registry.py new file mode 100644 index 0000000000000000000000000000000000000000..694e4bd87f5fe83cfe4ab380a24b7730adb9285d --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/registry.py @@ -0,0 +1,168 @@ +from abc import ABC, abstractmethod +import copy +from threading import Lock +from typing import Dict, Iterable, List, Optional + +from .metrics_core import Metric + + +# Ideally this would be a Protocol, but Protocols are only available in Python >= 3.8. +class Collector(ABC): + @abstractmethod + def collect(self) -> Iterable[Metric]: + pass + + +class _EmptyCollector(Collector): + def collect(self) -> Iterable[Metric]: + return [] + + +class CollectorRegistry(Collector): + """Metric collector registry. + + Collectors must have a no-argument method 'collect' that returns a list of + Metric objects. The returned metrics should be consistent with the Prometheus + exposition formats. + """ + + def __init__(self, auto_describe: bool = False, target_info: Optional[Dict[str, str]] = None): + self._collector_to_names: Dict[Collector, List[str]] = {} + self._names_to_collectors: Dict[str, Collector] = {} + self._auto_describe = auto_describe + self._lock = Lock() + self._target_info: Optional[Dict[str, str]] = {} + self.set_target_info(target_info) + + def register(self, collector: Collector) -> None: + """Add a collector to the registry.""" + with self._lock: + names = self._get_names(collector) + duplicates = set(self._names_to_collectors).intersection(names) + if duplicates: + raise ValueError( + 'Duplicated timeseries in CollectorRegistry: {}'.format( + duplicates)) + for name in names: + self._names_to_collectors[name] = collector + self._collector_to_names[collector] = names + + def unregister(self, collector: Collector) -> None: + """Remove a collector from the registry.""" + with self._lock: + for name in self._collector_to_names[collector]: + del self._names_to_collectors[name] + del self._collector_to_names[collector] + + def _get_names(self, collector): + """Get names of timeseries the collector produces and clashes with.""" + desc_func = None + # If there's a describe function, use it. + try: + desc_func = collector.describe + except AttributeError: + pass + # Otherwise, if auto describe is enabled use the collect function. + if not desc_func and self._auto_describe: + desc_func = collector.collect + + if not desc_func: + return [] + + result = [] + type_suffixes = { + 'counter': ['_total', '_created'], + 'summary': ['_sum', '_count', '_created'], + 'histogram': ['_bucket', '_sum', '_count', '_created'], + 'gaugehistogram': ['_bucket', '_gsum', '_gcount'], + 'info': ['_info'], + } + for metric in desc_func(): + result.append(metric.name) + for suffix in type_suffixes.get(metric.type, []): + result.append(metric.name + suffix) + return result + + def collect(self) -> Iterable[Metric]: + """Yields metrics from the collectors in the registry.""" + collectors = None + ti = None + with self._lock: + collectors = copy.copy(self._collector_to_names) + if self._target_info: + ti = self._target_info_metric() + if ti: + yield ti + for collector in collectors: + yield from collector.collect() + + def restricted_registry(self, names: Iterable[str]) -> "RestrictedRegistry": + """Returns object that only collects some metrics. + + Returns an object which upon collect() will return + only samples with the given names. + + Intended usage is: + generate_latest(REGISTRY.restricted_registry(['a_timeseries'])) + + Experimental.""" + names = set(names) + return RestrictedRegistry(names, self) + + def set_target_info(self, labels: Optional[Dict[str, str]]) -> None: + with self._lock: + if labels: + if not self._target_info and 'target_info' in self._names_to_collectors: + raise ValueError('CollectorRegistry already contains a target_info metric') + self._names_to_collectors['target_info'] = _EmptyCollector() + elif self._target_info: + self._names_to_collectors.pop('target_info', None) + self._target_info = labels + + def get_target_info(self) -> Optional[Dict[str, str]]: + with self._lock: + return self._target_info + + def _target_info_metric(self): + m = Metric('target', 'Target metadata', 'info') + m.add_sample('target_info', self._target_info, 1) + return m + + def get_sample_value(self, name: str, labels: Optional[Dict[str, str]] = None) -> Optional[float]: + """Returns the sample value, or None if not found. + + This is inefficient, and intended only for use in unittests. + """ + if labels is None: + labels = {} + for metric in self.collect(): + for s in metric.samples: + if s.name == name and s.labels == labels: + return s.value + return None + + +class RestrictedRegistry: + def __init__(self, names: Iterable[str], registry: CollectorRegistry): + self._name_set = set(names) + self._registry = registry + + def collect(self) -> Iterable[Metric]: + collectors = set() + target_info_metric = None + with self._registry._lock: + if 'target_info' in self._name_set and self._registry._target_info: + target_info_metric = self._registry._target_info_metric() + for name in self._name_set: + if name != 'target_info' and name in self._registry._names_to_collectors: + collectors.add(self._registry._names_to_collectors[name]) + if target_info_metric: + yield target_info_metric + for collector in collectors: + for metric in collector.collect(): + m = metric._restricted_metric(self._name_set) + if m: + yield m + + +REGISTRY = CollectorRegistry(auto_describe=True) diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/samples.py b/.venv/lib/python3.11/site-packages/prometheus_client/samples.py new file mode 100644 index 0000000000000000000000000000000000000000..53c472649193ad9bf46d73da229a5c004c7e6049 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/samples.py @@ -0,0 +1,53 @@ +from typing import Dict, NamedTuple, Optional, Union + + +class Timestamp: + """A nanosecond-resolution timestamp.""" + + def __init__(self, sec: float, nsec: float) -> None: + if nsec < 0 or nsec >= 1e9: + raise ValueError(f"Invalid value for nanoseconds in Timestamp: {nsec}") + if sec < 0: + nsec = -nsec + self.sec: int = int(sec) + self.nsec: int = int(nsec) + + def __str__(self) -> str: + return f"{self.sec}.{self.nsec:09d}" + + def __repr__(self) -> str: + return f"Timestamp({self.sec}, {self.nsec})" + + def __float__(self) -> float: + return float(self.sec) + float(self.nsec) / 1e9 + + def __eq__(self, other: object) -> bool: + return isinstance(other, Timestamp) and self.sec == other.sec and self.nsec == other.nsec + + def __ne__(self, other: object) -> bool: + return not self == other + + def __gt__(self, other: "Timestamp") -> bool: + return self.nsec > other.nsec if self.sec == other.sec else self.sec > other.sec + + def __lt__(self, other: "Timestamp") -> bool: + return self.nsec < other.nsec if self.sec == other.sec else self.sec < other.sec + + +# Timestamp and exemplar are optional. +# Value can be an int or a float. +# Timestamp can be a float containing a unixtime in seconds, +# a Timestamp object, or None. +# Exemplar can be an Exemplar object, or None. +class Exemplar(NamedTuple): + labels: Dict[str, str] + value: float + timestamp: Optional[Union[float, Timestamp]] = None + + +class Sample(NamedTuple): + name: str + labels: Dict[str, str] + value: float + timestamp: Optional[Union[float, Timestamp]] = None + exemplar: Optional[Exemplar] = None diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/utils.py b/.venv/lib/python3.11/site-packages/prometheus_client/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..0d2b09487c515e07bde1086b358861eefa2cb187 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/utils.py @@ -0,0 +1,24 @@ +import math + +INF = float("inf") +MINUS_INF = float("-inf") +NaN = float("NaN") + + +def floatToGoString(d): + d = float(d) + if d == INF: + return '+Inf' + elif d == MINUS_INF: + return '-Inf' + elif math.isnan(d): + return 'NaN' + else: + s = repr(d) + dot = s.find('.') + # Go switches to exponents sooner than Python. + # We only need to care about positive values for le/quantile. + if d > 0 and dot > 6: + mantissa = f'{s[0]}.{s[1:dot]}{s[dot + 1:]}'.rstrip('0.') + return f'{mantissa}e+0{dot - 1}' + return s diff --git a/.venv/lib/python3.11/site-packages/prometheus_client/values.py b/.venv/lib/python3.11/site-packages/prometheus_client/values.py new file mode 100644 index 0000000000000000000000000000000000000000..6ff85e3b4c95fa4fdca91f64b3e4bd044d94638a --- /dev/null +++ b/.venv/lib/python3.11/site-packages/prometheus_client/values.py @@ -0,0 +1,139 @@ +import os +from threading import Lock +import warnings + +from .mmap_dict import mmap_key, MmapedDict + + +class MutexValue: + """A float protected by a mutex.""" + + _multiprocess = False + + def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, **kwargs): + self._value = 0.0 + self._exemplar = None + self._lock = Lock() + + def inc(self, amount): + with self._lock: + self._value += amount + + def set(self, value, timestamp=None): + with self._lock: + self._value = value + + def set_exemplar(self, exemplar): + with self._lock: + self._exemplar = exemplar + + def get(self): + with self._lock: + return self._value + + def get_exemplar(self): + with self._lock: + return self._exemplar + + +def MultiProcessValue(process_identifier=os.getpid): + """Returns a MmapedValue class based on a process_identifier function. + + The 'process_identifier' function MUST comply with this simple rule: + when called in simultaneously running processes it MUST return distinct values. + + Using a different function than the default 'os.getpid' is at your own risk. + """ + files = {} + values = [] + pid = {'value': process_identifier()} + # Use a single global lock when in multi-processing mode + # as we presume this means there is no threading going on. + # This avoids the need to also have mutexes in __MmapDict. + lock = Lock() + + class MmapedValue: + """A float protected by a mutex backed by a per-process mmaped file.""" + + _multiprocess = True + + def __init__(self, typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode='', **kwargs): + self._params = typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode + # This deprecation warning can go away in a few releases when removing the compatibility + if 'prometheus_multiproc_dir' in os.environ and 'PROMETHEUS_MULTIPROC_DIR' not in os.environ: + os.environ['PROMETHEUS_MULTIPROC_DIR'] = os.environ['prometheus_multiproc_dir'] + warnings.warn("prometheus_multiproc_dir variable has been deprecated in favor of the upper case naming PROMETHEUS_MULTIPROC_DIR", DeprecationWarning) + with lock: + self.__check_for_pid_change() + self.__reset() + values.append(self) + + def __reset(self): + typ, metric_name, name, labelnames, labelvalues, help_text, multiprocess_mode = self._params + if typ == 'gauge': + file_prefix = typ + '_' + multiprocess_mode + else: + file_prefix = typ + if file_prefix not in files: + filename = os.path.join( + os.environ.get('PROMETHEUS_MULTIPROC_DIR'), + '{}_{}.db'.format(file_prefix, pid['value'])) + + files[file_prefix] = MmapedDict(filename) + self._file = files[file_prefix] + self._key = mmap_key(metric_name, name, labelnames, labelvalues, help_text) + self._value, self._timestamp = self._file.read_value(self._key) + + def __check_for_pid_change(self): + actual_pid = process_identifier() + if pid['value'] != actual_pid: + pid['value'] = actual_pid + # There has been a fork(), reset all the values. + for f in files.values(): + f.close() + files.clear() + for value in values: + value.__reset() + + def inc(self, amount): + with lock: + self.__check_for_pid_change() + self._value += amount + self._timestamp = 0.0 + self._file.write_value(self._key, self._value, self._timestamp) + + def set(self, value, timestamp=None): + with lock: + self.__check_for_pid_change() + self._value = value + self._timestamp = timestamp or 0.0 + self._file.write_value(self._key, self._value, self._timestamp) + + def set_exemplar(self, exemplar): + # TODO: Implement exemplars for multiprocess mode. + return + + def get(self): + with lock: + self.__check_for_pid_change() + return self._value + + def get_exemplar(self): + # TODO: Implement exemplars for multiprocess mode. + return None + + return MmapedValue + + +def get_value_class(): + # Should we enable multi-process mode? + # This needs to be chosen before the first metric is constructed, + # and as that may be in some arbitrary library the user/admin has + # no control over we use an environment variable. + if 'prometheus_multiproc_dir' in os.environ or 'PROMETHEUS_MULTIPROC_DIR' in os.environ: + return MultiProcessValue() + else: + return MutexValue + + +ValueClass = get_value_class() diff --git a/.venv/lib/python3.11/site-packages/rpds/__init__.py b/.venv/lib/python3.11/site-packages/rpds/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..257da6a7bd439c46cb2409e77531dc4a4dc6295c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/rpds/__init__.py @@ -0,0 +1,5 @@ +from .rpds import * + +__doc__ = rpds.__doc__ +if hasattr(rpds, "__all__"): + __all__ = rpds.__all__ \ No newline at end of file diff --git a/.venv/lib/python3.11/site-packages/rpds/__init__.pyi b/.venv/lib/python3.11/site-packages/rpds/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..5af0e323c1d6d4182d0d373e216a86e520affc87 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/rpds/__init__.pyi @@ -0,0 +1,77 @@ +from typing import ( + ItemsView, + Iterable, + Iterator, + KeysView, + Mapping, + TypeVar, + ValuesView, +) + +_T = TypeVar("_T") +_KT_co = TypeVar("_KT_co", covariant=True) +_VT_co = TypeVar("_VT_co", covariant=True) +_KU_co = TypeVar("_KU_co", covariant=True) +_VU_co = TypeVar("_VU_co", covariant=True) + +class HashTrieMap(Mapping[_KT_co, _VT_co]): + def __init__( + self, + value: Mapping[_KT_co, _VT_co] | Iterable[tuple[_KT_co, _VT_co]] = {}, + **kwds: Mapping[_KT_co, _VT_co], + ): ... + def __getitem__(self, key: _KT_co) -> _VT_co: ... + def __iter__(self) -> Iterator[_KT_co]: ... + def __len__(self) -> int: ... + def discard(self, key: _KT_co) -> HashTrieMap[_KT_co, _VT_co]: ... + def items(self) -> ItemsView[_KT_co, _VT_co]: ... + def keys(self) -> KeysView[_KT_co]: ... + def values(self) -> ValuesView[_VT_co]: ... + def remove(self, key: _KT_co) -> HashTrieMap[_KT_co, _VT_co]: ... + def insert( + self, + key: _KT_co, + val: _VT_co, + ) -> HashTrieMap[_KT_co, _VT_co]: ... + def update( + self, + *args: Mapping[_KU_co, _VU_co] | Iterable[tuple[_KU_co, _VU_co]], + ) -> HashTrieMap[_KT_co | _KU_co, _VT_co | _VU_co]: ... + @classmethod + def convert( + cls, + value: Mapping[_KT_co, _VT_co] | Iterable[tuple[_KT_co, _VT_co]], + ) -> HashTrieMap[_KT_co, _VT_co]: ... + @classmethod + def fromkeys( + cls, + keys: Iterable[_KT_co], + value: _VT_co = None, + ) -> HashTrieMap[_KT_co, _VT_co]: ... + +class HashTrieSet(frozenset[_T]): + def __init__(self, value: Iterable[_T] = ()): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def discard(self, value: _T) -> HashTrieSet[_T]: ... + def remove(self, value: _T) -> HashTrieSet[_T]: ... + def insert(self, value: _T) -> HashTrieSet[_T]: ... + def update(self, *args: Iterable[_T]) -> HashTrieSet[_T]: ... + +class List(Iterable[_T]): + def __init__(self, value: Iterable[_T] = (), *more: _T): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def push_front(self, value: _T) -> List[_T]: ... + def drop_first(self) -> List[_T]: ... + +class Queue(Iterable[_T]): + def __init__(self, value: Iterable[_T] = (), *more: _T): ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def enqueue(self, value: _T) -> Queue[_T]: ... + def dequeue(self, value: _T) -> Queue[_T]: ... + @property + def is_empty(self) -> _T: ... + @property + def peek(self) -> _T: ... diff --git a/.venv/lib/python3.11/site-packages/rpds/__pycache__/__init__.cpython-311.pyc b/.venv/lib/python3.11/site-packages/rpds/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57ffef1996f0c0dd828e71b2eb118e9d31ab695f Binary files /dev/null and b/.venv/lib/python3.11/site-packages/rpds/__pycache__/__init__.cpython-311.pyc differ diff --git a/.venv/lib/python3.11/site-packages/rpds/py.typed b/.venv/lib/python3.11/site-packages/rpds/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/INSTALLER b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/LICENSE b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f26bcf4d2de6eb136e31006ca3ab447d5e488adf --- /dev/null +++ b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/METADATA b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..f15e2b387737b14e013a83b32a766fe4eb0da611 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/METADATA @@ -0,0 +1,67 @@ +Metadata-Version: 2.1 +Name: typing_extensions +Version: 4.12.2 +Summary: Backported and Experimental Type Hints for Python 3.8+ +Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing +Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development +Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues +Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md +Project-URL: Documentation, https://typing-extensions.readthedocs.io/ +Project-URL: Home, https://github.com/python/typing_extensions +Project-URL: Q & A, https://github.com/python/typing/discussions +Project-URL: Repository, https://github.com/python/typing_extensions + +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) – +[PyPI](https://pypi.org/project/typing-extensions/) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +`typing_extensions` is treated specially by static type checkers such as +mypy and pyright. Objects defined in `typing_extensions` are treated the same +way as equivalent forms in `typing`. + +`typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version will be incremented only for backwards-incompatible changes. +Therefore, it's safe to depend +on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`, +where `x.y` is the first version that includes all features you need. + +## Included items + +See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a +complete listing of module contents. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md) +for how to contribute to `typing_extensions`. + diff --git a/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/RECORD b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..ab12483f8479227a027c35f26089d38ca462e95c --- /dev/null +++ b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/RECORD @@ -0,0 +1,7 @@ +__pycache__/typing_extensions.cpython-311.pyc,, +typing_extensions-4.12.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +typing_extensions-4.12.2.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936 +typing_extensions-4.12.2.dist-info/METADATA,sha256=BeUQIa8cnYbrjWx-N8TOznM9UGW5Gm2DicVpDtRA8W0,3018 +typing_extensions-4.12.2.dist-info/RECORD,, +typing_extensions-4.12.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +typing_extensions.py,sha256=gwekpyG9DVG3lxWKX4ni8u7nk3We5slG98mA9F3DJQw,134451 diff --git a/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/WHEEL b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..3b5e64b5e6c4a210201d1676a891fd57b15cda99 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/typing_extensions-4.12.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/.venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so b/.venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..ba47dcdfeee293029517c02ddac04d497b109b08 --- /dev/null +++ b/.venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6046d24c1b9b9a10d42c21ca46d1af8f6a5b4f2c134ca9952f63ec0a3e9d7fb +size 197248