ext/Python/errors.cpp (643 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include <unistd.h> #include <cerrno> #include <cstdarg> #include "cpython-data.h" #include "cpython-func.h" #include "api-handle.h" #include "dict-builtins.h" #include "exception-builtins.h" #include "fileutils.h" #include "runtime.h" #include "sys-module.h" #include "thread.h" #include "traceback-builtins.h" #include "type-builtins.h" namespace py { PY_EXPORT void PyErr_SetString(PyObject* exc, const char* msg) { PyObject* value = PyUnicode_FromString(msg); PyErr_SetObject(exc, value); Py_XDECREF(value); } PY_EXPORT PyObject* PyErr_Occurred() { Thread* thread = Thread::current(); if (!thread->hasPendingException()) { return nullptr; } return ApiHandle::borrowedReference(thread->runtime(), thread->pendingExceptionType()); } PY_EXPORT PyObject* PyErr_Format(PyObject* exception, const char* format, ...) { va_list vargs; va_start(vargs, format); PyErr_FormatV(exception, format, vargs); va_end(vargs); return nullptr; } PY_EXPORT void PyErr_Clear() { Thread::current()->clearPendingException(); } PY_EXPORT int PyErr_BadArgument() { Thread* thread = Thread::current(); thread->raiseBadArgument(); return 0; } PY_EXPORT PyObject* PyErr_NoMemory() { Thread* thread = Thread::current(); thread->raiseMemoryError(); return nullptr; } PY_EXPORT PyObject* _PyErr_FormatFromCause(PyObject* exception, const char* format, ...) { CHECK(PyErr_Occurred(), "_PyErr_FormatFromCause must be called with an exception set"); PyObject* exc = nullptr; PyObject* val = nullptr; PyObject* tb = nullptr; PyErr_Fetch(&exc, &val, &tb); PyErr_NormalizeException(&exc, &val, &tb); if (tb != nullptr) { PyException_SetTraceback(val, tb); Py_DECREF(tb); } Py_DECREF(exc); DCHECK(!PyErr_Occurred(), "error must not have occurred"); va_list vargs; va_start(vargs, format); PyErr_FormatV(exception, format, vargs); va_end(vargs); PyObject* val2 = nullptr; PyErr_Fetch(&exc, &val2, &tb); PyErr_NormalizeException(&exc, &val2, &tb); Py_INCREF(val); PyException_SetCause(val2, val); PyException_SetContext(val2, val); PyErr_Restore(exc, val2, tb); return nullptr; } // Remove the preprocessor macro for PyErr_BadInternalCall() so that we can // export the entry point for existing object code: #pragma push_macro("PyErr_BadInternalCall") #undef PyErr_BadInternalCall PY_EXPORT void PyErr_BadInternalCall() { Thread* thread = Thread::current(); thread->raiseBadInternalCall(); } #pragma pop_macro("PyErr_BadInternalCall") PY_EXPORT int PyErr_ExceptionMatches(PyObject* exc) { return PyErr_GivenExceptionMatches(PyErr_Occurred(), exc); } PY_EXPORT void PyErr_Fetch(PyObject** pexc, PyObject** pval, PyObject** ptb) { Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); DCHECK(pexc != nullptr, "pexc is null"); if (thread->pendingExceptionType().isNoneType()) { *pexc = nullptr; } else { *pexc = ApiHandle::newReference(runtime, thread->pendingExceptionType()); } DCHECK(pval != nullptr, "pval is null"); if (thread->pendingExceptionValue().isNoneType()) { *pval = nullptr; } else { *pval = ApiHandle::newReference(runtime, thread->pendingExceptionValue()); } DCHECK(ptb != nullptr, "ptb is null"); if (thread->pendingExceptionTraceback().isNoneType()) { *ptb = nullptr; } else { *ptb = ApiHandle::newReference(runtime, thread->pendingExceptionTraceback()); } thread->clearPendingException(); } PY_EXPORT PyObject* PyErr_FormatV(PyObject* exception, const char* format, va_list vargs) { PyErr_Clear(); // Cannot call PyUnicode_FromFormatV with an exception set PyObject* string = PyUnicode_FromFormatV(format, vargs); PyErr_SetObject(exception, string); Py_XDECREF(string); return nullptr; } PY_EXPORT void PyErr_GetExcInfo(PyObject** p_type, PyObject** p_value, PyObject** p_traceback) { Thread* thread = Thread::current(); HandleScope scope(thread); Object caught_exc_state_obj(&scope, thread->topmostCaughtExceptionState()); if (caught_exc_state_obj.isNoneType()) { *p_type = nullptr; *p_value = nullptr; *p_traceback = nullptr; return; } Runtime* runtime = thread->runtime(); ExceptionState caught_exc_state(&scope, *caught_exc_state_obj); *p_type = ApiHandle::newReference(runtime, caught_exc_state.type()); *p_value = ApiHandle::newReference(runtime, caught_exc_state.value()); *p_traceback = ApiHandle::newReference(runtime, caught_exc_state.traceback()); } PY_EXPORT int PyErr_GivenExceptionMatches(PyObject* given, PyObject* exc) { if (given == nullptr || exc == nullptr) { return 0; } Thread* thread = Thread::current(); HandleScope scope(thread); Object given_obj(&scope, ApiHandle::fromPyObject(given)->asObject()); Object exc_obj(&scope, ApiHandle::fromPyObject(exc)->asObject()); return givenExceptionMatches(thread, given_obj, exc_obj) ? 1 : 0; } PY_EXPORT PyObject* PyErr_NewException(const char* name, PyObject* base_or_null, PyObject* dict_or_null) { Thread* thread = Thread::current(); const char* dot = std::strrchr(name, '.'); if (dot == nullptr) { thread->raiseWithFmt(LayoutId::kSystemError, "PyErr_NewException: name must be module.class"); return nullptr; } Runtime* runtime = thread->runtime(); HandleScope scope(thread); word mod_name_len = dot - name; Object mod_name(&scope, runtime->newStrWithAll( {reinterpret_cast<const byte*>(name), mod_name_len})); Object exc_name(&scope, runtime->newStrFromCStr(dot + 1)); Object base(&scope, base_or_null == nullptr ? runtime->typeAt(LayoutId::kException) : ApiHandle::fromPyObject(base_or_null)->asObject()); Object dict(&scope, dict_or_null == nullptr ? runtime->newDict() : ApiHandle::fromPyObject(dict_or_null)->asObject()); Object type(&scope, thread->invokeFunction4(ID(builtins), ID(_exception_new), mod_name, exc_name, base, dict)); if (type.isError()) { DCHECK(!type.isErrorNotFound(), "missing _exception_new"); return nullptr; } return ApiHandle::newReference(runtime, *type); } PY_EXPORT PyObject* PyErr_NewExceptionWithDoc(const char* name, const char* doc, PyObject* base_or_null, PyObject* dict_or_null) { Thread* thread = Thread::current(); HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object dict_obj(&scope, dict_or_null == nullptr ? runtime->newDict() : ApiHandle::fromPyObject(dict_or_null)->asObject()); if (doc != nullptr) { if (!runtime->isInstanceOfDict(*dict_obj)) { thread->raiseBadInternalCall(); return nullptr; } Dict dict(&scope, *dict_obj); Object doc_str(&scope, runtime->newStrFromCStr(doc)); dictAtPutById(thread, dict, ID(__doc__), doc_str); } const char* dot = std::strrchr(name, '.'); if (dot == nullptr) { thread->raiseWithFmt(LayoutId::kSystemError, "PyErr_NewException: name must be module.class"); return nullptr; } word mod_name_len = dot - name; Object mod_name(&scope, runtime->newStrWithAll( {reinterpret_cast<const byte*>(name), mod_name_len})); Object exc_name(&scope, runtime->newStrFromCStr(dot + 1)); Object base(&scope, base_or_null == nullptr ? runtime->typeAt(LayoutId::kException) : ApiHandle::fromPyObject(base_or_null)->asObject()); Object type(&scope, thread->invokeFunction4(ID(builtins), ID(_exception_new), mod_name, exc_name, base, dict_obj)); if (type.isError()) { DCHECK(!type.isErrorNotFound(), "missing _exception_new"); return nullptr; } return ApiHandle::newReference(runtime, *type); } PY_EXPORT void PyErr_NormalizeException(PyObject** exc, PyObject** val, PyObject** tb) { Thread* thread = Thread::current(); HandleScope scope(thread); Object exc_obj(&scope, *exc ? ApiHandle::fromPyObject(*exc)->asObject() : NoneType::object()); Object exc_orig(&scope, *exc_obj); Object val_obj(&scope, *val ? ApiHandle::fromPyObject(*val)->asObject() : NoneType::object()); Object val_orig(&scope, *val_obj); Object tb_obj(&scope, *tb ? ApiHandle::fromPyObject(*tb)->asObject() : NoneType::object()); Object tb_orig(&scope, *tb_obj); normalizeException(thread, &exc_obj, &val_obj, &tb_obj); Runtime* runtime = thread->runtime(); if (*exc_obj != *exc_orig) { PyObject* tmp = *exc; *exc = ApiHandle::newReference(runtime, *exc_obj); Py_XDECREF(tmp); } if (*val_obj != *val_orig) { PyObject* tmp = *val; *val = ApiHandle::newReference(runtime, *val_obj); Py_XDECREF(tmp); } if (*tb_obj != *tb_orig) { PyObject* tmp = *tb; *tb = ApiHandle::newReference(runtime, *tb_obj); Py_XDECREF(tmp); } } PY_EXPORT PyObject* PyErr_ProgramText(const char* /* e */, int /* o */) { UNIMPLEMENTED("PyErr_ProgramText"); } PY_EXPORT PyObject* PyErr_SetExcFromWindowsErr(PyObject* /* c */, int /* r */) { UNIMPLEMENTED("PyErr_SetExcFromWindowsErr"); } PY_EXPORT PyObject* PyErr_SetExcFromWindowsErrWithFilename( PyObject* /* c */, int /* r */, const char* /* e */) { UNIMPLEMENTED("PyErr_SetExcFromWindowsErrWithFilename"); } PY_EXPORT PyObject* PyErr_SetExcFromWindowsErrWithFilenameObject( PyObject* /* c */, int /* r */, PyObject* /* t */) { UNIMPLEMENTED("PyErr_SetExcFromWindowsErrWithFilenameObject"); } PY_EXPORT PyObject* PyErr_SetExcFromWindowsErrWithFilenameObjects( PyObject* /* c */, int /* r */, PyObject* /* t */, PyObject* /* 2 */) { UNIMPLEMENTED("PyErr_SetExcFromWindowsErrWithFilenameObjects"); } PY_EXPORT void PyErr_SetExcInfo(PyObject* type, PyObject* value, PyObject* traceback) { Thread* thread = Thread::current(); HandleScope scope(thread); Object type_obj(&scope, type == nullptr ? NoneType::object() : ApiHandle::stealReference(type)); thread->setCaughtExceptionType(*type_obj); Object value_obj(&scope, value == nullptr ? NoneType::object() : ApiHandle::stealReference(value)); thread->setCaughtExceptionValue(*value_obj); Object traceback_obj(&scope, traceback == nullptr ? NoneType::object() : ApiHandle::stealReference(traceback)); thread->setCaughtExceptionTraceback(*traceback_obj); } PY_EXPORT PyObject* PyErr_SetFromErrno(PyObject* type) { int errno_value = errno; Thread* thread = Thread::current(); HandleScope scope(thread); Object type_obj(&scope, ApiHandle::fromPyObject(type)->asObject()); Object none(&scope, NoneType::object()); thread->raiseFromErrnoWithFilenames(type_obj, errno_value, none, none); return nullptr; } PY_EXPORT PyObject* PyErr_SetFromErrnoWithFilename(PyObject* type, const char* filename) { int errno_value = errno; Thread* thread = Thread::current(); HandleScope scope(thread); Object type_obj(&scope, ApiHandle::fromPyObject(type)->asObject()); Object filename_obj(&scope, thread->runtime()->newStrFromCStr(filename)); Object none(&scope, NoneType::object()); thread->raiseFromErrnoWithFilenames(type_obj, errno_value, filename_obj, none); return nullptr; } PY_EXPORT PyObject* PyErr_SetFromErrnoWithFilenameObject(PyObject* type, PyObject* filename) { int errno_value = errno; Thread* thread = Thread::current(); HandleScope scope(thread); Object type_obj(&scope, ApiHandle::fromPyObject(type)->asObject()); Object filename_obj(&scope, ApiHandle::fromPyObject(filename)->asObject()); Object none(&scope, NoneType::object()); thread->raiseFromErrnoWithFilenames(type_obj, errno_value, filename_obj, none); return nullptr; } PY_EXPORT PyObject* PyErr_SetFromErrnoWithFilenameObjects(PyObject* type, PyObject* filename0, PyObject* filename1) { int errno_value = errno; Thread* thread = Thread::current(); HandleScope scope(thread); Object type_obj(&scope, ApiHandle::fromPyObject(type)->asObject()); Object filename0_obj(&scope, ApiHandle::fromPyObject(filename0)->asObject()); Object filename1_obj(&scope, ApiHandle::fromPyObject(filename1)->asObject()); thread->raiseFromErrnoWithFilenames(type_obj, errno_value, filename0_obj, filename1_obj); return nullptr; } PY_EXPORT PyObject* PyErr_SetFromWindowsErr(int /* r */) { UNIMPLEMENTED("PyErr_SetFromWindowsErr"); } PY_EXPORT PyObject* PyErr_SetFromWindowsErrWithFilename(int /* r */, const char* /* e */) { UNIMPLEMENTED("PyErr_SetFromWindowsErrWithFilename"); } PY_EXPORT PyObject* PyErr_SetImportError(PyObject* /* g */, PyObject* /* e */, PyObject* /* h */) { UNIMPLEMENTED("PyErr_SetImportError"); } PY_EXPORT PyObject* PyErr_SetImportErrorSubclass(PyObject* /* n */, PyObject* /* g */, PyObject* /* e */, PyObject* /* h */) { UNIMPLEMENTED("PyErr_SetImportErrorSubclass"); } PY_EXPORT void PyErr_SetNone(PyObject* type) { PyErr_SetObject(type, nullptr); } PY_EXPORT void PyErr_SetObject(PyObject* exc, PyObject* val) { Thread* thread = Thread::current(); if (exc == nullptr) { DCHECK(val == nullptr, "nullptr exc with non-nullptr val"); thread->clearPendingException(); return; } HandleScope scope(thread); Object exc_obj(&scope, ApiHandle::fromPyObject(exc)->asObject()); Runtime* runtime = thread->runtime(); if (!runtime->isInstanceOfType(*exc_obj) || !Type(&scope, *exc_obj).isBaseExceptionSubclass()) { Object exc_repr(&scope, thread->invokeFunction1(ID(builtins), ID(repr), exc_obj)); if (exc_repr.isErrorException()) return; thread->raiseWithFmt(LayoutId::kSystemError, "exception %S not a BaseException subclass", &exc_repr); return; } Type exc_type(&scope, *exc_obj); Object val_obj(&scope, val == nullptr ? NoneType::object() : ApiHandle::fromPyObject(val)->asObject()); val_obj = thread->chainExceptionContext(exc_type, val_obj); if (val_obj.isError()) return; thread->setPendingExceptionType(*exc_obj); thread->setPendingExceptionValue(*val_obj); if (runtime->isInstanceOfBaseException(*val_obj)) { BaseException val_exc(&scope, *val_obj); thread->setPendingExceptionTraceback(val_exc.traceback()); } } PY_EXPORT void PyErr_SyntaxLocation(const char* /* e */, int /* o */) { UNIMPLEMENTED("PyErr_SyntaxLocation"); } PY_EXPORT void PyErr_SyntaxLocationEx(const char* filename, int lineno, int col_offset) { PyObject* fileobj; if (filename != nullptr) { fileobj = PyUnicode_DecodeFSDefault(filename); if (fileobj == nullptr) { PyErr_Clear(); } } else { fileobj = nullptr; } PyErr_SyntaxLocationObject(fileobj, lineno, col_offset); Py_XDECREF(fileobj); } PY_EXPORT void PyErr_SyntaxLocationObject(PyObject* filename, int lineno, int col_offset) { PyObject *exc, *val, *tb; // Add attributes for the line number and filename for the error PyErr_Fetch(&exc, &val, &tb); PyErr_NormalizeException(&exc, &val, &tb); // XXX check that it is, indeed, a syntax error. It might not be, though. PyObject* lineno_obj = PyLong_FromLong(lineno); if (lineno_obj == nullptr) { PyErr_Clear(); } else { if (PyObject_SetAttrString(val, "lineno", lineno_obj)) { PyErr_Clear(); } Py_DECREF(lineno_obj); } PyObject* col_obj = nullptr; if (col_offset >= 0) { col_obj = PyLong_FromLong(col_offset); if (col_obj == nullptr) { PyErr_Clear(); } } if (PyObject_SetAttrString(val, "offset", col_obj ? col_obj : Py_None)) { PyErr_Clear(); } Py_XDECREF(col_obj); if (filename != nullptr) { if (PyObject_SetAttrString(val, "filename", filename)) { PyErr_Clear(); } PyObject* text_obj = PyErr_ProgramTextObject(filename, lineno); if (text_obj) { if (PyObject_SetAttrString(val, "text", text_obj)) { PyErr_Clear(); } Py_DECREF(text_obj); } } if (exc != PyExc_SyntaxError) { if (!PyObject_HasAttrString(val, "msg")) { PyObject* msg_obj = PyObject_Str(val); if (msg_obj) { if (PyObject_SetAttrString(val, "msg", msg_obj)) { PyErr_Clear(); } Py_DECREF(msg_obj); } else { PyErr_Clear(); } } if (!PyObject_HasAttrString(val, "print_file_and_line")) { if (PyObject_SetAttrString(val, "print_file_and_line", Py_None)) { PyErr_Clear(); } } } PyErr_Restore(exc, val, tb); } static RawObject fileWriteObjectStrUnraisable(Thread* thread, const Object& file, const Object& obj) { HandleScope scope(thread); Object obj_str(&scope, thread->invokeFunction1(ID(builtins), ID(str), obj)); if (obj_str.isError()) { thread->clearPendingException(); return *obj_str; } RawObject result = thread->invokeMethod2(file, ID(write), obj_str); thread->clearPendingException(); return result; } static RawObject fileWriteObjectReprUnraisable(Thread* thread, const Object& file, const Object& obj) { HandleScope scope(thread); Object obj_repr(&scope, thread->invokeFunction1(ID(builtins), ID(repr), obj)); if (obj_repr.isError()) { thread->clearPendingException(); return *obj_repr; } RawObject result = thread->invokeMethod2(file, ID(write), obj_repr); thread->clearPendingException(); return result; } static RawObject fileWriteCStrUnraisable(Thread* thread, const Object& file, const char* c_str) { HandleScope scope(thread); Object str(&scope, thread->runtime()->newStrFromFmt(c_str)); RawObject result = thread->invokeMethod2(file, ID(write), str); thread->clearPendingException(); return result; } PY_EXPORT void PyErr_WriteUnraisable(PyObject* obj) { Thread* thread = Thread::current(); HandleScope scope(thread); Object exc(&scope, thread->pendingExceptionType()); Object val(&scope, thread->pendingExceptionValue()); Object tb(&scope, thread->pendingExceptionTraceback()); thread->clearPendingException(); Runtime* runtime = thread->runtime(); Object sys_stderr(&scope, runtime->lookupNameInModule(thread, ID(sys), ID(stderr))); if (obj != nullptr) { if (fileWriteCStrUnraisable(thread, sys_stderr, "Exception ignored in: ") .isError()) { return; } Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); if (fileWriteObjectReprUnraisable(thread, sys_stderr, object).isError()) { if (fileWriteCStrUnraisable(thread, sys_stderr, "<object repr() failed>") .isError()) { return; } } if (fileWriteCStrUnraisable(thread, sys_stderr, "\n").isError()) { return; } } if (tb.isTraceback()) { Traceback traceback(&scope, *tb); Object err(&scope, tracebackWrite(thread, traceback, sys_stderr)); DCHECK(!err.isErrorException(), "failed to write traceback"); } if (exc.isNoneType()) { thread->clearPendingException(); return; } DCHECK(runtime->isInstanceOfType(*exc), "exc must be a type"); Type exc_type(&scope, *exc); DCHECK(exc_type.isBaseExceptionSubclass(), "exc must be a subclass of BaseException"); // TODO(T42602623): If exc_type.name() is None, Remove dotted components of // name, eg A.B.C => C Object module_name_obj( &scope, runtime->attributeAtById(thread, exc_type, ID(__module__))); if (!runtime->isInstanceOfStr(*module_name_obj)) { thread->clearPendingException(); if (fileWriteCStrUnraisable(thread, sys_stderr, "<unknown>").isError()) { return; } } else { Str module_name(&scope, *module_name_obj); if (!module_name.equalsCStr("builtins")) { if (fileWriteObjectStrUnraisable(thread, sys_stderr, module_name) .isError()) { return; } if (fileWriteCStrUnraisable(thread, sys_stderr, ".").isError()) { return; } } } if (exc_type.name().isNoneType()) { if (fileWriteCStrUnraisable(thread, sys_stderr, "<unknown>").isError()) { return; } } else { Str exc_type_name(&scope, exc_type.name()); if (fileWriteObjectStrUnraisable(thread, sys_stderr, exc_type_name) .isError()) { return; } } if (!val.isNoneType()) { if (fileWriteCStrUnraisable(thread, sys_stderr, ": ").isError()) { return; } if (fileWriteObjectStrUnraisable(thread, sys_stderr, val).isError()) { if (fileWriteCStrUnraisable(thread, sys_stderr, "<exception str() failed>") .isError()) { return; } } } if (fileWriteCStrUnraisable(thread, sys_stderr, "\n").isError()) { return; } } PY_EXPORT void _PyErr_BadInternalCall(const char* filename, int lineno) { Thread::current()->raiseWithFmt(LayoutId::kSystemError, "%s:%d: bad argument to internal function", filename, lineno); } PY_EXPORT PyObject* PyErr_ProgramTextObject(PyObject* filename, int lineno) { if (filename == nullptr || lineno <= 0) { return nullptr; } Thread* thread = Thread::current(); HandleScope scope(thread); Object filename_obj(&scope, ApiHandle::fromPyObject(filename)->asObject()); Object lineno_obj(&scope, SmallInt::fromWord(lineno)); Object result(&scope, thread->invokeFunction2(ID(builtins), ID(_err_program_text), filename_obj, lineno_obj)); if (result.isErrorException()) { thread->clearPendingException(); return nullptr; } if (result == Str::empty()) { return nullptr; } return ApiHandle::newReference(thread->runtime(), *result); } PY_EXPORT void PyErr_Restore(PyObject* type, PyObject* value, PyObject* traceback) { Thread* thread = Thread::current(); if (type == nullptr) { thread->setPendingExceptionType(NoneType::object()); } else { thread->setPendingExceptionType(ApiHandle::fromPyObject(type)->asObject()); // This is a stolen reference, decrement the reference count ApiHandle::fromPyObject(type)->decref(); } if (value == nullptr) { thread->setPendingExceptionValue(NoneType::object()); } else { thread->setPendingExceptionValue( ApiHandle::fromPyObject(value)->asObject()); // This is a stolen reference, decrement the reference count ApiHandle::fromPyObject(value)->decref(); } if (traceback != nullptr && !ApiHandle::fromPyObject(traceback)->asObject().isTraceback()) { ApiHandle::fromPyObject(traceback)->decref(); // Can only store traceback instances as the traceback traceback = nullptr; } if (traceback == nullptr) { thread->setPendingExceptionTraceback(NoneType::object()); } else { thread->setPendingExceptionTraceback( ApiHandle::fromPyObject(traceback)->asObject()); // This is a stolen reference, decrement the reference count ApiHandle::fromPyObject(traceback)->decref(); } } // Like PyErr_Restore(), but if an exception is already set, set the context // associated with it. PY_EXPORT void _PyErr_ChainExceptions(PyObject* exc, PyObject* val, PyObject* tb) { if (exc == nullptr) return; if (PyErr_Occurred()) { PyObject *exc2, *val2, *tb2; PyErr_Fetch(&exc2, &val2, &tb2); PyErr_NormalizeException(&exc, &val, &tb); if (tb != nullptr) { PyException_SetTraceback(val, tb); Py_DECREF(tb); } Py_DECREF(exc); PyErr_NormalizeException(&exc2, &val2, &tb2); PyException_SetContext(val2, val); PyErr_Restore(exc2, val2, tb2); } else { PyErr_Restore(exc, val, tb); } } } // namespace py