findpython/0000755000176200001440000000000014404145472012441 5ustar liggesusersfindpython/NAMESPACE0000644000176200001440000000017714010325150013647 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(can_find_python_cmd) export(find_python_cmd) export(is_python_sufficient) findpython/LICENSE0000644000176200001440000000013513400306213013430 0ustar liggesusersYEAR: 2012-2018 COPYRIGHT HOLDER: Trevor L. Davis YEAR: 2014 COPYRIGHT HOLDER: Paul Gilbert findpython/README.md0000644000176200001440000000777414404131054013726 0ustar liggesusersfindpython ========== [![CRAN Status Badge](https://www.r-pkg.org/badges/version/findpython)](https://cran.r-project.org/package=findpython) [![R-CMD-check](https://github.com/trevorld/findpython/workflows/R-CMD-check/badge.svg)](https://github.com/trevorld/findpython/actions) [![Coverage Status](https://codecov.io/github/trevorld/findpython/branch/master/graph/badge.svg)](https://app.codecov.io/github/trevorld/findpython?branch=master) [![RStudio CRAN mirror downloads](https://cranlogs.r-pkg.org/badges/findpython)](https://cran.r-project.org/package=findpython) `findpython` is an R package that finds acceptable Python binaries for your program. Since there are often multiple python binaries installed on any given system and they aren\'t always added to the path this can be a non-trivial task. To install the latest version released to CRAN use: install.packages("findpython") To install the development version use: remotes::install_github("trevorld/findpython") It has no dependencies (other than R) but if you have the suggested `reticulate` package installed it will also use it to try to find an acceptable python binary. You\'ll also need the suggested `testthat` package to run the unit tests. Usage ----- `find_python_cmd` is the main function. It returns the path to a python binary that meets certain requirements you specify. Below are some examples. If you need to find a Python 2 binary which is at least Python 2.4: > library("findpython") > find_python_cmd(minimum_version = '2.4', maximum_version = '2.7') [1] "/usr/bin/python" If you need to find a version of Python at least Python 2.5 (but don\'t care if it is a Python 3 binary): > find_python_cmd(minimum_version = '2.5') [1] "/usr/bin/python" If you don\'t care what version of Python you use but it needs to have access to an `argparse` module as well as either the `json` OR `simplejson` module: > find_python_cmd(required_modules = c('argparse', 'json | simplejson')) [1] "/usr/bin/python" Although `find_python_cmd` will create a basic default message if left unspecified you can use the `error_message` argument to specify what error message your program will output if it is unable to find an acceptable binary: > error_message <- paste('Was unable to find the Python 4 binary dependency.', + 'See file INSTALL for more information.') > find_python_cmd(minimum_version = '4.0', error_message = error_message) Error in find_python_cmd(minimum_version = "4.0", error_message = error_message) : Was unable to find the Python 4 binary dependency. See file INSTALL for more information. There is also a wrapper for `find_python_cmd` that instead of throwing an error upon failing to find an appropriate Python command will return `FALSE` and if it finds an appropriate command will return `TRUE`. If successful it attaches the appropriate binary path as an attribute `python_cmd`: > did_find_python <- can_find_python_cmd() > python_cmd <- attr(did_find_python, "python_cmd") > cat(did_find_python, python_cmd, "\n") TRUE /usr/bin/python The default error message will be something like: > find_python_cmd(min='4.0') Error in find_python_cmd(min = "4.0") : Couldn't find a sufficient Python binary. If you haven't installed the Python dependency yet please do so. If you have but it isn't on the system path (as is default on Windows) please add it to path or set options('python_cmd'='/path/to/binary') or set the PYTHON, PYTHON2, or PYTHON3 environmental variables. Python must be at least version 4.0 If you already have a python binary you want to check you can use `is_python_sufficient` to test whether it has sufficient features. It has the same arguments `minimum_version`, `maximum_version`, and `required_modules` as `find_python_cmd`: > is_python_sufficient(path_to_binary, minimum_version = '2.6', required_modules = 'scipy') [1] FALSE License ------- This package is available under the [MIT](https://www.r-project.org/Licenses/MIT) license. findpython/man/0000755000176200001440000000000012660247705013220 5ustar liggesusersfindpython/man/can_find_python_cmd.Rd0000644000176200001440000000340513567053460017476 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/find_python_cmd.r \name{can_find_python_cmd} \alias{can_find_python_cmd} \title{Determines whether or not it can find a suitable python cmd} \usage{ can_find_python_cmd( minimum_version = NULL, maximum_version = NULL, required_modules = NULL, error_message = NULL, silent = FALSE ) } \arguments{ \item{minimum_version}{The minimum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{maximum_version}{The maximum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{required_modules}{Which modules should be required. Can use a single "|" to represent a single either-or requirement like "json|simplejson". If left NULL won't impose such a restriction.} \item{error_message}{What error message the user will see if couldn't find a sufficient python binary. If left NULL will print out a default message.} \item{silent}{Passed to \code{try}, whether any error messages from \code{find_python_cmd} should be suppressed} } \value{ \code{TRUE} or \code{FALSE} depending on whether \code{find_python_cmd} could find an appropriate python binary. If \code{TRUE} the path to an appropriate python binary is also set as an attribute. } \description{ \code{can_find_python_cmd} runs \code{find_python_cmd} and returns whether it could find a suitable python cmd. If it was successful its output also saves the found command as an attribute. } \examples{ did_find_cmd <- can_find_python_cmd() python_cmd <- attr(did_find_cmd, "python_cmd") } \seealso{ \code{\link{find_python_cmd}} } findpython/man/find_python_cmd.Rd0000644000176200001440000000310713567053460016654 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/find_python_cmd.r \name{find_python_cmd} \alias{find_python_cmd} \title{Find a suitable python cmd or give error if not possible} \usage{ find_python_cmd( minimum_version = NULL, maximum_version = NULL, required_modules = NULL, error_message = NULL ) } \arguments{ \item{minimum_version}{The minimum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{maximum_version}{The maximum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{required_modules}{Which modules should be required. Can use a single "|" to represent a single either-or requirement like "json|simplejson". If left NULL won't impose such a restriction.} \item{error_message}{What error message the user will see if couldn't find a sufficient python binary. If left NULL will print out a default message.} } \value{ The path to an appropriate python binary. If such a path wasn't found then it will throw an error. } \description{ \code{find_python_cmd} finds a suitable python cmd or raises an error if not possible } \examples{ \dontrun{ find_python_cmd() find_python_cmd(minimum_version = "2.6", maximum_version = "2.7") find_python_cmd(required_modules = c("argparse", "json | simplejson")) } } \seealso{ \code{\link{can_find_python_cmd}} for a wrapper which doesn't throw an error } findpython/man/is_python_sufficient.Rd0000644000176200001440000000243113567053460017742 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/find_python_cmd.r \name{is_python_sufficient} \alias{is_python_sufficient} \title{Tests whether the python command is sufficient} \usage{ is_python_sufficient( path, minimum_version = NULL, maximum_version = NULL, required_modules = NULL ) } \arguments{ \item{path}{The path to a given python binary. If binary is on system path just the binary name will work.} \item{minimum_version}{The minimum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{maximum_version}{The maximum version of python it should be. Should be a string with major and minor number separated by a \sQuote{.}. If left NULL won't impose such a restriction.} \item{required_modules}{Which modules should be required. Can use a single "|" to represent a single either-or requirement like "json|simplejson". If left NULL won't impose such a restriction.} } \value{ \code{TRUE} or \code{FALSE} depending on whether the python binary met all requirements } \description{ \code{is_python_sufficient} checks whether a given python binary has all the desired features (minimum and/or maximum version number and/or access to certain modules). } findpython/DESCRIPTION0000644000176200001440000000156514404145472014156 0ustar liggesusersPackage: findpython Type: Package Title: Functions to Find an Acceptable Python Binary Version: 1.0.8 Authors@R: c(person("Trevor L", "Davis", role=c("aut", "cre"), email="trevor.l.davis@gmail.com", comment = c(ORCID = "0000-0001-6341-4639")), person("Paul", "Gilbert", role="aut")) URL: https://github.com/trevorld/findpython BugReports: https://github.com/trevorld/findpython/issues Description: Package designed to find an acceptable python binary. Suggests: reticulate, testthat License: MIT + file LICENSE Collate: 'find_python_cmd.r' RoxygenNote: 7.2.1 Encoding: UTF-8 NeedsCompilation: no Packaged: 2023-03-14 18:37:27 UTC; trevorld Author: Trevor L Davis [aut, cre] (), Paul Gilbert [aut] Maintainer: Trevor L Davis Repository: CRAN Date/Publication: 2023-03-14 19:30:02 UTC findpython/tests/0000755000176200001440000000000013400041556013574 5ustar liggesusersfindpython/tests/testthat/0000755000176200001440000000000014010325144015430 5ustar liggesusersfindpython/tests/testthat/test-findpython.r0000644000176200001440000000713613567053743021004 0ustar liggesusers# Copyright (c) 2014 Paul Gilbert # Copyright (c) 2014-2017 Trevor L. Davis # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. context("Any python") test_that("python works as expected", { cmd_exists <- can_find_python_cmd() skip_if_not(as.logical(cmd_exists), "Can't find any version of python") cmd <- attr(cmd_exists, "python_cmd") # "sys" should always be installed expect_true(can_find_python_cmd(required_modules = "sys")) expect_true(is_python_sufficient(cmd, required_modules = "sys")) # "xxxyyyzzz" is being found and should not exist.") expect_false(can_find_python_cmd(required_modules = "xxxyyyzzz", silent = TRUE)) # Can't figure out how to capture try output via testthat... # expect_output(can_find_python_cmd(required_modules = "xxxyyyzzz"), # "Couldn"t find a sufficient Python binary.") expect_false(is_python_sufficient(cmd, required_modules = "xxxyyyzzz")) }) context("Python 2") test_that("Python 2 works as expected", { has_python2 <- can_find_python_cmd(minimum_version = "2.0", maximum_version = "2.7", silent = TRUE) skip_if_not(as.logical(has_python2), "Can't find Python 2") cmd <- attr(has_python2, "python_cmd") # "sys" should always be installed expect_true(is_python_sufficient(cmd, required_modules = "sys")) # "xxxyyyzzz" is being found and should not exist.") expect_false(is_python_sufficient(cmd, required_modules = "xxxyyyzzz")) }) context("Python 3") test_that("Python 3 works as expected", { has_python3 <- can_find_python_cmd(minimum_version = "3.0", silent = TRUE) skip_if_not(as.logical(has_python3), "Can't find Python 3") cmd <- attr(has_python3, "python_cmd") # "sys" should always be installed expect_true(is_python_sufficient(cmd, required_modules = "sys")) expect_true(is_python_sufficient(cmd, required_modules = "json | simplejson")) # "xxxyyyzzz" is being found and should not exist.") expect_false(is_python_sufficient(cmd, required_modules = "xxxyyyzzz")) }) context("Python 4") test_that("Python 4 works as expected", { # Check Python 4 doesn"t exist when testing other machines has_python4 <- can_find_python_cmd(minimum_version = "4.0", silent = TRUE) skip_if_not(as.logical(has_python4), "Python 4 not found") cmd <- attr(has_python4, "python_cmd") # "sys" should always be installed expect_true(is_python_sufficient(cmd, required_modules = "sys")) # "xxxyyyzzz" is being found and should not exist.") expect_false(is_python_sufficient(cmd, required_modules = "xxxyyyzzz")) }) findpython/tests/run-all.R0000644000176200001440000000005612301703766015301 0ustar liggesuserslibrary("testthat") test_check("findpython") findpython/R/0000755000176200001440000000000014404133040012626 5ustar liggesusersfindpython/R/find_python_cmd.r0000644000176200001440000002334214404131577016176 0ustar liggesusers# Copyright (c) 2012-2018 Trevor L. Davis # Copyright (c) 2014 Paul Gilbert # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. #' Tests whether the python command is sufficient #' #' \code{is_python_sufficient} checks whether a given python binary has all the #' desired features (minimum and/or maximum version number and/or access to #' certain modules). #' #' @param path The path to a given python binary. #' If binary is on system path just the binary name will work. #' @param minimum_version The minimum version of python it should be. #' Should be a string with major and minor number separated by a \sQuote{.}. #' If left NULL won't impose such a restriction. #' @param maximum_version The maximum version of python it should be. #' Should be a string with major and minor number separated by a \sQuote{.}. #' If left NULL won't impose such a restriction. #' @param required_modules Which modules should be required. #' Can use a single "|" to represent a single either-or requirement like "json|simplejson". #' If left NULL won't impose such a restriction. #' @return \code{TRUE} or \code{FALSE} depending on whether the python binary met all requirements #' @export is_python_sufficient <- function(path, minimum_version=NULL, maximum_version=NULL, required_modules=NULL) { python_code <- vector("character") if (!is.null(required_modules)) { import_code <- .create_import_code(required_modules) python_code <- append(python_code, import_code) } version_code <- .create_version_checking_code(minimum_version, maximum_version) python_code <- append(python_code, version_code) ok_message <- "Everything worked out okay" python_code <- append(python_code, paste("print(", shQuote(ok_message), ")", sep = "")) tryCatch({ output <- system(path, intern = TRUE, input = python_code, ignore.stderr = TRUE) any(grepl(ok_message, output)) }, warning = function(w) { FALSE }, error = function(e) { FALSE }) } #' Find a suitable python cmd or give error if not possible #' #' \code{find_python_cmd} finds a suitable python cmd or raises an error if not possible #' #' @inheritParams is_python_sufficient #' @param error_message What error message the user will see if couldn't find a sufficient python binary. #' If left NULL will print out a default message. #' @return The path to an appropriate python binary. If such a path wasn't found then it will throw an error. #' @examples #' \dontrun{ #' find_python_cmd() #' find_python_cmd(minimum_version = "2.6", maximum_version = "2.7") #' find_python_cmd(required_modules = c("argparse", "json | simplejson")) #' } #' @seealso \code{\link{can_find_python_cmd}} for a wrapper which doesn't throw an error #' @export find_python_cmd <- function(minimum_version = NULL, maximum_version = NULL, required_modules = NULL, error_message = NULL) { python_cmds <- get_python_cmds() for (cmd in python_cmds) { if (is_python_sufficient(cmd, minimum_version, maximum_version, required_modules)) { return(cmd) } } # Try using reticulate::py_discover_config()$python_versions if can't find a suitable command if (requireNamespace("reticulate", quietly = TRUE)) { python_cmds <- reticulate::py_discover_config()$python_versions for (cmd in python_cmds) { if (is_python_sufficient(cmd, minimum_version, maximum_version, required_modules)) { return(cmd) } } } if (is.null(error_message)) { error_message <- default_error_message(minimum_version, maximum_version, required_modules) } stop(error_message) } default_error_message <- function(minimum_version = NULL, maximum_version = NULL, required_modules = NULL) { paste("Couldn't find a sufficient Python binary.", "If you haven't installed the Python dependency yet please do so.", "If you have but it isn't on the system path (as is default on Windows) please add it to path", "or set options('python_cmd'='/path/to/binary') ", "or set the PYTHON, PYTHON2, or PYTHON3 environmental variables.", if (!is.null(minimum_version)) paste("Python must be at least version", minimum_version), if (!is.null(maximum_version)) paste("Python must be at most version", maximum_version), if (!is.null(required_modules)) paste("Python must have access to the modules:", paste(required_modules, collapse = ", "))) } get_python_cmds <- function() { python_cmds <- c(getOption("python_cmd", ""), "python", Sys.getenv("PYTHON", ""), paste0("python4.", seq(9, 0, by = -1)), Sys.getenv("PYTHON4", ""), "python4", paste0("python3.", seq(20, 0, by = -1)), Sys.getenv("PYTHON3", ""), "python3", paste0("python2.", seq(7, 0, by = -1)), Sys.getenv("PYTHON2", ""), "python2", "pypy", sprintf("C:/Python%s/python", c(49:20))) python_cmds <- unique(python_cmds) python_cmds <- Sys.which(python_cmds) python_cmds[which(python_cmds != "")] } #' Determines whether or not it can find a suitable python cmd #' #' \code{can_find_python_cmd} runs \code{find_python_cmd} and returns whether it could find a suitable python cmd. #' If it was successful its output also saves the found command as an attribute. #' #' @inheritParams find_python_cmd #' @param silent Passed to \code{try}, whether any error messages from \code{find_python_cmd} should be suppressed #' @return \code{TRUE} or \code{FALSE} depending on whether #' \code{find_python_cmd} could find an appropriate python binary. #' If \code{TRUE} the path to an appropriate python binary is also set as an attribute. #' @examples #' did_find_cmd <- can_find_python_cmd() #' python_cmd <- attr(did_find_cmd, "python_cmd") #' @seealso \code{\link{find_python_cmd}} #' @export can_find_python_cmd <- function(minimum_version = NULL, maximum_version = NULL, required_modules = NULL, error_message = NULL, silent = FALSE) { python_cmd <- try(find_python_cmd(minimum_version = minimum_version, maximum_version = maximum_version, required_modules = required_modules, error_message = error_message), silent = silent) if (inherits(python_cmd, "try-error")) { r <- FALSE } else { r <- TRUE attr(r, "python_cmd") <- python_cmd } r } # Create appropriate module import code .create_import_code <- function(required_modules) { import_code <- vector("character") for (module in required_modules) { if (grepl("\\|", module)) { module <- gsub(" ", "", module) module <- strsplit(module, "\\|")[[1]] import_code <- append(import_code, c("try:", paste(" import", module[1]), "except ImportError:", paste(" import", module[2]))) } else { import_code <- append(import_code, paste("import", module)) } } import_code } # Create appropriate version checking code .create_version_checking_code <- function(minimum_version = NULL, maximum_version = NULL) { if (is.null(minimum_version) && is.null(maximum_version)) { return(c()) } else { version_code <- c("import sys") } if (!is.null(minimum_version)) { min_version <- strsplit(minimum_version, "\\.")[[1]] major <- min_version[1] minor <- min_version[2] version_code <- append(version_code, c(paste("if sys.version_info.major <", major, ": raise Exception('Major version too low')"), paste("if sys.version_info.major ==", major, "and sys.version_info.minor <", minor, ": raise Exception('Minor version too low')"))) } if (!is.null(maximum_version)) { max_version <- strsplit(maximum_version, "\\.")[[1]] major <- max_version[1] minor <- max_version[2] version_code <- append(version_code, c(paste("if sys.version_info.major >", major, ": raise Exception('Major version too high')"), paste("if sys.version_info.major ==", major, "and sys.version_info.minor >", minor, ": raise Exception('Minor version too high')"))) } version_code } findpython/NEWS.md0000644000176200001440000000163714404132453013541 0ustar liggesusersfindpython 1.0.8 ================ * Looks for more python binaries (Python 3.13 through 3.20). This will help future-proof the package for later Python releases. findpython 1.0.7 ================ * Looks for more python binaries (Python 3.10 through 3.12). findpython 1.0.5 ================ * Minor internal refactoring and documentation improvements. findpython 1.0.4 ================ * No longer throws an unintended error when can't find a suitable python command AND ``reticulate`` package is not installed. findpython 1.0.3 ================ * Uses ``reticulate::py_discover_config`` to find more python binaries if can't otherwise find a suitable python command. findpython 1.0.2 ================ * Looks for more python binaries and likely folders in Windows (i.e. Python 3.5 through Python 3.9). Thanks Jori Liesenborgs for request. findpython 1.0.1 ================ * Initial released version on CRAN. findpython/MD50000644000176200001440000000107114404145472012750 0ustar liggesusers229193c07714b7afe98c030fc583c440 *DESCRIPTION 02400157f5068d7cca6ddc4ab5e47f26 *LICENSE 2ac170dcd095e1526fdf4d56b295cc9d *NAMESPACE c19aa8768f174fa010e64171cfd760be *NEWS.md c048d3b2b6afb46696113077933bb5f7 *R/find_python_cmd.r 897779525fc185d572185ab119dac60f *README.md ca791a142e5ded5569418997105e3032 *man/can_find_python_cmd.Rd 07eb988780426e88278506039d8fc469 *man/find_python_cmd.Rd 9bdbc45ecddd409e7ce755cd52815d4f *man/is_python_sufficient.Rd e9f668748d07cefc493a1a111d403784 *tests/run-all.R d3937c30ef5bafc57258bcb9ffe43cd9 *tests/testthat/test-findpython.r