##// END OF EJS Templates
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10...
typing: add minimal annotations to cmd_impls/graft.py to pytype with 3.10 I'm not sure why the same version of pytype passed in CI with Python 3.11. What's failing on 3.10 is related to `statedata`, which is keyed on bytes, but has various value types. It looks like these several types are treated as a union when run with 3.10, and then all of them need to have the same attributes. This will take awhile to untangle, because `TypedDict` requires str keys, so we'll either have to change the keys (and whoever calls this), or migrate to a class with typed fields (and change all of the callers). There are some changes to this module currently in-flight, so I'm opting for the minimal changes here to minimally affect that, while keeping my ability to run pytype locally and track the changes. It's worth pointing out that I'm starting to use py3.9 type hints here, i.e. `Foo | None` instead of `Optional[Foo]`. That's fine even with py3.8 support because of the `from __future__ import annotations`, which delays evaluation. We already don't support pytype checking with all of the runtime supported versions of Python since at least 0851d94bfdaa, with the `ByteString` usage. The errors at the start of this series were: File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 238, in _graft_revisions: No attribute 'get' on bool [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 238, in _graft_revisions: No attribute 'get' on bytes [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 239, in _graft_revisions: No attribute 'get' on bool [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 239, in _graft_revisions: No attribute 'get' on bytes [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 241, in _graft_revisions: No attribute 'get' on bool [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 241, in _graft_revisions: No attribute 'get' on bytes [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 260, in _graft_revisions: unsupported operand type(s) for item assignment: bool [unsupported-operands] No attribute '__setitem__' on bool Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 260, in _graft_revisions: unsupported operand type(s) for item assignment: bytes [unsupported-operands] No attribute '__setitem__' on bytes Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 270, in _graft_revisions: No attribute 'get' on bool [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 270, in _graft_revisions: No attribute 'get' on bytes [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 280, in _graft_revisions: No attribute 'get' on bool [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft File "/mnt/c/Users/Matt/hg/mercurial/cmd_impls/graft.py", line 280, in _graft_revisions: No attribute 'get' on bytes [attribute-error] In Union[Any, Callable, Dict[bytes, Optional[Any]], bool, bytes, dict] Called from (traceback): line 21, in cmd_graft

File last commit:

r53110:96b113d2 stable
r53248:9042ffea default
Show More
utils.rs
144 lines | 4.7 KiB | application/rls-services+xml | RustLexer
use cpython::exc::{KeyboardInterrupt, ValueError};
use cpython::{
ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyObject, PyResult,
PyTuple, Python, ToPyObject,
};
use hg::config::Config;
use hg::errors::HgError;
use hg::repo::{Repo, RepoError};
use hg::revlog::Node;
use hg::utils::files::get_path_from_bytes;
use crate::exceptions::FallbackError;
#[allow(unused)]
pub fn print_python_trace(py: Python) -> PyResult<PyObject> {
eprintln!("===============================");
eprintln!("Printing Python stack from Rust");
eprintln!("===============================");
let traceback = py.import("traceback")?;
let sys = py.import("sys")?;
let kwargs = PyDict::new(py);
kwargs.set_item(py, "file", sys.get(py, "stderr")?)?;
traceback.call(py, "print_stack", PyTuple::new(py, &[]), Some(&kwargs))
}
pub fn hgerror_to_pyerr<T>(
py: Python,
error: Result<T, HgError>,
) -> PyResult<T> {
error.map_err(|e| match e {
HgError::IoError { .. } => {
PyErr::new::<cpython::exc::IOError, _>(py, e.to_string())
}
HgError::UnsupportedFeature(e) => {
let as_string = e.to_string();
log::trace!("Update from null fallback: {}", as_string);
PyErr::new::<FallbackError, _>(py, &as_string)
}
HgError::RaceDetected(_) => {
unreachable!("must not surface to the user")
}
HgError::Path(path_error) => {
let msg = PyBytes::new(py, path_error.to_string().as_bytes());
let cls = py
.import("mercurial.error")
.and_then(|m| m.get(py, "InputError"))
.unwrap();
PyErr::from_instance(
py,
cls.call(py, (msg,), None).ok().into_py_object(py),
)
}
HgError::InterruptReceived => {
PyErr::new::<KeyboardInterrupt, _>(py, "")
}
e => PyErr::new::<cpython::exc::RuntimeError, _>(py, e.to_string()),
})
}
pub fn repo_error_to_pyerr<T>(
py: Python,
error: Result<T, RepoError>,
) -> PyResult<T> {
hgerror_to_pyerr(py, error.map_err(HgError::from))
}
/// Get a repository from a given [`PyObject`] path, and bubble up any error
/// that comes up.
pub fn repo_from_path(py: Python, repo_path: PyObject) -> Result<Repo, PyErr> {
// TODO make the Config a Python class and downcast it here, otherwise we
// lose CLI args and runtime overrides done in Python.
let config =
hgerror_to_pyerr(py, Config::load_non_repo().map_err(HgError::from))?;
let py_bytes = &repo_path.extract::<PyBytes>(py)?;
let repo_path = py_bytes.data(py);
let repo = repo_error_to_pyerr(
py,
Repo::find(&config, Some(get_path_from_bytes(repo_path).to_owned())),
)?;
Ok(repo)
}
// Necessary evil for the time being, could maybe be moved to
// a TryFrom in Node itself
const NODE_BYTES_LENGTH: usize = 20;
type NodeData = [u8; NODE_BYTES_LENGTH];
/// Copy incoming Python bytes given as `PyObject` into `Node`,
/// doing the necessary checks
pub fn node_from_py_object<'a>(
py: Python,
bytes: &'a PyObject,
) -> PyResult<Node> {
let as_py_bytes: &'a PyBytes = bytes.extract(py)?;
node_from_py_bytes(py, as_py_bytes)
}
/// Clone incoming Python bytes given as `PyBytes` as a `Node`,
/// doing the necessary checks.
pub fn node_from_py_bytes(py: Python, bytes: &PyBytes) -> PyResult<Node> {
<NodeData>::try_from(bytes.data(py))
.map_err(|_| {
PyErr::new::<ValueError, _>(
py,
format!("{}-byte hash required", NODE_BYTES_LENGTH),
)
})
.map(Into::into)
}
/// Wrap a call to `func` so that Python's `SIGINT` handler is first stored,
/// then restored after the call to `func` and finally raised if
/// `func` returns a [`HgError::InterruptReceived`]
pub fn with_sigint_wrapper<R>(
py: Python,
func: impl Fn() -> Result<R, HgError>,
) -> PyResult<Result<R, HgError>> {
let signal_py_mod = py.import("signal")?;
let sigint_py_const = signal_py_mod.get(py, "SIGINT")?;
let old_handler = signal_py_mod.call(
py,
"getsignal",
PyTuple::new(py, &[sigint_py_const.clone_ref(py)]),
None,
)?;
let res = func();
// Reset the old signal handler in Python because we've may have changed it
signal_py_mod.call(
py,
"signal",
PyTuple::new(py, &[sigint_py_const.clone_ref(py), old_handler]),
None,
)?;
if let Err(HgError::InterruptReceived) = res {
// Trigger the signal in Python
signal_py_mod.call(
py,
"raise_signal",
PyTuple::new(py, &[sigint_py_const]),
None,
)?;
}
Ok(res)
}