././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8779466 rpy2-3.5.15/0000755000175100001770000000000014543120757012116 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/AUTHORS0000644000175100001770000000112014543120705013151 0ustar00runnerdocker Author ------ Laurent Gautier Copyright Laurent Gautier 2008-2010 People have contributed suggestions or patches; they are thanked here, rpy2 is much better because of them. rpy2 is making a limited use (if much left) of code from: RPy - http://rpy.sourceforge.net -------------------------------- (in rinteface/rinterface.c) Copyright Walter Moreira 2002-2003 Copyright Gregory Warnes 2003-2008 Parseltongue project - http://serpent.speak.googlepages.com/ ------------------------------------------------------------ (in rinterface/rinterface.c) Copyright Alexander Belopolsky - 2006 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/LICENSE0000644000175100001770000004325414543120705013124 0ustar00runnerdocker GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/MANIFEST.in0000644000175100001770000000071014543120705013643 0ustar00runnerdockerglobal-include README global-exclude *patch* *diff* .hg include MANIFEST MANIFEST.in include NEWS README.md AUTHORS include gpl-2.0.txt include requirements.txt include _rinterface_cffi_build include rpy/__init__.py include rpy/situation.py recursive-include rpy/robjects *.py recursive-include rpy/ipython *.py recursive-include rpy/interactive *.py recursive-include rpy/rlike *.py prune dist include doc/Makefile include doc/source/rpy2_logo.png ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/NEWS0000644000175100001770000027471614543120705012627 0ustar00runnerdockerRelease 3.5.15 ============== New features ------------ - Checking C compiling against the R shared library can be skipped with the environment variable `RPY2_API_FORCE` set to `True`. - The rinterface-level class :class:`rpy2.rinterface.SexpExtPtr` now also has an robject-level wrapper :class:`rpy2.robjects.ExternalPointer`, and a representation for class name-based conversion rules. Bugs fixed ---------- - Fixed notebook representation of R named arrays or vectors when conversion rules might interfere (issue #1047). - Multi-line in environment variables (issue #1066). Changes ------- - Complete drop of support for Python 3.7. It is now removed from the build matrix. Release 3.5.14 ============== Bugs fixed ---------- - Fixed use of named arguments with `ExtractDelegator` (issue #1048). - `rpy2py` conversion rules in `numpy2ri` could convert R `list` objects into :class:`rpy2.containers.rlike.OrdDict` but there were no `py2rpy` rules to perform the reverse conversion (issue #944). - :meth:`rpy2.robjects.vectors.ListVector.from_length` was incorrectly calling the conversion rules before returning the :class:`ListVector`. Changes ------- - `distutils` was still used in a couple of places. The installation process should now be relying only on `setuptools`. Release 3.5.13 ============== New features ------------ - R is setting a number of environment variables in a wrapper script. Those variables were not set by default by rpy2 and this could lead to issues (see issue #1033). The variables are now added to `os.environ`. Bugs fixed ---------- - `python -m rpy2.situation LD_LIBRARY_PATH` could incorrectly truncate the value for `LD_LIBRARY_PATH` when the variable `LD_LIBRARY_PATH` is already defined. Changes ------- - The dependency on :mod:`pytz`, a deprecated package, was removed and replaced by a dependency to :mod:`zoneinfo`. If Python < 3.9 the package :mod:`backports.zoneinfo` is an added dependency. - The depency on :mod:`tzlocal` does no longer limit it to `<5.0` (issue #1035). - Remove dependency on :mod:`distutils` in Python's standard library. That package is deprecated and removed in Python 3.12 (see https://peps.python.org/pep-0632/ and issue #1040). Release 3.5.12 ============== New features ------------ - :func:`rpy2.rinterface.evalr_expr_with_visible` to evaluate an expression and return the visibility status of the value returned. - More events in the intialization and termination of an embedded R logged by :mod:`rpy2.rinterface_lib.embedded.logger` at `INFO` and `DEBUG` levels. - :meth:`rpy2.robjects.R.__call__` has 2 named arguments "visible" and "print_r_warnings" to handle R "invisible" results and print R warnings at the end of the evaluation. Invisible results happen when doing `rpy2.robjects.r("x <- 1")`. R will return the value of x "invisibly". The default are `invisible is True` and `print_r_warnings is True`. Bugs fixed ---------- - "R magic" cells are now show R warnings at the end of the evaluation of an R cell (#issue 226). Changes ------- - Evaluating a string as R code using :meth:`rpy2.robjects.R.__call__` (e.g., `rpy2.robjects.r("1+2")`) now shows R warnings at the end of the evaluation by default. - :meth:`rpy2.robjects.R.__call__` returns the results invisibly by default (see section "New features" for this release). Release 3.5.11 ============== New features ------------ - :class:`pandas.Categorical` objects are now converted to R `factor` objects. Bugs fixed ---------- - :mod:`pandas` 2.0 makes API-breaking changes that made conversion fail (issue #1013). - The default converter for R magic was dropping R factors to :mod:`numpy` arrays of integers rather than convert them to pandas :class:`Category` (issue #1010). Changes ------- - The fix for issue #1010 (see above) changes the numpy conversion for R factors. It returns an arrays with the level factors rather than the integer codes for the levels. - :func:`rpy2.rojects.pandas2ri.py2rpy_categoryseries` raises a :class:`DeprecationWarning`. Release 3.5.10 ============== New features ------------ - :mod:`rpy2.situation` reports cffi interface type information. - The ipython/jupyter R magic can now have `jpeg` as a default graphics format for static figures in addition to `png` and `svg`. Bugs fixed ---------- - Building the path to the R shared library was incorrectly using the output of `R CMD config LIBnn` (issue #982). - R started raising warnings when calling `formals` on `BUILTINSXP` R objects. Internal functions in :mod:`rpy.robjects.functions` calling `formals` no longer propagate the warnings. Release 3.5.9 ============= New features ------------ - Type hints in :mod:`rpy2` are now checked with `mypy`. Bugs fixed ---------- - Building the path to the R shared library was incorrectly using the output of `R CMD config LIBnn` (issue #982). - R started raising warnings when calling `formals` on `SPECIALSXP` R objects. Internal functions :mod:`rpy.robjects.functions` calling `formals` no longer propagate the warnings. - The :mod:`numpy` converter was not turning `NA_character_` into `None` (issue #979). - :mod:`rpy2.situation` included an eager `import rpy2` that could cause a version mismatch error with some build/install toolchain (issue #984). The import is now lazy/delayed. - Installation targets `pandas`, `all`, and `test` now specify `pandas>=1.2.0` (which should limit frequencies of issues like #998). Changes ------- - :class:`rpy2.robjects.conversion.Converter` objects are no longer functioning context managers. An exception is now raised when trying to use it that way. The feature was introduced with rpy2-3.5.7 but it does not guarantee the locality of a context manager and can result in permanently changed conversion rules. The method :meth:`rpy2.robjects.conversion.Converter.context` should be used instead. Release 3.5.8 ============= New features ------------ - Default value for the interactivity status of the embedded R as initialization is now a private variable `_DEFAULT_R_INTERACTIVE` in :mod:`rpy2.rinterface_lib.embedded`. Bugs fixed ---------- - :func:`rpy2.rinterface.initr_checkenv` (aliased to :func:`rpy2.rinterface.initr`) did not include initialization parameters for `interactive`, `_want_setcallbacks`, and `_c_stack_limit` (issue #976). - The numpy converter was not listing R objects of type `CHARSXP` as vectors. This make R `NA_character_` values wrapped in `rpy2` Python objects to be passed as-is (issue #983). Release 3.5.7 ============= New features ------------ - :class:`rpy2.robjects.conversion.Converter` objects are now also context managers. This makes the use of :func:`rpy2.robjects.conversion.localconverter` unnecessary as a converter can now be used directly with a Python `with` statement. The documentation was updated accordingly. Bugs fixed ---------- - Building the C-extension could fail when the C compiler is not C99-compatible or requires to be specified C99 compatibility (issue #963). - With the "R magic", converters in a local namespace were inadvertently skipped (the local namespace was not searched). - Calling :func:`rpy2.rinterface.rternalize` before the embedded R is initialized was ending with a segfault. It is now raising an :class:`rpy2.rinterface_lib/embedded/RNotReady` (issue #971). Changes ------- - The default for the :class:`contextvars.ContextVar` holding conversion rules (:obj:`rpy2.robjects.conversion.converter`) is now a dummy converter raising errors about missing conversion rules. This behavior helps with troubleshooting Python code using multithreading without fetching the context (see issue #965). - A warning is now issued when trying to convert a `DataFrame` with duplicate indexes (issue #811). Release 3.5.6 ============= Bugs fixed ---------- - :class:`rpy2.rlike.container.OrdDict` objects were triggering an error when unpickled (PR #955). - Function signature mapping not longer assumes that all default values are R vectors mapped to a `Vector` instance (PR #954). Release 3.5.5 ============= New features ------------ - For ipython and Jupyter notebooks, R "magics" accepting optional input arguments (`-i` or `--input`) can now be of form `` or `=`. The former is the way is has been working so far: the R object is given the name of the Python object. The latter allows to specify the name under which the input object will be known to R. In addition to that the python name can optionally be a path, that is a name nested in a sequence of namespaces. For example, `-i module.varname`. The magics concerned are `%R`, `%%R`, and `%Rpush` (issues #934 and #935). - For ipython and Jupyter notebooks, R "magics" accepting an optional conversion argument (`-c/--converter`) now also accepts names that are a path in Python, that is a name nested in a sequence of namespaces. For example, `-c rpy2.robjects.default_converter` (issue #934). - The "magic" `%Rpush` accepts an optional argument `-c/--converter` that is similar in nature to the argument of the same name in `%R` and `%%R`. Bugs fixed ---------- - :class:`rpy2.rlike.container.TaggedList` objects were triggering an error when unpickled (issue #947). - Converting :class:`pandas.Series` of type `"object"` with a fallback to R strings no longer fails with an error (issue #916). Changes ------- - :meth:`rpy2.robjects.help.Page.iteritems` is deprecated. Use the method `items()` instead. - Calling :func:`str` on rpy2 objects that are proxies of R objects is now using R's `print()` whenever R's `show()` results in an error (issue #908). Release 3.5.4 ============= New features ------------ - :func:`rpy2.rinterface.rternalize` can optionally mirror more accurately in R the function signature defined in Python (issue #905). Bugs fixed ---------- - Better handling of timezone in :class:`rpy2.robjects.vectors.POSIXct` (issue #917). - :mod:`pandas` conversion is now correctly handling R data frames with duplicated column names. They were dropped tbefore that (issue #925). - :mod:`pandas` conversion is now correctly handling R data frames with nested columns (issue #930). - Importing :mod:`rpy2.ipython.rmagic` while :mod:`pandas` is not installed resulted in an error. Changes ------- - :func:`rpy2.robjects.methods.getclassdef` is now explicitly naming the optional argument with the R package name when calling the underlying R function. - The `%R` line magic for ipython/jupyter was trying to substitute anything after a dolar (`$`) sign, which caused issue since this character is used to access by name elements in R containers. No substitution is happening anymore (issue #922). Release 3.5.3 ============= Bugs fixed ---------- - Pandas converter no longer fails with arrays of `dtype` :class:`pandas.BoolDtype` or :class:`pandas.Float64Dtype`.(issue #880). - Numpy and pandas conversion could fail an R object is a low-level wrapper for an R list (issue #890). - Fixed detection of the R install path on Windows (issues #875 and #885). - Fixed error with dynamic docstring generation for functions without arguments (issue #903). - Fixed error with dynamic docstring generation when R functions are missing the section 'Details' (isue #902). Release 3.5.2 ============= New features ------------ - The string representation for :class:`rpy2.robjects.conversion.Converter` objects lists classes the converter will dispatch on. Bugs fixed ---------- - :meth:`rpy2.robjects.vectors.FactorVector.iter_labels` was failing if any element is NA (issue #879). - The definition of converters was not thread-safe. The fix deprecates the use of :obj:`rpy2.robjects.conversion.converter` and replaces it with :func:`rpy2.robjects.conversion.get_conversion`. Release 3.5.1 ============= Bugs fixed ---------- - Fix `__setitem__` on R list when the value is a simple Python object with a default low-level conversion (issue #864). - The default converter for the rmagic could fail with a :class:`RecursionError` (issue #866). Release 3.5.0 ============= New features ------------ - :obj:`rpy2.rinterface.NULL` can be refererenced in code before the embedded R is initialized. - `LD_LIBRARY_PATH` is obtained from the executable `RScript` and set prior to initializing R at the :mod:`rpy2.rinterface` level (issue #833). - The package is now fully typed with type hints. - The :func:`rpy2.rinterface.evalr` accepts the named parameters `envir` and `enclos`, mirroring better the signature of the R function `eval`. - New function :func:`rpy2.rinterface.evalr_expr` to evaluate R expressions. - :mod:`rpy2.situation` is now using a module-level (called `logger`) to facilitate troubleshooting. When the module is run as an executable the flag `--verbose` can take the possible values DEBUG, INFO, WARNING, ERROR. Changes ------- - Better customizablity of conversion rules from R objects with `typeof` `INTSXP` (array of integers), `VECSEXP` (R list), and `LGLSXP` (array of booleans) :mod:`pandas`. - The representation of R integer is no longer using a comma separator for thousands (issue #797). - Requirement is now Python>=3.7. :mod:`rpy2` is no longer tested with Python 3.6. - :mod:`rpy2.robject.lib.grid` was refactored to fix the conversion. This fixes issue #804. A classes now have a `classmethod` `r` for the R constructor (the `__init__` is the Python constructor). - :obj:`rpy2.robjects.vectors.__all__` is removed. - Cleaner class design by having :class:`rpy2.rinterface.NumpyArrayMixin` removed and replaced by :class:`rpy2.rinterface.SexpVectorWithNumpyInterface`. - With Python < 3.8 the module :mod:`typing_extensions` is now a requirement. - Initialization and update steps for :class:`rpy2.robjects.conversion.NameClassMap` were updated to ensure type hints are correct. This probably solved cryptic bugs with conversion system. - :meth:`rpy2.rinterface.LangSexpVector.from_string` was at the robject level (class method of :class:`rpy2.robjects.language.LangVector`) but is now at the interface level. - :meth:`rpy2.rinterface.get_evaluation_context` is deprecated. The module level context variable `evaluation_context` should be used instead. The use of the context variable should address issues when using nested :meth:`rpy2.rinterface.local_context`. - Added recommendation to try :meth:`pandas.DataFrame.infer_objects` when the converion to R data frames fails because of mixed types (issue #794). - No longer implicit dependency on :mod:`numpy` for memory views. The Python API has an unresolved issue around handling FORTRAN-ordered arrays without going to C-level (https://bugs.python.org/issue34778), and C-extension as added to rpy2 to cut the dependency on numpy for just this (PR #856). Bugs fixed ---------- - R warning about partial argument name matching is now cleared (issue #821). - Setting a slice for a StrSexpVector where some of the elements are not strings no longer fails. - Assigning items to an R array of strings using a slice of more than one element was likely to fail. - The :mod:`rpy2.rinterface`-level conversion of Python integers to R integers is now checking that integers are not larger than what R can handle. - Trying to access items in an R environment before R is initialized is no longer ending with a segfault (issue #842). Release 3.4.6 ============= - Added a class and an alias to map ggplot2's `element_line` (issue #822). Release 3.4.5 ============= Changes ------- - The deprecation warning when using :func:`rpy2.robjects.lib.grid.activate` was missing (indirectly revealed through issue #804). - The named argument `LINPACK` in :meth:`rpy2.robjects.vectors.Matrix.svd` is no longer present in R. Bugs fixed ---------- - SIGPIPE sent to a process running Python+rpy2 could result in a segfault. This was caused by an incorrect setting of R signal handlers (issue #809). Release 3.4.4 ============== Changes ------- - `RRuntimeError` exceptions raised while evaluating R code an R magic (ipython/jupyter) are now propagated (issue #792). Release 3.4.3 ============= New features ------------ - :mod:`rpy2.robjects.lib.ggplot2` maps more functions in the R package (issue #767) - Utility function :func:`rpy2.robjects.lib.ggplot2.dict2rvec` to convert a Python `Dict[str, str]` into an R named vector of strings. Bugs fixed ---------- - Calling mod:`rpy2.situation` to report on the environment no longer stops with an uncaught exception when no R home can be determined (issue #774) - Converting pandas series with the older numpy types could result in an error (issue #781) - Numpy converter was not properly turing R integer or float arrays into their numpy equivalent (issue #785) - The HTML representation of R list without named element was incorrect (issue #787) - Fix for when R is installed in a user home directory on Windows (PR #788) Release 3.4.2 ============= Bugs fixed ---------- - Multithreading during the initialization of the embedded R no longer triggers a fatal error (issue #729) Changes ------- - :mod:`pytest` is now an optional package. Optional sets of packages are `numpy`, `pandas`, `test`, and `all` (all optional packages). They can be specified during the installation. For example `pip install rpy2[test]`. (issue #670) Release 3.4.1 ============= Bugs fixed ---------- - The file `requirements.txt` was missing from the source distribution on pypi (issue #764). Release 3.4.0 ============= New Features ------------ - The mapping of the R C API now includes `Rf_isSymbol()`. - Singleton class :class:`rpy2.rinterface_lib.sexp.RVersion` to report the R version for the embedded R. - :func:`rpy2.rinterface.local_context` to create a context manager to evaluate R code within a local environment. - The `staticmethod` :meth:`rpy2.robjects.vectors.DateVector.isrinstance` will tell whether an R objects is an R `Date` array. Changes ------- - The dynamic generation of docstrings for R man pages is now using R's `Rd2txt`. - The :func:`rpy2.rinterface_lib._rinterface_capi._findVarInFrame` is replaced by the function :func:`rpy2.rinterface_lib._rinterface_capi._findvar_in_frame` (see fix to issue #710). - The functions :func:`rpy2.robjects.numpy.activate()` and :func:`rpy2.robjects.pandas.activate()` are deprecated and will be removed in rpy2-3.5.0. - :func:`rpy2.rinterface_lib.embedded.setinitialized` was renamed to :func:`rpy2.rinterface_lib.embedded._setinitialized` to indicate that one should not use it. - :meth:`rpy2.robjects.lib.ggplot2.vars` to map the R function `ggplot2::vars` (issue #742). - Report correctly the class of R matrix objects with R>=4.0: it is now `('matrix', 'array')`. With R<4.0 `('matrix')` is still reported. - The conversion of R/rpy2 objects to python objects using R class name mapping is extended to more classes. The documentation about conversion covers the topic. - If `R_NilValue` is not null when the initialization of the embedded R is attempted, it is now assumed that R was initialized through other means (e.g., an other C library in the same process) and the C-level initialization is be skipped. - The conversion `rpy2py` is now working with any Python object inheriting from `_rinterface_capi.SupportsSEXP`. Bugs fixed ---------- - The C function `Rf_findVarInFrame()` in the R API can trigger in an R-level error, and while this is rare, when it does when embedded in Python it creates a segfault. Calls are now wrapped in `R_ToplevelExec()` to limit the propagation of R exceptions. This solved issue #710. - More complete and correct mapping of R class names in :func:`rpy2.rinterface_lib.sexp.rclass_get`. - Initializing the embedded R caused the loss of ability to use Ctrl-C to send SIGINT to a Python process (issue #723) - :mod:`rpy2.sitation` is now working when the environment variable `R_HOME` is set even though R is not in the `PATH` or in the Windows registry (issue #744). - Handling an R language objects could result in a segfault when its R class was queried (issue #749). - The conversion of R string arrays to `numpy` arrays was leaving R's `NA` value as R NA objects. NAs in this type of arrays are now turned to `None` in the resulting `numpy` array (issue #751). - `rpy2.situation.get_rlib_path()` was returning an environment variable with an invalid separator on Windows (mentioned in issue #754). - R strings encoded with something else than 'utf-8' could result in errors when trying to convert to Python strings (issue #754). - Extracting documentation pages for R objects in packages could generate spurious warnings when several "section" tags are present. - R `Date` arrays/vectors were not wrapped into :class:`rpy2.robjects.vectors.DateVector` objects but left as R arrays of floats (which they are at the C level). - The HTML representation of short R lists without names could fail with an error. - The :meth:`__repr__` of `robjects`-level objects was not displaying the rpy2 class the R object is mapped to. Release 3.3.6 ============= Bugs fixed ---------- - The unit tests for importing R packages with `lib_loc` were broken (issue #720). - Trying to create a memoryview for an R array with complex values was failing with an attribute error. - Fix the constructor of metaclass :class:`rpy2.robjects.methods.RS4Auto_Type`. - Fix call to end the embedded R in :class:`rpy2.robjects.R.__cleanup__` (issue #734). Release 3.3.5 ============= Bugs fixed ---------- - The callback handler to read input to R returned an invalid result, leading to R asking for input without ever acknowledging it received it. Release 3.3.4 ============= Bugs fixed ---------- - Creating an R vector object from a Python object implementing the buffer protocol could give incorrect results as C-level incompatibilities could be missed (issue #702). - :func:`rpy2.robjects.packages.importr` could fail when `lib_loc` was specified (issue #705). Release 3.3.3 ============= Bugs fixed ---------- - Fallback for when `str2lang` is missing (R < 3.6) - Fix segfault with :meth:`PairListSexpVector.__getitem__` when elements of the R pairlist have a `NILSXP` name (issue #700) Release 3.3.2 ============= Bugs fixed ---------- - Initial fixes to have rpy2 running in ABI mode on Windows. Few tests are not passing (many in callbacks for R's C API). - System detection is now checking for FreeBSD. Release 3.3.1 ============= Bugs fixed ----------- - :meth:`rpy2.robjects.conversion.NameClassMap.update` can update the mapping (:class:`dict`) or the default class. Changes ------- - Adding local converters was overwriting the base `NameClassMap`. Release 3.3.0 ============= New features ------------ - Trying to import an R package that is not installed will now raise an exception :class:`rpy2.robjects.packages.PackageNotInstalledError`. - The R C API functions `void SET_FRAME(SEXP x, SEXP v)`, `void SET_ENCLOS(SEXP x, SEXP v)` and `void SET_HASHTAB(SEXP x, SEXP v)` are now accessible through rpy2. - The module :mod:`rpy2.situation` can now return `LD_LIBRARY_PATH` information about R. For example with `python -m rpy2.situation LD_LIBRARY_PATH` - :meth:`rpy2.robjects.methods.RS4.extends` lists the class names in the inheritance line. - The conversion of R objects to Python allows much more flexibility and better allow the use of independent code converting different classes. This is currently limited to R objects that are lists, environments, or S4 objects. The Sphinx documentation contains an example. While this is still work in progress this should already address concerns at the origin of issue #539 about S4 classes. - :class:`rpy2.robjects.language.LangVector` to map R language objects at the `robjects` level. - :class:`rpy2.robjects.vectors.PairlistVector` to map R pairlist objects at the `robjects` level. - An alternative function to display the output of R cells can be specified using `-d` or `--display` in the magic arguments (in :mod:`rpy2.ipython.rmagic`). - Python classes representing underlying R objects no longer have to exclusively rely on inheritance from :mod:`rpy2.rinterface` objects`. An abstract class :class:`rpy2.rinterface_lib.sexp.SupportsSEXP` is added to identify objects supporting a `__sexp__` protocol, and that abstract class can also be used with type hints. - :func:`rpy2.robjects.functions.wrap_r_functions` can create Python functions with matching signature from R functions - :func:`rpy2.robjects.functions.wrap_r_functions` can create Python functions with matching signature from R functions. - New class :class:`rpy2.rinterface_lib._rinterface_capi.UninitializedRCapsule` to allow the instanciation of "placeholder" rpy2 objects before the embedded R is initialized. This facilitate the use of static typing checks such as mypy, mocking for tests that do not involve the execution of R code, and allow cleaner implementations of module-level globals that are R objects. - New class :class:`rpy2.robjects.vectors.DateVector` to represent R dates. - :class:`pandas.Series` containing date objects can now be converted to R `Date` vectors. Changes ------- - When calling R C-API's `R_ParseVector` and a error occurs, the exception message now contains the parsing status. - :mod:`rpy2.rinterface_lib.embedded` has a module-level "constant" `DEFAULT_C_STACK_LIMIT` used when initializing the embedded R. - When creating a :mod:`rpy2.robjects.vectors.DataFrame` from (name, vector) pairs, the names are no longer transformed to syntactically valid R symbols (issue #660). - The value `nan` in :mod:`pandas` Series with strings is now converted to R NA (issue #668). - Initial support for :const:`pandas.NA` (still experimental in pandas at the time of writing, and rpy2 support is limited to arrays of strings). - :mod:`pandas` series of dtype :class:`pandas.StringDType`, experimental in pandas 1.0, are now supported by the converted (in the pandas-to-R direction) (issue #669) - Version checking for the mapping of R packages in :mod:`rpy2.robjects.lib` is now more permissive (check that version prefixes are matching). Bugs fixed ----------- - Building ABI only mode could require an API build environment (and fail with an error when not present). - SVG output for the R magic were incorrectly bytes objects. - :meth:`rpy2.rinterface_lib.sexp.StrSexpVector.__getitem__` was returning the string `'NA'` when an R NA value. Not it returns `rpy2.rinterface_lib.na_values.NA_Character`. Release 3.2.7 ============= Bugs fixed ---------- - An f-string in `_rinterface_cffi_build.py` prevented installation on Python 3.5 (issue #654). Release 3.2.6 ============= Bugs fixed ---------- - The conversion of date/time object with specified timezones was wrong when different than the local time zone (issue #634) - Iterating over :mod:`rpy2.situation.iter_info()` could result in a error because of a typo in the code. Changes ------- - :mod:`pandas` 1.0.0 breaks the conversion layer. A warning is now emitted whenever trying to use `pandas` >= 1.0. Release 3.2.5 ============= Bugs fixed ---------- - Latest release for R package `rlang` broke import through `importr()`. A workaround for :mod:`rpy2.robjects.lib.ggplot2` is to rename the offending R object (issue #631). Changes ------- - f-string requiring Python >= 3.6 removed. Release 3.2.4 ============= Bugs fixed ---------- - An incomplete backport of the bug fixed in 3.2.3 broke the ABI mode. Release 3.2.3 ============= Bugs fixed ----------- - Error when parsing strings as R codes could result in a segfault. Release 3.2.2 ============= Bugs fixed ---------- - Python format error when trying to report that the system is not reported on Windows (issue #597). - The setup script would error on build if R is not installed. It is now printing an error message. Release 3.2.1 ============= Bugs fixed ---------- - The wrapper for the R package `dbplyr` could not import the underlying package (refactoring elsewhere was not propagated there). - Creating R objects called `names` `globalenv` caused the method :meth:`Sexp.names` to fail (issue #587). - Whenever the pandas conversion was activated :class:`FloatSexpVector` instances with the R class `POSIXct` attached where not corrected mapped back to pandas datetime arrays. (issue #594). - Fix installation when an installation when a prefix without write access is used (issue #588). Release 3.2.0 ============= New features ------------ - rpy2 can built and used with :mod:`cffi`'s ABI or API modes (releases 3.0.x and 3.1.x were using the ABI mode exclusively). At the time of writing the default is still the ABI mode but the choice can be controlled through the environment variable `RPY2_CFFI_MODE`. If set, possible values are `ABI` (default if the environment variable is not set), `API`, or `BOTH`. When the latter, both `API` and `ABI` modes are built, and the choice of which one to use can be made at run time. Changes ------- - The "consoleread" callback (reading input to the R console) is now assuming UTF-8 (was previously assuming ASCII) and is no longer trying to add a "new line" character at the end of the input. - Querying an R environment with an invalid key will generate a :class:`TypeError` or a :class:`ValueError` depending on the issue (rather than always :class:`ValueError` before. Bugs fixed ---------- - `setup.py` is now again compatible with Python2 (issue #580). - Unit tests were failing if numpy is not installed. - :mod:`rpy2.situation` is no longer breaking when R is not the in path and there is no environment variable `R_HOME`. - Build script for the cffi interface is now using the environment variable `R_HOME` whenever defined (rather that always infer it from the R in the PATH). - Converting R strings back to Python was incorrectly using `Latin1` while `UTF-8` was intended (issue #537). Release 3.1.0 ============= New features ------------ - Python matrix multiplication (`__matmul__` / `@`) added to R :class:`Matrix` objects. - An :class:`threading.RLock` is added to :mod:`rpy2.rinterface_lib.openrlib` and is used by the context manager :func:`rpy2.rinterface_lib.memorymanagement.rmemory` to ensure that protect/unprotect cycles cannot be broken by thread switching, at least as long as the context manager is used to handle such cycles (see issue #571). - The documentation covers the use of notebooks (mainly Jupyter/Jupyterlab). - The PNG output in Jupyter notebooks R cells can now specify an argument `--type` (passed as the named argument `type` in the R function `png`). For example on some Linux systems and R installations, the type `cairo` can fix issues when alpha transparency is used. Changes ------- - Added callbacks for `ptr_R_Busy()` and `ptr_R_ProcessEvents()`. - `rstart` now an objects in :mod:`rpy2.rinterface_lib.embedded` (set to `None` until R is initialized). - Unit tests are included in a subpackage :mod:`rpy2.tests` as was the case before release 3.0.0 (issue #528). - Experimental initialization for Microsoft Windows. - :mod:`rpy2.situation` is now also reporting the rpy2 version. - :func:`rpy2.robjecs.package_utils.default_symbol_check_after` was renamed :func:`rpy2.robjecs.package_utils.default_symbol_resolve`. The named parameters `default_symbol_check_after` present in few methods in :mod:`rpy2.robjects.packages` and :mod:`rpy2.robjects.functions` were modified to keep a consistent naming. - Trying to instantiate an :class:`rpy2.rlike.container.OrdDict` with a a :class:`dict` will result in a :class:`TypeError` rather than a :class:`ValueError`. - Methods of :class:`rpy2.rlike.container.OrdDict` now raises a :class:`NotImplementedError` when not implemented. - The creation of R vectors from Python sequences is now relying on a method :meth:`_populate_r_vector` that allows vectorized implementation to to improve speed. - Continuous integration tests run against Python 3.6, 3.7, and 3.8. It is no longer checked against Python 3.5. Bugs fixed ---------- - `aes` in :mod:`rpy2.robjects.lib.ggplot2` had stopped working with the R package ggplot2 reaching version 3.2.0. (issue #562). - Better handling of recent :mod:`pandas` arrays with missing values (related to issue #544). - The mapping of the R operator `%in%` reachable through the attribute `ro` of R vectors was always returning `True`. It is now working properly. - R POSIXct vectors with `NA` dates were triggering an error when converted in a data frame converted to :mod:`pandas` (issue #561). Release 3.0.5 ============= Bugs fixed ---------- - No longer allow installation if Python 3 but < 3.5. - Fixed error `undefined symbol: DATAPTR` if R < 3.5 (issue #565). Release 3.0.4 ============= Bugs fixed ---------- - Fixed conversion of `pandas` :class:`Series` of dtype `pandas.Int32Dtype`, or `pandas.Int64Dtype` (issue #544). Release 3.0.3 ============= Bugs fixed ---------- - Fixed the evaluation of R code using the "R magic" was delaying all output to the end of the execution of that code, independently of whether the attribute `cache_display_data` was `True` or `False` (issue #543). - Fixed conversion of :class:`pandas.Series` of `dtype` "object" when all items are either all of the same type or are :obj:`None` (issue #540). Release 3.0.2 ============= Bugs fixed ---------- - Failing to import `pandas` or `numpy` when loading the "R magic" extension for jupyter/ipython was hiding the cause of the error in the `ImportError` exception. - Fallback when an R `POSIXct` vector does not had an attribute `"tzone"` (issue #533). - Callback for console reset was not set during R initialization. - Fixed rternalized function returning rpy2 objects (issue #538). - `--vanilla` is no longer among the default options used to initialize R (issue #534). Release 3.0.1 ============= Bugs fixed ---------- - Script to install R packages for docker image never made it to version control. - Conversion of R arrays/matrices into numpy object trigged a segfault during garbage collection (issue #524). Release 3.0.0 ============= New features ------------ - rpy2 can be installed without a development environment. - Unit tests are now relying on the Python module `pytest`. - :attr:`rpy2.rinterface.NA_Integer` is now only defined when the embedded R is initialized. Changes ------- - complete rewrite of :mod:`rpy2.rinterface`. :mod:`cffi` is now used to interface with the R compiled shared library. This allows ABI calls and removes the need to compile binaries. However, if compilation is available (when installing or preparing pre-compiled binaries) faster implementations of performance bottlenecks will be available. - calling :func:`rpy2.rinterface.endr` multiple times is now only ending R the first time it is called (note: an ended R cannot successfully be re-initialized). - The conversion system in the mod:`rpy2.robjects.conversion` now has only two conversions `py2rpy` and rpy2py`. `py2rpy` tries to convert any Python object into an object rpy2 can use with R and `rpy2py` tries to convert any rpy2 object into a either a non-rpy2 Python object or a mod:`rpy2.robjects` level object. - The method `get` for R environments is now called `find()` to avoid confusion with the method of the same name in Python (:meth:`dict.get`). - :class:`rpy2.robjects.vectors.Vector`, :class:`rpy2.robjects.vectors.Matrix`, and :class:`rpy2.robjects.vectors.Array` can no longer be used to create R arrays of unspecified type. New type-specific classes (for example for vectors :class:`rpy2.robjects.vectors.IntVector`, :class:`rpy2.robjects.vectors.BoolVector`, :class:`rpy2.robjects.vectors.FloatVector`, :class:`rpy2.robjects.vectors.ComplexVector`, or :class:`rpy2.robjects.vectors.StrVector`) should be used instead. - mod:`rpy2.rpy_classic`, an implementation of the `rpy` interface using :mod:`rpy2.rinterface` is no longer available. - :class:`rpy2.robjects.ParsedCode` and :class:`rpy2.robjects.SourceCode` are moved to :class:`rpy2.robjects.packages.ParsedCode` and :class:`rpy2.robjects.packages.SourceCode`. Bugs fixed ---------- - Row names in R data frames were lost when converting to pandas data frames (issue #484). Known issues ------------ - Mismatch between R's POSIXlt `wday` and Python time struct_time's `tm_wday` (issue #523). Release 2.9.6 ============= Bugs fixed ---------- - Latest release of :mod:`pandas` deprecated :meth:`DataFrame.from_items`. (issue #514). - Latest release of :mod:`pandas` requires categories to be a list (not an other sequence). Known issues ------------ - The numpy buffer implemented by R arrays is broken for complex numbers Release 2.9.5 ============= Bugs fixed ---------- - Missing values in pandas :class:`Category` series were creating invalid R factors when converted (issue #493). Release 2.9.4 ============= Bugs fixed ---------- - Fallback for failure to import numpy or pandas is now dissociated from failure to import :mod:`numpy2ri` or :mod:`pandas2ri` (issue #463). - :func:`repr` for R POSIX date/time vectors is now showing a string representation of the date/time rather than the timestamp as a float (issue #467). - The HTML representation of R data frame (the default representation in the Jupyter notebook) was displaying an inconsistent number of rows (found while workin on issue #466). - Handle time zones in timezones in Pandas when converting to R data frames (issue #454). - When exiting the Python process, the R cleanup is now explicitly request to happen before Python's exit. This is preventing possible segfaults the process is terminating (issue #471). - dplyr method `ungroup()` was missing from :class:`rpy2.robjects.lib.dplyr.DataFrame` (issue #473). Release 2.9.3 ============= Bugs fixed ---------- - Delegate finding where is local time zone file to either a user-specified module-level variable `default_timezone` or to the third-party module :mod:`tzlocal` (issue #448). Release 2.9.2 ============= Changes ------- - The pandas converter is converting :class:`pandas.Series` of `dtype` `"O"` to :class:`rpy2.robjects.vectors.StrVector` objects, issueing a warning about it (See issue #421). - The conversion of pandas data frame is now working with columns rather than rows (introduce in bug fix for issue #442 below) and this is expected to result in more efficient conversions. Bugs fixed ---------- - Allow floats in figure sizes for R magic (Pull request #63) - Fixed pickling unpickling of robjects-level instances, regression introduced in fix for issue #432 with release 2.9.1 (issue #443). - Fixed broken unit test for columns of `dtype` `"O"` in `pandas` data frames. - Fixed incorrect conversion of R factors in data frames to columns of integers in pandas data frame (issue #442). Release 2.9.1 ============= Changes ------- - Fixing issue #432 (see Section Bugs fixed below) involved removed the method `__reduce__` previously provided for all rpy2 objects representing R objects. Bugs fixed ---------- - An error when installing with an unsupported R version was fixed (issue #420). - The docstring for `rinterface.endr()` was improperly stating that the function was not taking any argument (issue #423). - Target version of dplyr and tidyr are now 0.7.4 and 0.7.2 respectively. - Fixed memory leak when pickling objects (issue #432). Fixing the leak caused a slight change in the API (see Section Changes above). - Conversion to :mod:`pandas` now handling R ordered factor (issue #398). - :mod:`jinja2` was not listed as a dependency (issue #437). Release 2.9.0 ============= New features ------------ - New module :mod:`rpy2.situation` to extract and report informations about the environment, such as where is the R HOME, what is the version of R, what is the version of R rpy2 was built with, etc... The module is also designed to be run directly and provide diagnostics: `python -m rpy2.situation`. - :meth:`Environment.values`, :meth:`Environment.pop`, :meth:`Environment.popitems`, :meth:`Environment.clear` to match :meth:`dict.values`, :meth:`dict.pop`, :meth:`dict.popitems`, :meth:`dict.clear`. - :class:`VectorOperationsDelegator` now has a method `__matmul__` to implement Python's matrix multiplication operator (PEP-0645). - A rule to convert R POSIXct vectors to pandas Timestamp vectors was added (issue #418). - method :meth:`_repr_html_` for R vectors to display HTML in jupyter. Changes ------- - Starting several times the singleton :class:`EventProcessor` longer results in a :class:`RuntimeError`. This is now only a warning, addressing issue #182. - The target version for the R package `dplyr` mapped is now 0.7.1, and :func:`rpy2.robjects.lib.dplyr.src_dt` (issue #357) and :func:`rpy2.robjects.lib.dplyr.src_desc` are no longer present. - :meth:`Environment.keys` is now a iterator to match :meth:`dict.keys`, also an interator in Python 3. - Target version of `ggplot2` library is 2.2.1. - Option `stringsasfactors` in the constructor for the class `DataFrame`. If `False`, the strings are no longer converted to factors. When converting from pandas data frames the default is to no longer convert columns of strings to factors. - The R "magic" for jupyter is now more consistently using the conversion system, and the use of custom converters through the magic argument `-c` will work as expected. - Docker-related files moved to directory docker/ (where variants image for rpy2 are available) Bugs fixed ---------- - :func:`numpy.float128` is not available on all platforms. The unit test for it is now skipped on systems where it is not present (issue #347) - R pairlist objects can now be sliced (and issue #380 is resolved). - Passing parameters names that are empty string to R function was causing a segfault (issue #409). - Trying to build an atomic R vector from a Python object that has a length, but it not a sequence nor an iterator was causing a segfault (issue #407). Release 2.8.6 ============= Bugs fixed ---------- - Trying to build an atomic R vector from a Python object that has a length, but it not a sequence nor an iterator was causing a segfault (issue #407 - backport from rpy2-2.9.0). Release 2.8.5 ============= Bugs fixed ---------- - The defintion of the method :class:`rpy2.rlike.container.OrdDict.items` was incorrect, and so was the documentation for `rcall` (issue #383) - Giving an empty sequence to :meth:`robjects.sequence_to_vector` is now raising a :class:`ValueError` rather than fail with an :class:`UnboundLocalError` (see issue #388). - :meth:`robjects.robject.RSlots.items` is now working (see pull request #57). Release 2.8.4 ============= Bugs fixed ---------- - The context manager :func:`rpy2.robjects.lib.grdevices.render_to_file` is no longer trying to impose a file name generated by :mod:`tempfile` (issue #371) - The symbol `LISTSXP` (corresponding to R pairlist objects) was not imported from the C module in rpy2. - The functions `scale_linetype_discrete` and `scale_linetype_continuous` in ggplot2 were not wrapped by :mod:`rpy2.robjects.lib.ggplot2` (issue #381) Release 2.8.3 ============= Bugs fixed ---------- - Fixed the error when the transformation of R "man" pages into Python docstrings was failing when the section "arguments" was missing (issue #368) - Failing to find R in the PATH during the installation of rpy2 is now printing an error message instead of a warning (issue #366) Release 2.8.2 ============= Bugs fixed ---------- - R's `dplyr::src_dt` was moved to `dtdplyr::src_dt` with `dplyr` release 0.5.0. To address this, `src_dt` will become a `None` if the R package `dplyr` is discovered to be of version >= 0.5.0 at runtime. (issue #357) - Conversion issue when R symbols were accessed as attribute of the singleton :class:`rpy2.robjects.R`. (issue #334) - The `rmagic` extension for `ipython` was no longer loading with the latest ipython (version 5.0.0). (issue #359) Changes ------- - The fix to issue #357 (see bugs fixed above) was expanded to cover all R packages wrapped in :mod:`rpy2.robjects.lib` and ensure that the respective Python modules can loaded even if symbols are no longer defined in future versions of the corresponding R packages. Release 2.8.1 ============= New features ------------ - `Dockerfile` with automated build on dockerhub (https://hub.docker.com/r/rpy2/rpy2) Bugs Fixed ---------- - Trying to install rpy2 while R is not in the `PATH` resulted in an error in `setup.py`. Release 2.8.0 ============= New features ------------ - New class :class:`rpy2.robjects.SourceCode`. The class extends Python's :class:`str` and is meant to represent R source code. An HTML renderer for the ipython notebook (syntax highlighting using :mod:`pygment` is also added). - New module :mod:`rpy2.robjects.lib.tidyr` providing a custom wrapper for the R library `tidyr` - The long-deprecated functions :func:`rpy2.rinterface.set_writeconsole` and :func:`rpy2.rinterface.get_writeconsole` are no longer available. One of :func:`rpy2.rinterface.set_writeconsole_regular` / :func:`rpy2.rinterface.set_writeconsole_warnerror` or :func:`rpy2.rinterface.get_writeconsole_regular` / :func:`rpy2.rinterface.get_writeconsole_warnerror` respectively should be used instead. - The attribute :attr:`rpy2.robjects.RObject.slots` can now be implictly interated on (the method :meth:`__iter__` is now an alias for :meth:`keys`). - The default Python-R conversion is now handling functions. This means that Python function can directly be used as parameters to R functions (when relevant). - Ipython display hook `display_png` for ggplot2 graphics. - :mod:`pandas` "category" vectors are better handled by the pandas conversion. - New module :mod:`rpy2.robjects.lib.grdevices` providing a custom wrapper for the R library 'grDevices', exposing few key functions in the package and providing context managers (`render_to_file` and `render_to_bytesio`) designed to simplify the handling of static plots (e.g., webserver producing graphics on the fly or figure embedded in a Jupyter notebook). - Numpy conversion is handling better arrays with `dtype` equal to `"O"` when all objects are either all inheriting from :class:`str` or from :class:`bytes`. Such arrays are now producing :class:`StrSexpVector` or :class:`BytesSexpVector` objects respectively. - R's own printing of warnings if now transformed to warnings of type `rinterface.RRuntimeWarning` (it used to be a regular `UserWarning`) - The family of functions `src_*` and the function `tbl` in the R package `dplyr` have aliases in the module :mod:`rpy2.robjects.lib.dplyr`, and a class :class:`DataSource` has been added for convenience. - :class:`rpy2.robjects.vectors.DataFrame` has a method `head` corresponding to R's method of the same name. The method takes the n first row of a data frame. - dplyr's functions `count_` and `tally` are now exposed as methods for the class :class:`dplyr.DataFrame`. Changes ------- - Building/installing rpy2 with a development version of R does not require the use of option `--ignore-check-rversion` any longer. A warning is simply issue when the R version is "development". - On MSWindows, the dependency on `pywin32` was removed (issue #315) - :class:`GroupedDataFrame` in the dplyr interface module is now inheriting from the definition of DataFrame in that same module (it was previously inheriting from :class:`robjects.vectors.DataFrame`). - The default `repr()` for R objects is now printing the R classes (as suggested in issue #349). Bugs Fixed ---------- - Parameter names to R function that are in UTF-8 are no longer causing a segfault (issue #332) - Looking for a missing key in an R environment (using `__getitem__` or `[`) could raise a `LookupError` instead of a `KeyError`. - R environment can now handle unicode keys as UTF-8 (was previously trying Latin1) - rpy2 is interrupting attempts to install with Python < 2.7 with an informative error message (issue #338) - Setting the R class can be done by using a simple Python string (issue #341) - `rpy2.robjects.lib.grid.viewport` is now returning an instance of class `Viewport` (defined in the same module) (issue #350) Release 2.7.9 ============= Bug fixed --------- - Python objects exposed to R could lead to segfault when the Python process is exiting (issue #331) Release 2.7.8 ============= Bugs fixed ---------- - American English spelling was missing for some of the function names to specify colour (color) scales. - Fix for printing R objects on Windows (pull request #47) Release 2.7.7 ============= Bugs fixed ---------- - Pickling `robjects`-level objects resulted in `rinterface`-level objects when unpickled (issue #324). Release 2.7.6 ============= Changes ------- - :mod:`rpy2.robjects.lib.ggplot2` was modified to match the newly released ggplot2-2.0.0. This is introducing API-breaking changes, which breaks the promise to keep the API stable through bugfix releases within series, but without it 2.7.x will not a work with new default installation of the R package ggplot2. Release 2.7.5 ============= Bugs fixed ---------- - Division and floordivision through the delegator `.ro` provided with R vectors wrapped by `robjects`. (issue #320) - Memory leak when unserializing (unpickling) R objects bundled in Python objects (issue #321) Release 2.7.4 ============= Bugs fixed ---------- - Python 3.5 highlighted slightly incorrect C-level flags in rpy2 objects declarations, and :mod:`rpy2.robjects` could not be imported. - Fixed unit tests for rmagic when :mod:`numpy` is not installed, and for :mod:`numpy` is installed by :mod:`pandas` in missing. Release 2.7.3 ============= Bugs fixed ---------- - method :meth:`DataFrame.collect` in :mod:`rpy2.robjects.lib.dplyr` was not functioning. - Applied patch by Matthias Klose to fix implict pointer conversions. - :mod:`pandas2ri.ri2py_dataframe` is now propagating the row names in the R data frame into an index in the pandas data frame (issue #285) - methods `union`, `intersect`, `setdiff`, `ungroup` defined in the R package `dplyr` were missing from the `DataFrame` definition in :mod:`rpy2.robjects.lib.dplyr` Release 2.7.2 ============= Bugs fixed ---------- - methods `distinct`, `sample_n`, and `sample_frac` defined in the R package `dplyr` were missing from the `DataFrame` definition in :mod:`rpy2.robjects.lib.dplyr` - The fix for the inheritance problem with :mod:`rpy2.robjects.lib.dplyr.DataFrame` introduced a regression whenever `group_by` is used. - The methods to perform joins on dplyr `DataFrame` objects where not working properly. Release 2.7.1 ============= Bugs fixed ---------- - The :meth:`__repr__` for :mod:`robjects`-level vectors was broken for vectors of length 1 (issue #306) - The ipython notebook-based sections of the documentation were not building - Classes inheriting from :mod:`dplyr.DataFrame` had dplyr methods returning objects of their parent class. Release 2.7.0 ============= New features ------------ - New exception :class:`rpy2.rinterface.RParsingError`. Errors occurring when parsing R code through :func:`rpy2.rinterface.parse` raise this exception (previously :class:`rpy2.rinterface.RRuntimeError`). - New class :class:`rpy2.robjects.conversion.Converter` to replace the `namedtuple` of the same name - New class :class:`rpy2.robjects.converter.ConversionContext`. This is a context manager allowing an easy setting of local conversion rules. The constructor has an alias called :meth:`rpy2.robjects.constructor.localconverter`. - New module :mod:`rpy2.robjects.lib.dplyr` providing a custom wrapper for the R library `dplyr` - Method :meth:`Environment.items()` to iterate through the symbols and associated objects in an R environment. - Exception :class:`rpy2.rinterface.ParsingIncompleError`, a child class of :class:`rpy2.rinterface.ParsingError`, raised when calling :meth:`rpy2.rinteface.parse` results in R's C-level status to be `PARSE_INCOMPLETE`. This can make the Python implementation of an IDE for R easier. - Attribute :attr:`slots` for :mod:`rpy2.robjects`-level objects. The attribute is a :class:`rpy2.robjects.Rslots` which behaves like a Python mapping to provide access to R-attributes for the object (see issue #275). - The R "magic" for ipython `%%R` can be passed a local converter (see new features above) by using `-c`. Bugs fixed ---------- - Conversion rules were not applied when parsing and evaluating string as R with :class:`rpy2.robjects.R`. - Calling the constructor for :class:`rpy2.robjects.vectors.FactorVector` with an R factor is no longer making a copy, loosing the associated R attributes if any (fixes issue #299). - `rpy2` could crash when R was unable to dynamically load the C extension for one of its packages (noticed with issue #303). Changes ------- - :func:`rpy2.rinterface.is_initialized` is now a function. - :meth:`rpy2.robjects.R.__call__` is now calling R's `base::parse()` to parse the string rather the parser through R's C-API. The workaround let's us retrieve R's error message in case of failure (see issue #300) Release 2.6.3 ============= Bug fixed --------- - Metaclass `RS4Auto_Type` facilitating the creation of Python classes from R S4 classes was not handling classes without methods (issue #301) Release 2.6.2 ============= Bugs fixed ---------- - Check that R >= 3.2 is used at build time (issue #291) - Conversion rules were not applied when parsing and evaluating string as R code with :class:`rpy2.robjects.R`. Release 2.6.1 ============= New features ------------ - Because of their long names, the classes :class:`SignatureTranslatedAnonymousPackage`, :class:`SignatureTranslatedPackage`, and :class:`SignatureTranslatedFunction` in :mod:`rpy2.robjects.packages` have now the aliases :class:`STAP`, :class:`STP`, and :class:`STF` respectively. Bugs fixed ---------- - Typo in function name emitting warnings at build time (issue #283) - The conversion of `TaggedList` instances is now handling the names of items in the list (issue #286) Changes ------- - Loading the `ipython` extension in the absence of `pandas` or `numpy` is now issuing a warning (issue #279) Release 2.6.0 ============= New features ------------ - Report the existence during build time of a file `.Renviron`, or the definition of the environment variables `R_ENVIRON' or `R_ENVIRON_USER` with a warning. (issue #204) - Moved console writting callback to use `ptr_R_WriteConsoleEx` rather than `ptr_R_WriteConsole`. This allows callbacks for warnings and messages. `get/set_writeconsole` is now replaced by `get/set_writeconsole_regular` (regular output) and `get/set_writeconsole_warnerror` (warning and error). In order to conserve backward compatibility an alias for `get/set_writeconsole_regular` called `get/set_writeconsole` is provided. - Added callback for `ptr_R_ResetConsole`. - :mod:`pandas` :class:`Categorical` objects are automatically handled in the pandas converter. - The translation of R symbols into Python symbols used in `importr` and underlying classes and methods can be customized with a callback. The default translation turning `.` into `_` is `default_symbol_r2python`. - Translation of named arguments in R function is now sharing code with the translation of R symbols (see point above), providing a consistent way to perform translations. - Utility function `sequence_to_vector` in `robjects` to convert Python sequences (e.g., `list` or `tuple`) to R vector without having to specify the type (the type is inferred from the list). - :mod:`robjects.vectors` object have a property :attr:`NAvalue` that contains the `NA` value for the vector, allowing generic code on R vectors. For example, testing whether any vector contains `NA` can be written as `any(x is myvector.NAvalue for x in myvector)`. Making numpy /masked/ array is an other application. Changes ------- - The automatic name translation from R to Python used in `importr` is now slightly more complex. It will not only translate `.` to `_` but should a conflict arise from the existence in R of both the `.` and `_` versions the `.` version will be appended a `_` (in accordance with :pep:0008). The change was discussed in issue #274). - The ipython 'R magic' is now starting with a default conversion mode that is `pandas2ri` if it can find it, then `numpy2ri` if it can find it, and then the basic conversion. - R vectors are now typed at the C level (IntSexpVector, FloatSexpVector, ListSexpVector, etc...) whenever retrieving them from the embedded R with the low-level `rinterface`. This is facilitating dispatch on vector type (e.g., with `singledispatch` now used for the conversion system). Bugs fixed ---------- - The evaluation of R code through R's C-level function `tryEval` caused console output whenever an error occurred. Moving to the seemingly experimental `tryEvalSilent` makes evaluations less verbose. - Multiple plots in one ipython cell (pull request #44) Release 2.5.7 ============= - `simplegeneric` was moved of ipython 4.0.0 (pull request #43) Release 2.5.6 ============= Bugs fixed ---------- - Detection of the R version during setup on Win8 (issues #255 and #258) - Segmentation fault when converting :mod:`pandas` :class:`Series` with elements of type object (issue #264) - The default converter from Python (non-rpy2) objects to rinterface-level objects was producing robjects-level objects whenever the input was of type :class:`list` (discovered while fixing issue #264) - Implemented suggested fix for issue with unlinking files on Windows (issue #191) - Testing rpy2 in the absence of ipython no longer stops with an error (issue #266) Release 2.5.5 ============= Bugs fixed ---------- - Crash (segfault) when querying an R object in an R environment triggers an error (symbol exists, but associated values resolves to an error - issue #251) - Change in the signature of `rcall` was not updated in the documentation (issue #259) - Minor update to the documentation (issue #257) Release 2.5.4 ============= Bugs fixed ---------- - Filter PNG files on size, preventing empty files causing trouble to be ipython notebook rendering of graphics later on (slight modification of the pull request #39) - Fix installation left unresolved with rpy2-2.5.3 (issue #248) - Possible segfault with Python 3.4 (issue #249) Release 2.5.3 ============= Changes ------- - `setup.py` has `install_requires` in addition to `requires` in the hope to fix the missing dependency with Python 2 (:mod:`singledispatch` is required but not installed). Bugs fixed ---------- - Extracting configuration information from should now work when R is emitting a warning (issue #247) - On OS X the library discovery step can yield nothing (see issue #246). A tentative fix is to issue a warning and keep moving. Release 2.5.2 ============= Bugs fixed ---------- - String representation of :class:`robjects.R` (issue #238) - Check during `build_ext` if unsupported version of R (pull request #32) - HTMl display of columns of factors in a DataFrame (issue #236) - HTML display of factors (issue #242) Release 2.5.1 ============= Bugs fixed ---------- - Require singledispatch if Python 3.3 (issue #232) - Fixed bug when R spits out a warning when asked configuration information (issue #233) - Restored printing of compilation information when running `setup.py` - Fixed installation issue on some systems (issue #234) - Workaround obscure failure message from unittest if Python < 3.4 and :mod:`singledispatch` cannot be imported (issue #235) Release 2.5.0 ============= New features ------------ - Experimental alternative way to preserve R objects from garbage collection. This can be activated with `rinterface.initr(r_preservehash=True)` (default is `False`. - :class:`GGPlot` object getting a method :meth:`save` mirroring R's `ggplot2::ggsave()`. - The conversion system is now using generics/single dispatch. - New module :mod:`rpy2.ipython.html` with HTML display for rpy2 objects - [Experimental] New function :func:`robjects.methods.rs4instance_factory` to type RS4 objects with more specificity. Changes ------- - The script `setup.py` was rewritten for clarity and ease of maintenance. Now it only uses `setuptools`. Release 2.4.4 ============= Bugs fixed ---------- - Use `input` rather than `raw_input` in the default console callback with Python 3 (fixes issue #222) - Issues with conversions, pandas, and rmagic (fixes issue #218 and more) Release 2.4.3 ============= Bugs fixed ---------- - `geom_raster` was missing from `rpy2.robjects.lib.ggplot2` (pull request #30) - Fixed issue with SVG rendering in ipython notebook (issue #217) - Regression with `rx2()` introduced with new conversion (issue #219) - Fixed documentation (missing `import`) (issue #213) Release 2.4.2 ============= Bugs fixed ---------- - Assigning an R `DataFrame` into an environment was failing if the conversion for Pandas was activated. (Issue #207) Release 2.4.1 ============= Bugs fixed ---------- - :meth:`rpy2.ipython` fixed spurious output to notebook cells. Release 2.4.0 ============= Changes ------- - Conversion system slightly changed, with the optional conversions for :mod:`numpy` and :mod:`pandas` modified accordingly. The changes should only matter if using third-party conversion functions. - The Python 3 version is now a first class citizen. `2to3` is no longer used, and the code base is made directly compatible with Python. This lowers significantly the installation time with Python 3 (which matters when developping rpy2). - The default options to initialize R (`rpy2.rinterface.initoptions') are no longer `('rpy2', '--quiet', '--vanilla', '--no-save')` but now `('rpy2', '--quiet', '--no-save')`. - :class:`robjects.vectors.ListVector` can be instanciated from any objects with a method `items()` with the expectation that the method returns an iterable of (name, value) tuples, or even be an iterable of (name, value) tuples. New features ------------ - For instances of :class:`rpy2.robjects.Function`, the `__doc__` is now a property fetching information about the parameters in the R signature. - Convenience function :func:`rpy2.robjects.packages.data` to extract the datasets in an R pacakges - :mod:`ipython`'s `rmagic` is now part of :mod:`rpy`. To use, `%load_ext rpy2.ipython` from within IPython. - new method :meth:`rpy2.rinterface.SexpEnvironment.keys`, returnings the names in the environment as a tuple of Python strings. - convenience class :class:`robjects.packages.InstalledPackages`, with a companion function :func:`robjects.packages.isinstalled`. - new class :class:`rinterface.SexpSymbol` to represent R symbols Bugs fixed ---------- - :meth:`rpy2.rinterface.Sexp.do_slot` was crashing when the parameter was an empty string (PR #155) Release 2.3.10 ============== Bugs fixed ---------- - `setup.py build` was broken when new R compiled with OpenMP (Issue #183) Release 2.3.9 ============= - Changes in pandas 0.13.0 broke the rpy2 conversion layer (Issue #173) Release 2.3.8 ============= Bugs fixed ---------- - Crash with R-3.0.2. Changes in R-3.0.2's C API coupled to a strange behaviour with R promises caused the problem. (PR #150) Release 2.3.7 ============= Bugs fixed ---------- - ggplot2's "guides" were missing - ggplot2's "theme_classic" was missing (PR #143) - ggplot2's "element_rect" was missing (PR #144) - :func:`rpy2.interactive.packages` was broken (PR #142) Release 2.3.6 ============= Bugs fixed ---------- - Several reports of segfault on OS X (since rpy2-2.3.1 - PR #109) - More fixes in converting `DataFrames` with dates from `pandas` Relase 2.3.5 ============ Bugs fixed ---------- - Missing mapping to ggplot2's `scale_shape_discrete` function - Better handling of dates in Pandas - Constructor for POSIXct improved (and fixed) Changes ------- - The attribute :attr:`rclass` is no longer read-only and can be set (since R allows it) - Importing the module :mod:`rpy2.interactive` no longer activates event processing by default (triggering concurrency errors when used with ipython). New features ------------ - New module :mod:`rpy2.interactive.ipython` (so far plotting automatically a ggplot2 figure in the iPython's console) - It is now possible to set the :attr:`rclass`. Relase 2.3.4 ============ Bugs fixed ---------- - Spurious error when running unit tests with Python 3 and numpy installed - Missing mapping to ggplot2's `geom_dotplot` function - Warnings are not longer printed (see Changes below) Changes ------- - Bumped target version of ggplot2 to 0.9.3.1 - Warnings are not longer printed. The C-level function in R became hidden in R-3.0, and the cost of an R-level check/print is relatively high if the R code called is very short. This might evolve into printing warnings only if interactive mode in Python (if this can be checked reliably). Release 2.3.3 ============= Bugs fixed ---------- - Some of the data.frames converted from :mod:`pandas` were triggering a :class:`TypeError` when calling :func:`repr` - In :mod:`rpy2.robjects.lib.ggplot2`, a mapping to `coord_fixed` was missing (PR #120) - Using the parameter `lib_loc` in a call to :func:`rpy2.robjects.packages.importr` was resulting in an error (PR #119) - Creating a `layer` through the `rpy2.robjects.lib.ggplot2` interface did not accept parameters (PR #122) - Testing the Python version was crashing of a number of unsupported Python versions (<= 2.6) (PR #117) New features ------------ - New module pandas2ri to convert from mod:`pandas` `DataFrame` objects - New classes :class:`rpy2.robjects.lib.grid.Unit` and :class:`rpy2.robjects.lib.grid.Gpar` to model their counterparts in R's `grid` package as they were previously missing from rpy2. Release 2.3.2 ============= Bug fixed --------- - Building on Win64 (pull request #6) - Fetching data from an R package through `importr` was masking any R object called `data` in that package. The data are now under the attribute name `__rdata__`. This is not completely safe either, although much less likely, a warning will be issued if still masking anything. Changes ------- - More informative error message when failing to build because `R CMD config` does not return what is expected Release 2.3.1 ============= Bugs fixed ---------- - default console print callback with Python (issue #112 linked to it) - deprecation warnings with ggplot2 (issue #111 and contributed patch) Release 2.3.0 ============= New Features ------------ :mod:`rpy2.rinterface`: - C-level API, allowing other C-level modules to make use of utilities without going through the Python level. The exact definition of the API is not yet fixed. For now there is PyRinteractive_IsInitialized() to assess whether R was initialized (through :mod:`rpy2.rinterface` or not). - C-module _rpy_device, allowing one to implement R graphical devices in Python [(very) experimental] - Tracking of R objects kept protected from garbage collection by rpy2 is now possible. - New method :meth:`Sexp.rid` to return the identifier of the R object represented by a Python/rpy2 object :mod:`rpy2.rinteractive`: - Dynamic build of Python docstrings out of the R manual pages :mod:`rpy2.robjects.help`: - Build dynamic help :mod:`rpy2.robjects.packages`: - Build anonymous R packages from strings - When using :func:`importr`, the datasets are added as an attribute :attr:`data`, itself an instance of a new class :class:`PackageData`. It no longer possible to access datasets are regular objects from a code package (because of changes in R), and the new system is more robust against quirks. Changes ------- :mod:`rpy2.rinterface`: - :attr:`SexpClosure.env` to replace the method `closureenv`. Release 2.2.6 ============= Bugs fixed ---------- - Newest R-2.15 and ggplot2 0.9 broke the ggplot2 interaface in :mod:`rpy2.robjects.lib.ggplot2` Release 2.2.5 ============= Bugs fixed ---------- - install process: Library location for some of the R installations - should compile on win32 (thanks to a patch from Evgeny Cherkashin), a work to a limited extend Release 2.2.4 ============= Bugs fixed ---------- - Memory leak when creating R vectors from Python (issue #82) Release 2.2.3 ============= Bugs fixed ---------- - Dynamic construction of S4 classes was looking for R help as 'class.' rather than '-class' - The cleanup of temporary directories created by R was not happening if the Python process terminated without calline :func:`rpy2.rinterface.endr()` (issue #68, and proof-of-principle fix by chrish42) Release 2.2.2 ============= Bugs fixed ---------- - With the robjects layer, repr() on a list containing non-vector elements was failing Release 2.2.1 ============= Bugs fixed ---------- - MANIFEST.in was missing from MANIFEST.in, required with Python 3 Release 2.2.0 ============= New Features ------------ - Support for Python 3, and for some of its features ported to Python 2.7 :mod:`rpy2.robjects`: - :meth:`Environment.keys` to list the keys - classes :class:`robjects.vectors.POSIXlt` and :class:`robjects.vectors.POSIXlt` to represent vectors of R dates/time - :func:`packages.get_packagepath` to get the path to an R package - module :mod:`rpy2.robjects.help` to expose the R help system to Python - Metaclass utilities in :mod:`rpy2.robjects.methods`, allowing to reflect automatically R S4 classes as Python classes. - :meth:`rpy2.robjects.vectors.FactorVector.iter_labels` to iterate over the labels - :class:`rpy2.robjects.vectors.ListVector` to represent R lists. - Constructor for :class:`rpy2.robjects.vectors.ListVector` and :class:`rpy2.robjects.vectors.DataFrame` accept any iterable at the condition that the elements iterated through also valid subscripts for it (e.g., given an iterable v, the following is valid: .. code-block:: python x[k] for x in v :mod:`rpy2.rinterface`: - :data:`NA_Complex` and :class:`NAComplexType` for missing complex values. - :class:`SexpExtPtr` to represent R objects of type EXTPTR (external pointers). - :func:`rpy2.rinterface.parse` to parse a string a R code - :func:`rpy2.rinterface.rternalise` to wrap Python function as :class:`SexpClosure` that can be called by R just as it was a function of its own. - :class:`rpy2.rinterface.RNULLType` for R's C-level NULL value and :class:`rpy2.rinterface.UnboundValueType` for R's C-level R_UnboundValue (both singletons). - :meth:`rinterface.SexpVector.index`, of similar behaviour to :meth:`list.index`. - :meth:`rpy2.rinterface.Sexp.list_attrs` to list the names of all R attributes for a given object. - :class:`rpy2.rinterface.ByteSexpVector` to represent R 'raw' vectors. - constant `R_LEN_T_MAX` to store what is the maximum length for a vector in R. - tuple `R_VERSION_BUILD` to store the version of R rpy2 was built against - getter :attr:`Sexp.rclass` to return the R class associated with an object :mod:`rpy2.rlike`: - :class:`container.OrdDict` get proper methods :meth:`keys` and `get` :mod:`rpy2.interactive`: - A new sub-package to provide utilities for interactive work, either for handling R interactive events or use Python for interactive programming (as often done with the R console) Changes ------- :mod:`rpy2.robjects`: - NA_bool, NA_real, NA_integer, NA_character and NA_complex are now deprecated (and removed). NA_Logical, NA_Real, NA_Integer, NA_Character, NA_Complex should be used. - :class:`rpy2.robjects.packages.Package` now inherits from :class:`types.ModuleType` - classes representing R vector also inherit their type-specific rinterface-level counterpart. - Importing the :class:`rpy2.robjects.numpy2ri` is no longer sufficient to active the conversion. Explicit activation is now needed; the function `activate` can do that. :mod:`rpy2.rinterface`: - :class:`IntSexpVector`, :class:`FloatSexpVector`, :class:`StrSexpVector`, :class:`BoolSexpVector`, :class:`ComplexSexpVector` are now defined at the C level, improving performances and memory footprint whenever a lot of instances are created. Bugs fixed ---------- - Better and more explicit detection system for needed libraries when compiling rpy2 (ported to release 2.1.6) - Long-standing issue with readline fixed (issue #10) Release 2.1.9 ============= Bugs fixed ---------- - The R class in rpy2.robjects is now truly a singleton - When using numpy 1.5 and Python >= 2.7, the exposed buffer for R numerical (double) vectors or arrays was wrong. Release 2.1.8 ============= Bugs fixed ---------- - Fixed issue with R arrays with more than 2 dimensions and numpy arrays (issue #47 - backported from the branch 2.2.x). Release 2.1.7 ============= Bugs fixed ---------- - More fixes for the automated detection of include and libraries at build time. Release 2.1.6 ============= Bugs fixed ---------- - Further fixes in the automatic detection of includes and libraries needed to compile rpy2 against R. The detection code has been refactored (backport from the 2.2.x branch) Release 2.1.5 ============= Bugs fixed ---------- - fixes the automatic detection of R_HOME/lib during building/compiling when R_HOME/lib is not in lib/ (issue #54) Release 2.1.4 ============= New features ------------ - :mod:`rpy2.robjects.lib.ggplot2` now has the functions :func:`limits`, :func:`xlim`, :func:`ylim` exposed (patch contributed anonymously) Bugs fixed ---------- - Install script when the BLAS library used by R is specified as a library file (patch by Michael Kuhn) Release 2.1.3 ============= Bugs fixed ---------- - Spurious error message when using DataFrame.from_csvfile() without specifying col_names or row_names - Patch to finally compile with Python < 2.6 (contribDuted by Denis Barbier) Release 2.1.2 ============= New Features ------------ :mod:`rpy2.robjects`: - NA_Logical, NA_Real, NA_Integer, NA_Character from :mod:`rpy2.rinterface` are imported by robjects. Changes ------- :mod:`rpy2.robjects`: - NA_bool, NA_real, NA_integer, NA_character and NA_complex are now robjects-level vectors (they were rinterface-level vectors). Consider using the rinterface-defined NAs instead of them. Bugs fixed ---------- - Missing conditional C definition to compile with Python 2.4 # issue 38 - Fixed error when calling robjects.vectors.Vector.iteritems() on an R vector without names - Fixed automatic conversion issues (issue #41) Release 2.1.1 ============= Bugs fixed ---------- - Issues with NA values # issue 37 - Missing manual scale functions in :mod:`rpy2.robjects.lib.ggplot2` # issue 39 Release 2.1.0 ============= New Features ------------ :mod:`rpy2.robjects`: - Method :meth:`formals` for :class:`Function` (formerly *RFunction*) - Methods :meth:`slotnames`, :meth:`isclass`, and :meth:`validobject` for :class:`RS4` - Vector-like objects now in a module :mod:`rpy2.robjects.vectors` - :func:`set_accessors` for adding simply accessors to a class inheriting from :class:`RS4` - :class:`RS4_Type` for metaclass-declared accessors - Delegating classes :class:`ExtractDelegator` and :class:`DoubleExtractDelegator` for extracting the R-way - :class:`DataFrame` (formerly *RDataFrame*) can now be created from :`rlike.container.OrdDict` instances, or any other object inheriting from dict. - :class:`FactorVector` to represent R factors - the conversion is now returning subclasses of :class:`robjects.vectors.Vector` -formerly *RVector*- (such as :class:`IntVector`, :class:`FloatVector`, etc...) rather than only return :class:`Vector` - :class:`StrVector` has a method :meth:`factor` to turn a vector of strings into an R factor - :class:`Matrix` was added the methods: :meth:`dot`, :meth:`svd`, :meth:`crossprod`, :meth:`tcrossprod`, :meth:`transpose`. - :meth:`IntVector.tabulate` to count the number of times a value is found in the vector - :meth:`Vector.sample` to draw a (random) sample of arbitrary size from a vector - :data:`NA_Bool`, :data:`NA_Real`, :data:`NA_Integer`, :data:`NA_Character`, :data:`NA_Complex` as aliases for R's missing values. - :data:`ComplexVector` for vectors of complex (real + imaginary) elements - :mod:`packages` to provide utility functions to handle R packages (import of R packages) - :mod:`functions` to provide classes related to R functions, with the new class :class:`SignatureTranslatedFunction` - :meth:`DataFrame.iter_row` and :meth:`DataFrame.iter_column`, iterating through rows and columns respectively. - :meth:`DataFrame.cbind` and :meth:`DataFrame.rbind` for binding columns or rows to a DataFrame. - :meth:`Vector.iteritems` to iterate on pairs of names and values. - :attr:`Robject.__rname__` to store the "R name" :mod:`rpy2.rinterface`: - New functions for specifying callback functions for R's front-ends: :func:`set_showmessage`, :func:`set_flushconsole`, :func:`set_choosefile`, :func:`set_showfiles` - New object :data:`MissingArg`, exposing R's special object for representing a "missing" parameter in a function call. (#this was first a patch by Nathaniel Smith with a function getMissingArgSexp) - Initial commit of a callback-based implementation of an R graphical device (this is for the moment very experimental - and not fully working) - :meth:`SexpClosure.rcall` is now taking 2 parameters, a tuple with the parameters and an :class:`SexpEnvironment` in which the call is to be evaluated. - :attr:`Sexp.__sexp__` now has a setter method. This permits the rebinding of the underlying R SEXP, and allows to expose `foo<-` type of R methods as Python function/methods with side effects. - Objects of R type RAWSXP are now exposed as instances of class :class:`SexpVector`. - Factory function :func:`unserialize` to build Sexp* instances from byte string serialized with R's own 'serialize'. - Method :meth:`Sexp.__reduce__` for pickling/unpickling - Ability to specify a callback function for R_CleanUp (called upon exiting R) through :func:`get_cleanup` and :func:`set_cleanup` [very experimental] - Class :class:`ListSexpVector` for creating R lists easily (complementing :class:`IntSexpVector`, :class:`StrSexpVector`, and friends) - :meth:`colnames`, :meth:`rownames` for :class:`Array` (formerly *RArray*) are now property-style getters - Pairlists (LISTSXP) now handled - Experimental function :func:`set_interactive` to set whether R is in interactive mode or not (#following an issue reported by Yaroslav Halchenko) - New object :data:`R_NilValue`, exposing R's special object for representing a "NULL". - :data:`ComplexSexpVector` for vectors of complex (real + imaginary) elements - Scalar Python parameters of type :class:`int`, :class:`long`, :class:`double`, :class:`bool`, and :class:`None` in a call (using :class:`SexpClosure`) are now automatically converted to length-one R vectors (at the exception of None, converted to R_NilValue). - Python slices can now be used on R vector-like objects - Better handling of R's missing values NA, `NA_integer_`, `NA_real_`, and `NA_character_`. :mod:`rpy2.rlike`: - :meth:`iteritems` for :class:`OrdDict` (formerly:class:`ArgDict`) and :class:`TaggedList` - static method :meth:`from_iteritems` for :class:`TaggedList`, for creating a TaggedList from any object having a method :meth:`iteritems` Changes ------- - The setup.py script is now taking command-line arguments when specifying R library-related paths are wished. python setup.py --help build_ext will list them :mod:`rpy2.robjects`: - RS4 no longer makes R's slots as Python attributes through :meth:`__attr__` - The package is split into modules - The broken variables NA_STRING, NA_INTEGER, NA_LOGICAL, and NA_REAL are removed. The documentation on missing values was revised. - :data:`globalEnv` and :data:`baseNameSpaceEnv` were renamed to :data:`globalenv` and :data:`baseenv` respectively - The parameter *wantFun* in :meth:`Environment.get` (formerly *REnvironment.get()*) is now *wantfun* - :attr:`Vector.r` does not have a __getitem__ method any longer (see in `.rx` and `.rx2` in the new features) - :meth:`colnames`, :meth:`rownames`, :meth:`nrow`, :meth:`ncol` for :class:`DataFrame` are now property-style getters - :meth:`nrow`, :meth:`ncol` for :class:`Array` are now property-style getters - static method :meth:`from_csvfile` and instance method :meth:`to_csvfile` for :class:`DataFrame` - module :mod:`lib` to store modules representing R packages - module :mod:`lib.ggplot2` for the CRAN package ggplot2. - renaming of few classes, the *R* prefix: :class:`Formula` (from *RFormula*), :class:`DataFrame` (from *RDataFrame*), :class:`Array` (from *RArray*), :class:`Matrix` (from *RMatrix*), :class:`Environment` (from *REnvironment*), :class:`Function` (from *RFunction*), :class:`Vector` (from *RVector*). - :class:`robjects.vectors.Vector` lost the (now redundant) methods `subset` and `assign`. Those operations were just aliases to the :class:`ExtractDelegator` :mod:`rpy2.rinterface`: - :data:`globalEnv`, :data:`baseNameSpaceEnv`, and :data:`emptyEnv` were renamed to :data:`globalenv`, :data:`baseenv` and :data:`emptyenv` respectively - The parameter *wantFun* in :meth:`SexpEnvironment.get` is now *wantfun* - The call-back getters and setters are now :func:`get_readconsole`, :func:`set_readconsole`, :func:`get_writeconsole`, :func:`set_writeconsole`, :func:`get_flushconsole`, and :func:`set_flushconsole`. - Functions also accept named parameters equal to Py_None, and transform them to R NULL (previously only accepted parameters inheriting from Sexp). :mod:`rpy2.rlike`: - :class:`ArgDict` becomes :class:`OrdDict`. - :meth:`tags` of :class:`TaggedList` is now a property (with a getter and a setter) :mod:`rpy2.rpy_classic`: - R named lists are returned as Python :class:`dict`, like rpy-1.x does it, with the notable difference that duplicate names are not silently overwritten: an exception of class :class:`ValueError` is thrown whenever happening Bugs fixed ---------- - :meth:`REnvironment.get` now accepts a named parameter *wantFun* (like :meth:`rinterface.SexpEnvironment` does) - :class:`rinterface.SexpVector` will now properly raise an exception when trying to create vector-like object of impossible type - Crash when trying to create a SexpVector of a non-vector type - R objects of class *matrix* are now properly converted into :class:`RMatrix` (instead of :class:`Array`) - :meth:`Robj.as_py` was not working at all (and now it does to some extent) Release 2.0.7 ============= Bugs fixed ---------- - On win32, printing an object was leaving an open file handle behind each time, leading to an error and the impossibility to print (# bug report and fix by Christopher Gutierrez) Release 2.0.6 ============= No user-visible change. Win32-specific additions to the C module were made to compile it. Release 2.0.5 ============= Bugs fixed ---------- - Crash when calling :meth:`SexpEnvironment.get` with an empty string #bug report by Walter Moreira - :meth:`SexpEnvironment.__getitem__` called with an empty string caused unpredictable (and bad) things Release 2.0.4 ============= Bugs fixed ---------- - Added missing named parameter *wantfun* to method :meth:`REnvironment.get` (making it similar to :meth:`SexpEnvironment.get`) - Leak in reference counting when creating SexpVector objects fixed (the symptom was a process growing in size when creating R vector from Python list or numpy arrays) - `R CMD config LAPACK_LIBS` could return an empty string when R was compiled with the veclib framework, causing the setup.py script to raise an exception. setup.py now only print a message about an empty string returned from R CMD config - Numpy arrays with complex elements are no longer causing segfaults - Calls to :meth:`SexpClosure.rcall` with something else that the expected kind of tuple could cause a segfault Release 2.0.3 ============= New Features ------------ :mod:`rpy2.rinterface`: - :meth:`process_revents`, a Wrapper for R_ProcessEvents (# suggested by June Kim to help with issues related to interactive display on win32), and for R_RunHandlers on UNIX-like systems (# patch by Nathaniel Smith). - All callbacks are getting a get to complement the set. (# Patch by Nathaniel Smith) - :meth:`Sexp.__deepcopy__` to copy an object (calling Rf_Duplicate) (# from a patch by Nathaniel Smith) Changes ------- - the default for reading and writing the console are now using sys.stdin and sys.stdout (# patch submitted by Nathaniel Smith) - console IO callbacks (reading and writing) are complemented by one to flush the console - :meth:`Sexp.do_slot_assign` now creates the slot if missing (design-fix - # patch by Nathaniel Smith) Bugs fixed ---------- - fixed problem of numpy interface with R boolean vectors. They are now presented as 'i' rather than 'b' to numpy (# patch submitted by Nathaniel Smith) - The mechanism for setting arbitrary callaback functions for console I/O now ensures that a traceback is printed to stderr whenever an error occurs during the evalutation of the callback (the raised exception used to be silently propagated to the next python call, leading to problems). Release 2.0.2 ============= Bugs fixed ---------- - Fix installation bug when the include directories contain either '-' or 'I' #spotted by James Yoo - Failing to initialize R now throws a RuntimeError - Copying an R "NA" into Python returns a None (and no longer a True) (#fixes a bug reported by Jeff Gentry) Release 2.0.1 ============= New features ------------ :mod:`rpy2.robjects`: - Property `names` for the :class:`RVector` methods :meth:`getnames` and :meth:`setnames` (this was likely forgotten for Release 2.0.0). - Property `rclass` for :class:`RObjectMixin` Changes ------- :mod:`rpy2.robjects`: - :meth:`rclass` becomes :meth:`getrclass` Bugs fixed ---------- - Having the environment variable R_HOME specified resulted in an error when importing :mod:`rpy2.rinterface` # root of the problem spotted by Peter - Setup.py has no longer a (possibly outdated) static hardcoded version number for rpy2 - Testing no longer stops with an error in the absence of the third-party module :mod:`numpy` - :meth:`rpy2.rlike.container.TaggedList.pop` is now returning the element matching the given index Release 2.0.0 ============= New features ------------ - New module :mod:`rpy2.robjects.conversion`. - New module :mod:`rpy2.robjects.numpy2ri` to convert :mod:`numpy` objects into :mod:`rpy2` objects. # adapted from a patch contributed by Nathaniel Smith Changes ------- - :meth:`RObject.__repr__` moved to :meth:`RObject.r_repr` Bugs fixed ---------- - Informative message returned as RuntimeError when failing to find R's HOME - Use the registry to find the R's HOME on win32 # snatched from Peter's earlier contribution to rpy-1.x Release 2.0.0rc1 ================ :mod:`rpy2.rpy_classic`: - :meth:`rpy_classic.RObj.getSexp` moved to a property :attr:`rpy_classic.Robj.sexp`. :mod:`rpy2.robjects`: - :meth:`RObject.__repr__` moved to :meth:`RObject.r_repr` - :meth:`ri2py`, :meth:`ro2py`, and :meth:`py2ri` moved to the new module :mod:`conversion`. Adding the prefix `conversion.` to calls to those functions will be enough to update existing code Bugs fixed ---------- - Informative message returned as RuntimeError when failing to find R's HOME - Use the registry to find the R's HOME on win32 # snatched from Peter's earlier contribution to rpy-1.x Release 2.0.0rc1 ================ New features ------------ - added :data:`__version__` to rpy2/__init__.py :mod:`rpy2.robjects`: - added classes :class:`StrVector`, :class:`IntVector`, :class:`FloatVector`, :class:`BoolVector` :mod:`rpy2.rinterface`: - added missing class :class:`BoolSexpVector`. Changes ------- :mod:`rpy2.robjects`: - does not alias :class:`rinterface.StrSexpVector`, :class:`rinterface.IntSexpVector`, :class:`rinterface.FloatSexpVector` anymore - Constructor for :class:`rpy2.robjects.RDataFrame` checks that R lists are data.frames (not all lists are data.frame) - Formerly new attribute :attr:`_dotter` for :class:`R` is now gone. The documentaion now points to :mod:`rpy2.rpy_classic` for this sort of things. Bugs fixed ---------- - conditional typedef in rinterface.c to compile under win32 # reported and initial proposed fix from Paul Harrington - __pow__ was missing from the delegator object for robjects.RVector (while the documentation was claiming it was there) # bug report by Robert Nuske - Earlier change from Sexp.typeof() to getter Sexp.typeof was not reflected in :mod:`rpy2.rpy_classic` # bug report by Robert Denham Release 2.0.0b1 =============== New features ------------ :mod:`rpy2.robjects`: - added :meth:`setenvironment` for :class:`RFormula`, and defined `environment` as a property - defined `names` as a property for :class:`RVector` :mod:`rpy2.rinterface`: - added functions :func:`get_initoptions` and :func:`set_initoptions`. - new attribute :attr:`_dotter` for :class:`R` singleton. Setting it to True will translate '_' into '.' if the attribute is not found Changes ------- :mod:`rpy2.robjects`: - constructor for RDataFrame now now accepts either :class:`rlike.container.TaggedList` or :class:`rinterface.SexpVector` :mod:`rpy2.rinterface`: - :func:`sexpTypeEmbeddedR` is now called :func:`str_typeint`. - :attr:`initOptions` is now called :attr:`initoptions`. Changes of options can only be done through :func:`set_initoptions`. Bugs fixed ---------- - crash of :meth:`Sexp.enclos` when R not yet initialized (bug report #2078176) - potential crash of :meth:`Sexp.frame` when R not yet initialized - proper reference counting when handling, and deleting, :attr:`Sexp.__sexp__` generated CObjects - setup.py: get properly the include directories (no matter where they are) #bug report and fix adapted from Robert Nuske - setup.py: link to external lapack or blas library when relevant - added a MANIFEST.in ensuring that headers get included in the source distribution #missing headers reported by Nicholas Lewin-Koh - :func:`rinterface.str_typeint` was causing segfault when called with 99 - fixed subsetting for LANGSXP objects Release 2.0.0a3 =============== New features ------------ :mod:`rpy2.rinterface`: - :func:`setReadConsole`: specify Python callback for console input - `R` string vectors can now be built from Python unicode objects - getter :attr:`__sexp__` to return an opaque C pointer to the underlying R object - method :meth:`rsame` to test if the underlying R objects for two :class:`Sexp` are the same. - added `emptyEnv` (R's C-level `R_EmptyEnv`) - added method :meth:`Sexp.do_slot_assign` :mod:`rpy2.robjects`: - R string vectors can now be built from Python unicode objects :mod:`rpy2.rlike`: - module :mod:`functional` with the functions :func:`tapply`, :func:`listify`, :func:`iterify`. - module :mod:`indexing` with the function :func:`order` - method :meth:`TaggedList.sort` now implemented Changes ------- :mod:`rpy2.rinterface`: - :func:`initEmbeddedR` is only initializing if R is not started (no effect otherwise, and no exception thrown anymore) - the method :meth:`Sexp.typeof` was replaced by a Python `getter` :attr:`typeof`. - the method :meth:`Sexp.named` was replaced by a Python `getter` :attr:`named`. - R objects of type LANGSXP are now one kind of vector (... but this may change again) - R objects of type EXPRSXP are now handled as vectors (... but this may change again) - :func:`initEmbeddedR` renamed to :func:`initr` - :func:`endEmbeddedR` renamed to :func:`endr` :mod:`rpy2.robjects`: - :class:`R` remains a singleton, but does not throw an exception when multiple instances are requested Bugs fixed ---------- - unable to compile on Python2.4 (definition of aliases to Python2.5-specific were not where they should be). - overflow issues on Python 2.4/64 bits when indexing R vector with very large integers. - handling of negative indexes for :class:`SexpVector`'s :meth:`__getitem__` and :meth:`__setitem__` was missing - trying to create an instance of :class:`SexpVector` before initializing R raises a RuntimeException (used to segfault) - experimental method :meth:`enclos` was not properly exported - setup.py was exiting prematurely when R was compiled against an existing BLAS library - complex vectors should now be handled properly by :mod:`rpy2.rinterface.robjects`. - methods :meth:`rownames` and :meth:`colnames` for :class:`RDataFrame` were incorrect. Release 2.0.0a2 =============== New features ------------ :mod:`rpy2.rlike`: - package for R-like features in Python - module :mod:`rpy2.rlike.container` - class :class:`ArgsDict` in :mod:`rpy2.rlike.container` - class :class:`TaggedList` in :mod:`rpy2.rlike.container` :mod:`rpy2.rinterface`: - method :meth:`named`, corresponding to R's C-level NAMED - experimental methods :meth:`frame` and :meth:`enclos` for SexpEnvironment corresponding to R's C-level FRAME and ENCLOS - method :meth:`rcall` for :class:`ClosureSexp` - new experimental class :class:`SexpLang` for R language objects. Bugs fixed ---------- - R stack checking is disabled (no longer crashes when multithreading) - fixed missing R_PreserveObject for vectors (causing R part of the object to sometimes vanish during garbage collection) - prevents calling an R function when R has been ended (raise :class:`RuntimeException`). Release 2.0.0a1 =============== New features ------------ :mod:`rpy2.robjects`: - method :meth:`getnames` for :class:`RVector` - experimental methods :meth:`__setitem__` and :meth:`setnames` for :class:`RVector` - method 'getnames' for :class:`RArray` - new class :class:`RFormula` - new helper class :class:`RVectorDelegator` (see below) - indexing RVector the "R way" with subset is now possible through a delegating attribute (e.g., myvec.r[True] rather than myvec.subset(True)). #suggested by Michael Sorich - new class :class:`RDataFrame`. The constructor :meth:`__init__` is still experimental (need for an ordered dictionnary, that will be in before the beta - filled documentation about mapping between objects Changes ------- - many fixes and additions to the documentation - improved GTK console in the demos - changed the major version number to 2 in order to avoid confusion with rpy 1.x # Suggested by Peter and Gregory Warnes - moved test.py to demos/example01.py :mod:`rpy2.robjects`: - changed method name `getNames` to `getnames` where available (all lower-case names for methods seems to be the accepted norm in Python). Bugs fixed ---------- :mod:`rpy2.robjects`: - fixed string representation of R object on Microsoft Windows (using fifo, not available on win32) - :meth:`__getattr__` for :class:`RS4` is now using :meth:`ri2py` :mod:`rpy2.rinterface`: - fixed context of evaluation for R functions (now R_GlobalEnv) Release 1.0a0 ============= - first public release ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8779466 rpy2-3.5.15/PKG-INFO0000644000175100001770000001063014543120757013213 0ustar00runnerdockerMetadata-Version: 2.1 Name: rpy2 Version: 3.5.15 Summary: Python interface to the R language (embedded R) Author-email: Laurent Gautier License: GPLv2+ Project-URL: Homepage, https://rpy2.github.io Project-URL: Documentation, https://rpy2.github.io/doc.html Project-URL: Source, https://github.com/rpy2/rpy2 Project-URL: Tracker, https://github.com/rpy2/rpy2/issue Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 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: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE License-File: AUTHORS Requires-Dist: cffi>=1.10.0 Requires-Dist: jinja2 Requires-Dist: tzlocal Requires-Dist: packaging; platform_system == "Windows" Requires-Dist: typing-extensions; python_version < "3.8" Requires-Dist: backports.zoneinfo; python_version < "3.9" Provides-Extra: test-minimal Requires-Dist: pytest; extra == "test-minimal" Requires-Dist: coverage; extra == "test-minimal" Requires-Dist: pytest-cov; extra == "test-minimal" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: ipython; extra == "test" Requires-Dist: numpy; extra == "test" Requires-Dist: pandas>=1.3.5; extra == "test" Provides-Extra: numpy Provides-Extra: pandas Requires-Dist: numpy; extra == "pandas" Requires-Dist: pandas>=1.3.5; extra == "pandas" Provides-Extra: types Requires-Dist: mypy; extra == "types" Requires-Dist: types-tzlocal; extra == "types" Provides-Extra: all Requires-Dist: pytest; extra == "all" Requires-Dist: ipython; extra == "all" Requires-Dist: pandas>=1.3.5; extra == "all" Requires-Dist: numpy; extra == "all" # Python -> R bridge [![pypi](https://img.shields.io/pypi/v/rpy2.svg?style=flat-square)](https://pypi.python.org/pypi/rpy2) [![Codecov](https://codecov.io/gh/rpy2/rpy2/branch/master/graph/badge.svg)](https://codecov.io/gh/rpy2/rpy2) [![GH Actions](https://github.com/rpy2/rpy2/workflows/Python%20package/badge.svg)](https://github.com/rpy2/rpy2/actions?query=workflow%3A%22Python+package%22) The project's webpage is here: https://rpy2.github.io/ # Installation `pip` should work out of the box: ```bash pip install rpy2 ``` The package has optional depencies providing specific functionalities not otherwise required to use the rest of rpy2. For example, to be able to run the unit tests: ```bash pip install rpy2[test] ``` To install all optional dependencies (numpy, pandas, ipython), use: ```bash pip install rpy2[all] ``` The package is known to compile on Linux, MacOSX (provided that developper tools are installed, and you are ready figure out how by yourself). The situation is currently a little more complicated on Windows. Check the issue tracker. In case you find yourself with this source without any idea of what it takes to compile anything on your platform, try first ```bash python setup.py install ``` ## Issues loading shared C libraries Whenever R is in not installed in a system location, the system might not know where to find the R shared library. If `R` is in the `PATH`, that is entering `R` on the command line successfully starts an R terminal, but rpy2 does not work because of missing C libraries, try the following before starting Python: ```bash export LD_LIBRARY_PATH="$(python -m rpy2.situation LD_LIBRARY_PATH)":${LD_LIBRARY_PATH} ``` # Documentation Documentation is available either in the source tree (`doc/`), or [online](https://rpy2.github.io/doc.html). ## Testing `rpy2` uses `pytest`, with the plugin `pytest-cov` for code coverage. To test the package from the source tree, either to check and installation on your system or before submitting a pull request, do: ```bash pytest tests/ ``` For code coverage, do: ```bash pytest --cov=rpy2.rinterface_lib \ --cov=rpy2.rinterface \ --cov=rpy2.ipython \ --cov=rpy2.robject \ tests ``` For more options, such as how to run specify tests, please refer to the `pytest` documentation. # License RPy2 can be used under the terms of the GNU General Public License Version 2 or later (see the file gpl-2.0.txt). This is the very same license R itself is released under. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/README.md0000644000175100001770000000506014543120705013367 0ustar00runnerdocker# Python -> R bridge [![pypi](https://img.shields.io/pypi/v/rpy2.svg?style=flat-square)](https://pypi.python.org/pypi/rpy2) [![Codecov](https://codecov.io/gh/rpy2/rpy2/branch/master/graph/badge.svg)](https://codecov.io/gh/rpy2/rpy2) [![GH Actions](https://github.com/rpy2/rpy2/workflows/Python%20package/badge.svg)](https://github.com/rpy2/rpy2/actions?query=workflow%3A%22Python+package%22) The project's webpage is here: https://rpy2.github.io/ # Installation `pip` should work out of the box: ```bash pip install rpy2 ``` The package has optional depencies providing specific functionalities not otherwise required to use the rest of rpy2. For example, to be able to run the unit tests: ```bash pip install rpy2[test] ``` To install all optional dependencies (numpy, pandas, ipython), use: ```bash pip install rpy2[all] ``` The package is known to compile on Linux, MacOSX (provided that developper tools are installed, and you are ready figure out how by yourself). The situation is currently a little more complicated on Windows. Check the issue tracker. In case you find yourself with this source without any idea of what it takes to compile anything on your platform, try first ```bash python setup.py install ``` ## Issues loading shared C libraries Whenever R is in not installed in a system location, the system might not know where to find the R shared library. If `R` is in the `PATH`, that is entering `R` on the command line successfully starts an R terminal, but rpy2 does not work because of missing C libraries, try the following before starting Python: ```bash export LD_LIBRARY_PATH="$(python -m rpy2.situation LD_LIBRARY_PATH)":${LD_LIBRARY_PATH} ``` # Documentation Documentation is available either in the source tree (`doc/`), or [online](https://rpy2.github.io/doc.html). ## Testing `rpy2` uses `pytest`, with the plugin `pytest-cov` for code coverage. To test the package from the source tree, either to check and installation on your system or before submitting a pull request, do: ```bash pytest tests/ ``` For code coverage, do: ```bash pytest --cov=rpy2.rinterface_lib \ --cov=rpy2.rinterface \ --cov=rpy2.ipython \ --cov=rpy2.robject \ tests ``` For more options, such as how to run specify tests, please refer to the `pytest` documentation. # License RPy2 can be used under the terms of the GNU General Public License Version 2 or later (see the file gpl-2.0.txt). This is the very same license R itself is released under. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8499465 rpy2-3.5.15/doc/0000755000175100001770000000000014543120757012663 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/doc/Makefile0000644000175100001770000002034314543120705014316 0ustar00runnerdocker# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . _dummy := $(shell mkdir -p generated_rst) .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \'make \' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " rpy2demo_graphics to build the figures used in the documentation about graphics" @echo " rpy2demo_benchmark to build the figure(s) used in the documentation about benchmarks" @echo " notebooks to build the ipython notebooks used in the documentation" clean: rm -rf $(BUILDDIR)/* rpy2demo_benchmark: @cd _static/demos && python benchmarks.py rpy2demo_graphics: @cd _static/demos && python graphics.py rpy2demo_all: rpy2demo_graphics rpy2demo_benchmark # rule to build ipython notebooks from markdown notebooks/%.ipynb : notebooks/%.md notedown \ --precode 'from functools import partial' \ 'from rpy2.ipython import html' \ 'html.html_rdataframe=partial(html.html_rdataframe, table_class="docutils")' \ --run \ -o $@ \ $< cp -p $@ _static/notebooks/ # rule to build sphinx-friendly ReST from ipython notebooks notebooks/%.rst : notebooks/%.ipynb @cd generated_rst && jupyter nbconvert --to rst ../$< --output-dir=. # rule to build HTML render of an ipython notebook _static/notebooks/%.html : notebooks/%.ipynb jupyter nbconvert --to html $< --output ../$@ MD_NOTEBOOKS := $(basename $(notdir $(wildcard notebooks/*.md))) _ensure_notebooks_dir: mkdir -p _static/notebooks _notebooks: $(MD_NOTEBOOKS:%=notebooks/%.rst) _notebooks_html: $(MD_NOTEBOOKS:%=_static/notebooks/%.html) _notebooks_rst: $(MD_NOTEBOOKS:%=notebooks/%.rst) _notebooks_parents: cp -u generated_wrapper_rst/notebooks.rst generated_rst/ notebooks: _ensure_notebooks_dir _notebooks _notebooks_html _notebooks_rst _notebooks_parents html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rpy2.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rpy2.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/rpy2" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rpy2" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/gpl-2.0.txt0000644000175100001770000004325414543120705013737 0ustar00runnerdocker GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/pyproject.toml0000644000175100001770000000454314543120705015031 0ustar00runnerdocker[build-system] requires = [ "setuptools >= 61", "wheel", "cffi>=1.15.0", "packaging;platform_system=='Windows'", ] build-backend = "setuptools.build_meta" [project] name = "rpy2" description = "Python interface to the R language (embedded R)" readme = "README.md" requires-python = ">=3.7" license = { text = "GPLv2+" } authors = [{ name = "Laurent Gautier", email = "lgautier@gmail.com" }] classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Intended Audience :: Developers", "Intended Audience :: Science/Research", ] dependencies = [ "cffi>=1.10.0", "jinja2", "tzlocal", "packaging;platform_system=='Windows'", "typing-extensions;python_version<'3.8'", "backports.zoneinfo;python_version<'3.9'" ] dynamic = ["version"] [project.optional-dependencies] test_minimal = ["pytest", "coverage", "pytest-cov"] test = ["pytest", "ipython", "numpy", "pandas>=1.3.5"] numpy = [] pandas = ["numpy", "pandas>=1.3.5"] types = ["mypy", "types-tzlocal"] all = ["pytest", "ipython", "pandas>=1.3.5", "numpy"] [project.urls] Homepage = "https://rpy2.github.io" Documentation = "https://rpy2.github.io/doc.html" Source = "https://github.com/rpy2/rpy2" Tracker = "https://github.com/rpy2/rpy2/issue" [tool.setuptools] packages = [ "rpy2", "rpy2.rlike", 'rpy2.rinterface_lib', 'rpy2.robjects', 'rpy2.robjects.lib', 'rpy2.interactive', 'rpy2.ipython', 'rpy2.tests', 'rpy2.tests.rinterface', 'rpy2.tests.rlike', 'rpy2.tests.robjects', 'rpy2.tests.ipython', 'rpy2.tests.robjects.lib', ] # zip_safe = false # not supported as of setuptools==62.3.2 [tool.setuptools.dynamic] version = { attr = "rpy2.__version__" } # [tool.setuptools.package_data] # rpy2 = [ # 'rpy2/rinterface_lib/R_API.h', # 'rpy2/rinterface_lib/R_API_eventloop.h', # 'rpy2/rinterface_lib/R_API_eventloop.c', # 'rpy2/rinterface_lib/RPY2.h', # 'rpy2/rinterface_lib/_bufferprotocol.c', # 'py.typed', # ] # not supported as of setuptools==62.3.2 [tool.pytest.ini_options] minversion = "6.0" testpaths = ["rpy2/tests"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/requirements.txt0000644000175100001770000000010514543120705015367 0ustar00runnerdockercffi>=1.10.0 jinja2 pytz tzlocal packaging;platform_system=='Windows'././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8499465 rpy2-3.5.15/rpy2/0000755000175100001770000000000014543120757013012 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/__init__.py0000644000175100001770000000013414543120705015112 0ustar00runnerdocker__version_vector__ = (3, 5, 15) __version__ = '.'.join(str(x) for x in __version_vector__) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/_rinterface_cffi_build.py0000644000175100001770000002246314543120705020013 0ustar00runnerdockerimport cffi # type: ignore import os import re import sys import warnings import situation # preloaded in setup.py # from rpy2.rinterface_lib import ffi_proxy import importlib spec = importlib.util.spec_from_file_location( 'rinterface_lib', './rpy2/rinterface_lib/ffi_proxy.py') ffi_proxy = importlib.util.module_from_spec(spec) sys.modules['ffi_proxy'] = ffi_proxy spec.loader.exec_module(ffi_proxy) IFDEF_PAT = re.compile('^#ifdef (.+) ?.*$') ELSE_PAT = re.compile('^#else ?.*$') ENDIF_PAT = re.compile('^#endif ?.*$') DEFINE_PAT = re.compile('^#define +([^ ]+) +([^ ]+) *$') def _c_preprocess_block(csource, definitions={}, rownum=0): localdefs = definitions.copy() block = [] for row in csource: rownum += 1 m = DEFINE_PAT.match(row) if m: localdefs[m.group(1)] = m.group(2) continue m_ifdef = IFDEF_PAT.match(row) if m_ifdef: subblock, subdefs = _c_preprocess_ifdef( csource, m_ifdef.group(1) in localdefs, definitions=localdefs, rownum=rownum) block.extend(subblock) definitions.update(subdefs) continue m_else = ELSE_PAT.match(row) if m_else: return ('else', block, definitions) m_endif = ENDIF_PAT.match(row) if m_endif: return ('endif', block, definitions) for k, v in localdefs.items(): if isinstance(v, str): row = row.replace(k, v) block.append(row) def _c_preprocess_ifdef(csource, want_block_a, definitions={}, rownum=0): ending, block_a, defs_a = _c_preprocess_block( csource, definitions=definitions, rownum=rownum) if ending == 'else': ending, block_b, defs_b = _c_preprocess_block( csource, definitions=definitions, rownum=rownum) else: block_b = '' defs_b = definitions assert ending == 'endif' if want_block_a: return (block_a, defs_a) else: return (block_b, defs_b) def c_preprocess(csource, definitions={}, rownum=0): """ Rudimentary C-preprocessor for ifdef blocks. Args: - csource: iterator C source code - definitions: a mapping (e.g., set or dict contaning which "names" are defined) Returns: The csource with the conditional ifdef blocks for name processed. """ localdefs = definitions.copy() block = [] for row in csource: rownum += 1 m = DEFINE_PAT.match(row) if m: localdefs[m.group(1)] = m.group(2) continue m_ifdef = IFDEF_PAT.match(row) if m_ifdef: name = m_ifdef.group(1) subblock, subdefs = _c_preprocess_ifdef(csource, name in localdefs, definitions=localdefs, rownum=0) block.extend(subblock) localdefs.update(subdefs) continue for k, v in localdefs.items(): if isinstance(v, str): row = row.replace(k, v) block.append(row) return block, rownum def define_rlen_kind(ffibuilder, definitions): if ffibuilder.sizeof('size_t') > 4: # The following was defined in the first cffi port, # and they in the C-extension version. They should # be ported to the header. # LONG_VECTOR_SUPPORT = True # R_XLEN_T_MAX = 4503599627370496 # R_SHORT_LEN_MAX = 2147483647 definitions['RPY2_RLEN_LONG'] = True def define_osname(definitions): if os.name == 'nt': definitions['OSNAME_NT'] = True def create_cdef(definitions, header_filename): cdef = [] with open( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'rinterface_lib', header_filename) ) as fh: cdef, _ = c_preprocess(fh, definitions=definitions, rownum=0) return ''.join(cdef) def read_source(src_filename): with open( os.path.join( os.path.dirname(os.path.realpath(__file__)), 'rinterface_lib', src_filename) ) as fh: cdef = fh.read() return cdef def createbuilder_abi(): ffibuilder = cffi.FFI() definitions = {} define_rlen_kind(ffibuilder, definitions) define_osname(definitions) r_h = read_source('R_API.h') # TODO: is R_INTERFACE_PTRS defined for all non-Windows systems? if not os.name == 'nt': definitions['R_INTERFACE_PTRS'] = True cdef_r, _ = c_preprocess( iter(r_h.split('\n')), definitions=definitions, rownum=1) ffibuilder.set_source('_rinterface_cffi_abi', None) ffibuilder.cdef('\n'.join(cdef_r)) return ffibuilder def createbuilder_api(): ffibuilder = cffi.FFI() definitions = {} define_rlen_kind(ffibuilder, definitions) define_osname(definitions) # TODO: is R_INTERFACE_PTRS defined for all non-Windows systems? if not os.name == 'nt': definitions['R_INTERFACE_PTRS'] = True r_h = read_source('R_API.h') eventloop_h = read_source('R_API_eventloop.h') eventloop_c = read_source('R_API_eventloop.c') rpy2_h = read_source('RPY2.h') r_home = situation.get_r_home() if r_home is None: sys.exit('Error: rpy2 in API mode cannot be built without R in ' 'the PATH or R_HOME defined. Correct this or force ' 'ABI mode-only by defining the environment variable ' 'RPY2_CFFI_MODE=ABI') c_ext = situation.CExtensionOptions() c_ext.add_lib( *(situation.get_r_flags(r_home, '--ldflags')) ) c_ext.add_lib( *(situation.get_r_libs(r_home, 'BLAS_LIBS')) ) c_ext.add_include( *situation.get_r_flags(r_home, '--cppflags') ) c_ext.extra_link_args.append( f'-Wl,-rpath,{situation.get_rlib_rpath(r_home)}' ) if 'RPY2_RLEN_LONG' in definitions: definitions['RPY2_RLEN_LONG'] = True ffibuilder.set_source( '_rinterface_cffi_api', eventloop_c + rpy2_h, libraries=c_ext.libraries, library_dirs=c_ext.library_dirs, # If we were using the R headers, we would use # include_dirs=c_ext.include_dirs. include_dirs=['rpy2/rinterface_lib/'], define_macros=list(definitions.items()), extra_compile_args=c_ext.extra_compile_args, extra_link_args=c_ext.extra_link_args) callback_defns_api = '\n'.join( x.extern_python_def for x in [ ffi_proxy._capsule_finalizer_def, ffi_proxy._evaluate_in_r_def, ffi_proxy._consoleflush_def, ffi_proxy._consoleread_def, ffi_proxy._consolereset_def, ffi_proxy._consolewrite_def, ffi_proxy._consolewrite_ex_def, ffi_proxy._showmessage_def, ffi_proxy._choosefile_def, ffi_proxy._cleanup_def, ffi_proxy._showfiles_def, ffi_proxy._processevents_def, ffi_proxy._busy_def, ffi_proxy._callback_def, ffi_proxy._yesnocancel_def, ffi_proxy._parsevector_wrap_def, ffi_proxy._handler_def, ffi_proxy._exec_findvar_in_frame_def ]) cdef_r, _ = c_preprocess( iter(r_h.split('\n')), definitions=definitions, rownum=1) ffibuilder.cdef('\n'.join(cdef_r)) ffibuilder.cdef(rpy2_h) ffibuilder.cdef(callback_defns_api) cdef_eventloop, _ = c_preprocess( iter(eventloop_h.split('\n')), definitions={'CFFI_SOURCE': True}, rownum=1) ffibuilder.cdef('\n'.join(cdef_eventloop)) ffibuilder.cdef('void rpy2_runHandlers(InputHandler *handlers);') return ffibuilder # This sort of redundant with setup.py defining cffi_modules, # but at least both use situation.get_ffi_mode(). cffi_mode = situation.get_cffi_mode() ffibuilder_api = None ffibuilder_abi = None if cffi_mode in (situation.CFFI_MODE.ABI, situation.CFFI_MODE.BOTH): ffibuilder_abi = createbuilder_abi() elif cffi_mode == situation.CFFI_MODE.ANY: try: ffibuilder_abi = createbuilder_abi() except Exception as e: warnings.warn(str(e)) ffibuilder_abi = None if cffi_mode in (situation.CFFI_MODE.API, situation.CFFI_MODE.BOTH): ffibuilder_api = createbuilder_api() elif cffi_mode == situation.CFFI_MODE.ANY: try: ffibuilder_api = createbuilder_api() except Exception as e: warnings.warn(str(e)) ffibuilder_api = None if __name__ == '__main__': if cffi_mode in (situation.CFFI_MODE.ABI, situation.CFFI_MODE.BOTH): ffibuilder_abi.compile(verbose=True) elif cffi_mode == situation.CFFI_MODE.ANY: try: ffibuilder_api.compile(verbose=True) except Exception as e: warnings.warn(str(e)) if cffi_mode in (situation.CFFI_MODE.API, situation.CFFI_MODE.BOTH): ffibuilder_api.compile(verbose=True) elif cffi_mode == situation.CFFI_MODE.ANY: try: ffibuilder_api.compile(verbose=True) except Exception as e: warnings.warn(str(e)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8539467 rpy2-3.5.15/rpy2/interactive/0000755000175100001770000000000014543120757015327 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/interactive/__init__.py0000644000175100001770000000267414543120705017442 0ustar00runnerdocker""" Package to interface with R, with a focus on interactive usage (REPL approach to code-writing), although the package will work in non-interactive settings. The package aims at being simple rather than exhaustively complete (rpy2.robjects, or rpy2.rinterface, can be used if needed), providing a comfortable experience with autocompletion-capable python consoles. """ from collections import OrderedDict from rpy2.robjects.packages import _loaded_namespaces from rpy2.robjects.vectors import IntVector, FloatVector, ComplexVector from rpy2.robjects.vectors import Array, Matrix from rpy2.robjects.vectors import StrVector from rpy2.robjects.vectors import ListVector, DataFrame from rpy2.robjects.environments import Environment from rpy2.rinterface import NULL from rpy2.robjects import Formula, RS4 from rpy2.robjects import methods from rpy2.robjects import conversion from rpy2.robjects import help as rhelp from rpy2.robjects.language import eval from . import process_revents as revents from os import linesep import re class S4Classes(object): """ *Very* experimental attempt at getting the S4 classes dynamically mirrored. """ __instance = None def __new__(cls): if cls.__instance is None: cls.__instance = object.__new__(cls) return cls.__instance def __setattr__(self, name, value): raise AttributeError("Attributes cannot be set. Use 'importr'") #classes = S4Classes() #revents.start() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/interactive/packages.py0000644000175100001770000000313214543120705017447 0ustar00runnerdockerfrom rpy2.robjects.packages import importr as _importr from rpy2.robjects.packages import data import rpy2.robjects.help as rhelp from rpy2.rinterface import baseenv from os import linesep from collections import OrderedDict import re class Packages(object): __instance = None def __new__(cls): if cls.__instance is None: cls.__instance = object.__new__(cls) return cls.__instance def __setattr__(self, name, value): raise AttributeError("Attributes cannot be set. Use 'importr'") packages = Packages() _loaded_namespaces = baseenv['loadedNamespaces'] def importr(packname, newname = None, verbose = False): """ Wrapper around rpy2.robjects.packages.importr, adding the following feature(s): - package instance added to the pseudo-module 'packages' """ assert isinstance(packname, str) packinstance = _importr(packname, on_conflict = 'warn') # fix the package name (dots possible in R package names) if newname is None: newname = packname.replace('.', '_') Packages().__dict__[newname] = packinstance ## Currently too slow for a serious usage: R's introspection ## of S4 classes is not fast enough # d = {} # for cn in methods.get_classnames(packname): # class AutoS4(RS4): # __metaclass__ = methods.RS4Auto_Type # __rpackagename__ = packname # __rname__ = cn # newcn = cn.replace('.', '_') # d[newcn] = AutoS4 # S4Classes().__dict__[newname] = d return packinstance for packname in _loaded_namespaces(): importr(packname) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/interactive/process_revents.py0000644000175100001770000000334514543120705021123 0ustar00runnerdocker"""This module runs continuous updates for R, such as redrawing graphs when the plot window is resized. Use the start() and stop() functions to turn updates on and off. """ from rpy2.rinterface import process_revents import time import warnings import threading class _EventProcessorThread(threading.Thread): """ Call rinterface.process_revents(), pausing for at least EventProcessor.interval between calls. """ _continue = True def run(self): while self._continue: process_revents() time.sleep(EventProcessor.interval) class EventProcessor(object): """ Processor for R events (Singleton class) """ interval = 0.2 daemon_thread = True name_thread = 'rpy2_process_revents' _thread = None _instance = None def __new__(cls): if cls._instance is None: cls._instance = object.__new__(cls) return cls._instance def start(self): """ start the event processor """ if (self._thread is not None) and (self._thread.is_alive()): raise warnings.warn("Processing of R events already started.") else: self._thread = _EventProcessorThread(name = self.name_thread) self._thread.setDaemon(self.daemon_thread) self._thread.start() def stop(self): """ stop the event processor """ self._thread._continue = False self._thread.join() thread = property(lambda self: self._thread, None, None, "Thread that processes the events.") def start(): """ Start the threaded processing of R events. """ EventProcessor().start() def stop(): """ Stop the threaded processing of R events. """ EventProcessor().stop() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8539467 rpy2-3.5.15/rpy2/ipython/0000755000175100001770000000000014543120757014504 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/ipython/__init__.py0000644000175100001770000000011514543120705016603 0ustar00runnerdockerfrom . import rmagic load_ipython_extension = rmagic.load_ipython_extension ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/ipython/ggplot.py0000644000175100001770000000467614543120705016360 0ustar00runnerdocker""" Utilities for using ggplot2 witn ipython / jupyter. """ from rpy2 import robjects from rpy2.robjects.lib import ggplot2, grdevices from IPython import get_ipython # type: ignore from IPython.core.display import Image # type: ignore class GGPlot(ggplot2.GGPlot): def png(self, width=700, height=500): """ Build an Ipython "Image" (requires iPython). """ return image_png(self, width=width, height=height) class GGPlotSVG(ggplot2.GGPlot): """ The embedding of several SVG figures into one ipython notebook is giving garbled figures. The SVG functionality is taken out to a child class. """ def svg(self, width=6, height=4): """ Build an Ipython "Image" (requires iPython). """ with grdevices.render_to_bytesio(grdevices.svg, width=width, height=height) as b: robjects.r("print")(self) data = b.getvalue() ip_img = Image(data=data, format='svg', embed=False) return ip_img def image_png(gg, width=800, height=400): with grdevices.render_to_bytesio(grdevices.png, type="cairo-png", width=width, height=height, antialias="subpixel") as b: robjects.r("print")(gg) data = b.getvalue() ip_img = Image(data=data, format='png', embed=True) return ip_img def display_png(gg, width=800, height=400): ip_img = image_png(gg, width=width, height=height) return ip_img._repr_png_() def set_png_formatter(): # register display func with PNG formatter: png_formatter = get_ipython().display_formatter.formatters['image/png'] dpi = png_formatter.for_type(ggplot2.GGPlot, display_png) return dpi class PNGplot(object): """ Context manager """ def __init__(self, width=600, height=400): self._width = width self._height = height png_formatter = get_ipython().display_formatter.formatters['image/png'] self._png_formatter = png_formatter self._for_ggplot = self._png_formatter.for_type(ggplot2.GGPlot) def __enter__(self): self._png_formatter.for_type(ggplot2.GGPlot, display_png) return None def __exit__(self, exc_type, exc_val, exc_tb): self._png_formatter.for_type(ggplot2.GGPlot, self._for_ggplot) return False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/ipython/html.py0000644000175100001770000002201314543120705016011 0ustar00runnerdockerimport jinja2 # type: ignore from rpy2.robjects import (vectors, RObject, SignatureTranslatedFunction, RS4) from rpy2 import rinterface from rpy2.robjects.packages import SourceCode from rpy2.robjects.packages import wherefrom from IPython import get_ipython # type: ignore template_list = jinja2.Template("""

{{ clsname }} with {{ rlist | length }} elements:

{%- for elt_i in range(display_neltmax) %}
{{ rlist.names[elt_i] }}
{{ rlist[elt_i] }}
{%- endfor %} {%- if display_neltmax < (rlist | length) %}
...
{%- endif %}
""") template_vector_horizontal = jinja2.Template(""" {{ clsname }} with {{ vector | length }} elements: {%- for elt_i in range(display_ncolmax - size_tail) %} {%- endfor %} {%- if display_ncolmax < (vector | length) %} {%- endif %} {%- for elt_i in elt_i_tail %} {%- endfor %}
{{ vector[elt_i] }}...{{ vector[elt_i] }}
""") template_vector_vertical = jinja2.Template(""" {{ clsname }} with {{ vector | length }} elements: {%- for elt_i in range(display_nrowmax - size_tail) %} {%- if has_vector_names %} {%- endif %} {%- endfor %} {%- if display_nrowmax < (vector | length) %} {%- if has_vector_names %} {%- endif %} {%- endif %} {%- for elt_i in elt_i_tail %} {%- if has_vector_names %} {%- endif %} {%- endfor %}
{{ elt_i }}{{ vector.names[elt_i] }}{{ "%s" | format(vector[elt_i]) | truncate(12) }}
.........
{{ elt_i }}{{ vector.names[elt_i] }}{{ "%s" | format(vector[elt_i]) | truncate(12) }}
""") template_dataframe = jinja2.Template(""" {{ clsname }} with {{ dataf.nrow }} rows and {{ dataf | length }} columns: {%- if has_rownames %} {%- endif %} {%- for col_i in range(display_ncolmax - size_coltail) %} {%- endfor %} {%- if display_ncolmax < dataf.ncol %} {%- endif %} {%- for col_i in col_i_tail %} {%- endfor %} {%- for row_i in range(display_nrowmax - size_rowtail) %} {%- if has_rownames %} {%- endif %} {%- for col_i in range(display_ncolmax - size_coltail) %} {%- endfor %} {%- if display_ncolmax < dataf.ncol %} {%- endif %} {%- for col_i in col_i_tail %} {%- endfor %} {%- endfor %} {%- if dataf.nrow > display_nrowmax %} {%- if has_rownames %} {%- endif %} {%- for col_i in range(display_ncolmax - size_coltail) %} {%- endfor %} {%- if display_ncolmax < dataf.ncol %} {%- endif %} {%- for col_i in range(2) %} {%- endfor %} {%- endif %} {%- for row_i in row_i_tail %} {%- if has_rownames %} {%- endif %} {%- for col_i in range(display_ncolmax - size_coltail) %} {%- endfor %} {%- if display_ncolmax < dataf.ncol %} {%- endif %} {%- for col_i in col_i_tail %} {%- endfor %} {%- endfor %}
{{ dataf.names[col_i] }}...{{ dataf.names[col_i] }}
{{ row_i }}{{ dataf.rownames[row_i] }}{{ dataf[col_i][row_i] }}...{{ dataf[col_i][row_i] }}
...............
{{ row_i }}{{ dataf.rownames[row_i] }}{{ dataf[col_i][row_i] }}...{{ dataf[col_i][row_i] }}
""") template_ridentifiedobject = jinja2.Template("""
  • {{ clsname }} object
  • Origin in R: {{ origin }}
  • Class(es) in R:
      {%- for rclsname in obj.rclass %}
    • {{ rclsname }}
    • {%- endfor %}
""") template_rs4 = jinja2.Template("""
{{ clsname }} object
Origin in R: {{ origin }}
Class(es) in R:
    {%- for rclsname in obj.rclass %}
  • {{ rclsname }}
  • {%- endfor %}
Attributes:
    {%- for sln in obj.slotnames() %}
  • {{ sln }}
  • {%- endfor %}
""") template_sourcecode = jinja2.Template("""

R source code:

{{ sourcecode }} """) class StrFactorVector(vectors.FactorVector): def __getitem__(self, item): integer = super(StrFactorVector, self).__getitem__(item) # R is one-offset, Python is zero-offset return self.levels[integer-1] class StrDataFrame(vectors.DataFrame): def __getitem__(self, item): obj = super(StrDataFrame, self).__getitem__(item) if isinstance(obj, vectors.FactorVector): obj = StrFactorVector(obj) return obj def html_vector_horizontal(vector, display_ncolmax=10, size_tail=2, table_class='rpy2_table'): if isinstance(vector, vectors.FactorVector): vector = StrFactorVector(vector) html = template_vector_horizontal.render({ 'table_class': table_class, 'clsname': type(vector).__name__, 'vector': vector, 'display_ncolmax': min(display_ncolmax, len(vector)), 'size_tail': size_tail, 'elt_i_tail': range(max(0, len(vector)-size_tail), len(vector))}) return html def html_rlist(vector, display_nrowmax=10, size_tail=2, table_class='rpy2_table'): rg = range(max(0, len(vector)-size_tail), len(vector)) html = template_vector_vertical.render({ 'table_class': table_class, 'clsname': type(vector).__name__, 'vector': vector, 'has_vector_names': vector.names is not rinterface.NULL, 'display_nrowmax': min(display_nrowmax, len(vector)), 'size_tail': size_tail, 'elt_i_tail': rg}) return html def html_rdataframe(dataf, display_nrowmax=10, display_ncolmax=6, size_coltail=2, size_rowtail=2, table_class='rpy2_table'): html = template_dataframe.render( {'dataf': StrDataFrame(dataf), 'table_class': table_class, 'has_rownames': dataf.rownames is not None, 'clsname': type(dataf).__name__, 'display_nrowmax': min(display_nrowmax, dataf.nrow), 'display_ncolmax': min(display_ncolmax, dataf.ncol), 'col_i_tail': range(max(0, dataf.ncol-size_coltail), dataf.ncol), 'row_i_tail': range(max(0, dataf.nrow-size_rowtail), dataf.nrow), 'size_coltail': size_coltail, 'size_rowtail': size_rowtail}) return html def html_sourcecode(sourcecode): from pygments import highlight # type: ignore from pygments.lexers import SLexer # type: ignore from pygments.formatters import HtmlFormatter # type: ignore formatter = HtmlFormatter() htmlcode = highlight(sourcecode, SLexer(), formatter) d = {'sourcecode': htmlcode, 'syntax_highlighting': formatter.get_style_defs()} html = template_sourcecode.render(d) return html def _dict_ridentifiedobject(obj): if hasattr(obj, '__rname__') and obj.__rname__ is not None: env = wherefrom(obj.__rname__) try: origin = env.do_slot('name')[0] except LookupError: origin = 'package:base ?' else: origin = '???' d = {'clsname': type(obj).__name__, 'origin': origin, 'obj': obj} return d def html_ridentifiedobject(obj): d = _dict_ridentifiedobject(obj) html = template_ridentifiedobject.render(d) return html def html_rs4(obj, table_class='rpy2_table'): d = _dict_ridentifiedobject(obj) d['table_class'] = table_class html = template_rs4.render(d) return html def init_printing(): ip = get_ipython() html_f = ip.display_formatter.formatters['text/html'] html_f.for_type(vectors.Vector, html_vector_horizontal) html_f.for_type(vectors.ListVector, html_rlist) html_f.for_type(vectors.DataFrame, html_rdataframe) html_f.for_type(RObject, html_ridentifiedobject) html_f.for_type(RS4, html_rs4) html_f.for_type(SignatureTranslatedFunction, html_ridentifiedobject) html_f.for_type(SourceCode, html_sourcecode) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/ipython/rmagic.py0000644000175100001770000010660514543120705016321 0ustar00runnerdocker# -*- coding: utf-8 -*- """ ====== Rmagic ====== Magic command interface for interactive work with R in ipython. %R and %%R are the line and cell magics, respectively. .. note:: You will need a working copy of R. Usage ===== To enable the magics below, execute `%load_ext rpy2.ipython`. `%R` {R_DOC} `%Rpush` {RPUSH_DOC} `%Rpull` {RPULL_DOC} `%Rget` {RGET_DOC} """ # ----------------------------------------------------------------------------- # Copyright (C) 2012 The IPython Development Team # Copyright (C) 2013-2019 rpy2 authors # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. # ----------------------------------------------------------------------------- import contextlib import sys import tempfile from glob import glob import os from shutil import rmtree import textwrap import typing import rpy2.rinterface_lib.callbacks import rpy2.rinterface as ri import rpy2.rinterface_lib.openrlib import rpy2.robjects as ro import rpy2.robjects.packages as rpacks from rpy2.robjects.lib import grdevices from rpy2.robjects.conversion import (Converter, localconverter, get_conversion) import warnings # Try loading pandas and numpy, emitting a warning if either cannot be # loaded. try: import numpy # noqa: F401 NUMPY_IMPORTED = True try: import pandas # noqa: F401 PANDAS_IMPORTED = True except ImportError as ie: PANDAS_IMPORTED = False warnings.warn('The Python package `pandas` is strongly ' 'recommended when using `rpy2.ipython`. ' 'Unfortunately it could not be loaded ' '(error: %s), ' 'but at least we found `numpy`.' % str(ie)) except ImportError as ie: NUMPY_IMPORTED = False PANDAS_IMPORTED = False warnings.warn('The Python package `pandas` is strongly ' 'recommended when using `rpy2.ipython`. ' 'Unfortunately it could not be loaded, ' 'as we did not manage to load `numpy` ' 'in the first place (error: %s).' % str(ie)) # IPython imports. import IPython.display # type: ignore from IPython.core import displaypub # type: ignore from IPython.core.magic import (Magics, # type: ignore magics_class, line_cell_magic, line_magic, needs_local_scope, no_var_expand) from IPython.core.magic_arguments import (argument, # type: ignore argument_group, magic_arguments, parse_argstring) template_converter = get_conversion() DEVICES_STATIC_RASTER: typing.Set[str] = {'png', 'jpeg'} DEVICES_STATIC = DEVICES_STATIC_RASTER | {'svg'} DEVICES_SUPPORTED = DEVICES_STATIC | {'X11'} if NUMPY_IMPORTED: if PANDAS_IMPORTED: def _get_ipython_template_converter(template_converter=template_converter): from rpy2.robjects import numpy2ri template_converter += numpy2ri.converter from rpy2.robjects import pandas2ri template_converter += pandas2ri.converter return template_converter else: def _get_ipython_template_converter(template_converter=template_converter): from rpy2.robjects import numpy2ri template_converter += numpy2ri.converter return template_converter else: def _get_ipython_template_converter(template_converter=template_converter): return template_converter def _get_converter(template_converter=template_converter): return Converter('ipython conversion', template=template_converter) ipy_template_converter = _get_ipython_template_converter(template_converter) converter = _get_converter(template_converter=ipy_template_converter) def CELL_DISPLAY_DEFAULT(res, args): return ro.r.show(res) class RInterpreterError(ri.embedded.RRuntimeError): """An error when running R code in a %%R magic cell.""" msg_prefix_template = ('Failed to parse and evaluate line %r.\n' 'R error message: %r') rstdout_prefix = '\nR stdout:\n' def __init__(self, line, err, stdout): self.line = line self.err = err.rstrip() self.stdout = stdout.rstrip() def __str__(self): s = (self.msg_prefix_template % (self.line, self.err)) if self.stdout and (self.stdout != self.err): s += self.rstdout_prefix + self.stdout return s # The default conversion for lists is currently to make them an R list. That # has some advantages, but can be inconvenient (and, it's inconsistent with # the way python lists are automatically converted by numpy functions), so # for interactive use in the rmagic, we call unlist, which converts lists to # vectors **if the list was of uniform (atomic) type**. @converter.py2rpy.register(list) def py2rpy_list(obj): # simplify2array is a utility function, but nice for us # TODO: use an early binding of the R function cv = ro.conversion.get_conversion() robj = ri.ListSexpVector( [cv.py2rpy(x) for x in obj] ) res = ro.r.simplify2array(robj) # The current default converter for the ipython rmagic # might make `res` a numpy array. We need to ensure that # a rpy2 objects is returned (issue #866). res_rpy = cv.py2rpy(res) return res_rpy def _find(name: str, ns: dict): """Find a Python name, which might include dot-separated path to a name. Args: - name: a key in dict if no dot ('.') in it, otherwise a sequence of dot-separated namespaces with the name of the object last (e.g., `package.module.name`). Returns: The object wanted. Raises a NameError or an AttributeError if not found. """ obj = None obj_path = name.split('.') look_for_i = 0 try: obj = ns[obj_path[look_for_i]] except KeyError as e: message = f"name '{obj_path[look_for_i]}' is not defined." if obj_path[look_for_i] == "": message += ' Did you forget to remove trailing comma `,` or included spaces?' raise NameError(message) from e look_for_i += 1 while look_for_i < len(obj_path): try: obj = getattr(obj, obj_path[look_for_i]) except AttributeError as e: raise AttributeError( f"'{'.'.join(obj_path[:look_for_i])}' " f"has no attribute '{obj_path[look_for_i]}'." ) from e look_for_i += 1 return obj def _parse_input_argument(arg: str) -> typing.Tuple[str, str]: """Process the input to an R magic commmand (`%R`, `%%R`, `%Rpush`).""" arg_elts = arg.split('=', maxsplit=1) if len(arg_elts) == 1: rhs = arg_elts[0] lhs = rhs else: lhs, rhs = arg_elts return lhs, rhs # TODO: remove ? # # The R magic is opiniated about what the R vectors should become. # @converter.ri2ro.register(ri.SexpVector) # def _(obj): # if 'data.frame' in obj.rclass: # # request to turn it to a pandas DataFrame # res = converter.rpy2py(obj) # else: # res = ro.sexpvector_to_ro(obj) # return res def display_figures(graph_dir, format='png'): """Iterator to display PNG figures generated by R. graph_dir : str A directory where R figures are to be searched. The iterator yields the image objects.""" assert format in DEVICES_STATIC_RASTER for imgfile in sorted(glob(os.path.join(graph_dir, f'Rplots*{format}'))): if os.stat(imgfile).st_size >= 1000: img = IPython.display.Image(filename=imgfile) IPython.display.display(img, metadata={}) # TODO: Synchronization in the console (though it's a bandaid, not a # real solution). sys.stdout.flush() sys.stderr.flush() yield img def display_figures_svg(graph_dir, isolate_svgs=True): """Display SVG figures generated by R. graph_dir : str A directory where R figures are to be searched. isolate_svgs : bool Enable SVG namespace isolation in metadata. The iterator yields the image object.""" # as onefile=TRUE, there is only one .svg file imgfile = "%s/Rplot.svg" % graph_dir # Cairo creates an SVG file every time R is called # -- empty ones are not published if os.stat(imgfile).st_size >= 1000: img = IPython.display.SVG(filename=imgfile), IPython.display.display_svg( img, metadata={'image/svg+xml': dict(isolated=True)} if isolate_svgs else {} ) # TODO: Synchronization in the console (though it's a bandaid, not a # real solution). sys.stdout.flush() sys.stderr.flush() yield img @magics_class class RMagics(Magics): """A set of magics useful for interactive work with R via rpy2. """ def __init__(self, shell, converter=converter, cache_display_data=False, device='png'): """ Parameters ---------- shell : IPython shell converter : rpy2 Converter instance to use. If None, the magic's current converter is used. cache_display_data : bool If True, the published results of the final call to R are cached in the variable 'display_cache'. device : ['png', 'jpeg', 'X11', 'svg'] Device to be used for plotting. Currently only 'png', 'jpeg', 'X11' and 'svg' are supported, with 'X11' allowing interactive plots on a locally-running jupyter, and the other allowing to visualize R figure generated on a remote jupyter server/kernel. """ super(RMagics, self).__init__(shell) self.cache_display_data = cache_display_data self.Rstdout_cache = [] self.converter = converter self.set_R_plotting_device(device) def set_R_plotting_device(self, device): """ Set which device R should use to produce plots. If device == 'svg' then the package 'Cairo' must be installed. Because Cairo forces "onefile=TRUE", it is not posible to include multiple plots per cell. :param device: ['png', 'jpeg', 'X11', 'svg'] Device to be used for plotting. Currently only 'png', 'jpeg', 'X11' and 'svg' are supported, with 'X11' allowing interactive plots on a locally-running jupyter, and the other allowing to visualize R figure generated on a remote jupyter server/kernel. """ device = device.strip() if device not in DEVICES_SUPPORTED: raise ValueError( f'device must be one of {DEVICES_SUPPORTED}, got "{device}"' ) if device == 'svg': try: self.cairo = rpacks.importr('Cairo') except ri.embedded.RRuntimeError as rre: if rpacks.isinstalled('Cairo'): msg = ('An error occurred when trying to load the ' + 'R package Cairo\'\n%s' % str(rre)) else: msg = textwrap.dedent(""" The R package 'Cairo' is required but it does not appear to be installed/available. Try: import rpy2.robjects.packages as rpacks utils = rpacks.importr('utils') utils.chooseCRANmirror(ind=1) utils.install_packages('Cairo') """) raise RInterpreterError(msg) self.device = device @line_magic def Rdevice(self, line): """ Change the plotting device R uses to one of {}. """.format(DEVICES_SUPPORTED) self.set_R_plotting_device(line.strip()) def eval(self, code): """ Parse and evaluate a line of R code with rpy2. Returns the output to R's stdout() connection, the value generated by evaluating the code, and a boolean indicating whether the return value would be visible if the line of code were evaluated in an R REPL. R Code evaluation and visibility determination are done via an R call of the form withVisible(code_string), and this entire expression needs to be evaluated in R (we can't use rpy2 function proxies here, as withVisible is a LISPy R function). """ with contextlib.ExitStack() as stack: obj_in_module = (rpy2.rinterface_lib .callbacks.obj_in_module) if self.cache_display_data: stack.enter_context( obj_in_module( rpy2.rinterface_lib.callbacks, 'consolewrite_print', self.write_console_regular ) ) stack.enter_context( obj_in_module(rpy2.rinterface_lib.callbacks, 'consolewrite_warnerror', self.write_console_regular) ) stack.enter_context( obj_in_module( rpy2.rinterface_lib.callbacks, '_WRITECONSOLE_EXCEPTION_LOG', '%s') ) try: # Need the newline in case the last line in code is a comment. r_expr = ri.parse(code) value, visible = ri.evalr_expr_with_visible( r_expr ) except (ri.embedded.RRuntimeError, ValueError) as exception: # Otherwise next return seems to have copy of error. warning_or_other_msg = self.flush() raise RInterpreterError(code, str(exception), warning_or_other_msg) finally: ro._print_deferred_warnings() text_output = self.flush() return text_output, value, visible[0] def write_console_regular(self, output): """ A hook to capture R's stdout in a cache. """ self.Rstdout_cache.append(output) def flush(self): """ Flush R's stdout cache to a string, returning the string. """ value = ''.join(self.Rstdout_cache) self.Rstdout_cache = [] return value def _import_name_into_r( self, arg: str, env: ri.SexpEnvironment, local_ns: dict ) -> None: lhs, rhs = _parse_input_argument(arg) val = None try: val = _find(rhs, local_ns) except NameError: if self.shell is None: warnings.warn( f'The shell is None. Unable to look for {rhs}.' ) else: val = _find(rhs, self.shell.user_ns) if val is not None: env[lhs] = val def _find_converter( self, name: str, local_ns: dict ) -> ro.conversion.Converter: converter = None if name is None: converter = self.converter else: try: converter = _find(name, local_ns) except NameError: if self.shell is None: warnings.warn( f'The shell is None. Unable to look for converter {name}.' ) else: converter = _find(name, self.shell.user_ns) if not isinstance(converter, Converter): raise TypeError("'%s' must be a %s object (but it is a %s)." % (converter, Converter, type(converter))) return converter # @skip_doctest @magic_arguments() @argument( '-c', '--converter', default=None, help=textwrap.dedent(""" Name of local converter to use. A converter contains the rules to convert objects back and forth between Python and R. If not specified/None, the defaut converter for the magic\'s module is used (that is rpy2\'s default converter + numpy converter + pandas converter if all three are available).""")) @argument( 'inputs', nargs='*', ) @needs_local_scope @line_magic def Rpush(self, line, local_ns=None): """ A line-level magic that pushes variables from python to R. The line should be made up of whitespace separated variable names in the IPython namespace:: In [7]: import numpy as np In [8]: X = np.array([4.5,6.3,7.9]) In [9]: X.mean() Out[9]: 6.2333333333333343 In [10]: %Rpush X In [11]: %R mean(X) Out[11]: array([ 6.23333333]) """ args = parse_argstring(self.Rpush, line) converter = self._find_converter(args.converter, local_ns) if local_ns is None: local_ns = {} with localconverter(converter): for arg in args.inputs: self._import_name_into_r(arg, ro.globalenv, local_ns) # @skip_doctest @magic_arguments() @argument( 'outputs', nargs='*', ) @line_magic def Rpull(self, line): """ A line-level magic for R that pulls variables from python to rpy2:: In [18]: _ = %R x = c(3,4,6.7); y = c(4,6,7); z = c('a',3,4) In [19]: %Rpull x y z In [20]: x Out[20]: array([ 3. , 4. , 6.7]) In [21]: y Out[21]: array([ 4., 6., 7.]) In [22]: z Out[22]: array(['a', '3', '4'], dtype='|S1') This is useful when a structured array is desired as output, or when the object in R has mixed data types. See the %%R docstring for more examples. Notes ----- Beware that R names can have dots ('.') so this is not fool proof. To avoid this, don't name your R objects with dots... """ args = parse_argstring(self.Rpull, line) outputs = args.outputs with localconverter(self.converter): for output in outputs: robj = ri.globalenv.find(output) self.shell.push({output: robj}) # @skip_doctest @magic_arguments() @argument( 'output', nargs=1, type=str, ) @line_magic def Rget(self, line): """ Return an object from rpy2, possibly as a structured array (if possible). Similar to Rpull except only one argument is accepted and the value is returned rather than pushed to self.shell.user_ns:: In [3]: dtype=[('x', '= 1000: with open(imgfile, 'rb') as fh_img: images.append(fh_img.read()) else: # as onefile=TRUE, there is only one .svg file imgfile = "%s/Rplot.svg" % graph_dir # Cairo creates an SVG file every time R is called # -- empty ones are not published if os.stat(imgfile).st_size >= 1000: with open(imgfile, 'rb') as fh_img: images.append(fh_img.read().decode()) mimetypes = {'png': 'image/png', 'svg': 'image/svg+xml'} mime = mimetypes[self.device] # By default, isolate SVG images in the Notebook to avoid garbling. if images and self.device == "svg" and isolate_svgs: md = {'image/svg+xml': dict(isolated=True)} # Flush text streams before sending figures, helps a little with # output. for image in images: # TODO: Synchronization in the console (though it's a bandaid, not a # real solution). sys.stdout.flush() sys.stderr.flush() display_data.append(('RMagic.R', {mime: image})) return display_data, md # @skip_doctest @magic_arguments() @argument( '-i', '--input', action='append', help=textwrap.dedent(""" Names of Python objects to be assigned to R objects after using the default converter or one specified through the argument `-c/--converter`. Multiple inputs can be passed separated only by commas with no whitespace. Names of Python objects can be either the name of an object in the current notebook/ipython shell, or a path to a name nested in a namespace visible from the current notebook/ipython shell. For example, '-i myvariable' or '-i mypackage.myothervariable' would both work. Each input can be either the name of Python object, in which case the same name will be used for the R object, or an expression of the form =.""") ) @argument( '-o', '--output', action='append', help=textwrap.dedent(""" Names of variables to be pushed from rpy2 to `shell.user_ns` after executing cell body (rpy2's internal facilities will apply ri2ro as appropriate). Multiple names can be passed separated only by commas with no whitespace.""") ) @argument( '-n', '--noreturn', help='Force the magic to not return anything.', action='store_true', default=False ) @argument_group("Plot", "Arguments to plotting device") @argument( '-w', '--width', type=float, help='Width of plotting device in R.' ) @argument( '-h', '--height', type=float, help='Height of plotting device in R.' ) @argument( '-p', '--pointsize', type=int, help='Pointsize of plotting device in R.' ) @argument( '-b', '--bg', help='Background of plotting device in R.' ) @argument_group("SVG", "SVG specific arguments") @argument( '--noisolation', help=textwrap.dedent(""" Disable SVG isolation in the Notebook. By default, SVGs are isolated to avoid namespace collisions between figures. Disabling SVG isolation allows to reference previous figures or share CSS rules across a set of SVGs."""), action='store_false', default=True, dest='isolate_svgs' ) @argument_group("PNG", "PNG specific arguments") @argument( '-u', '--units', type=str, choices=["px", "in", "cm", "mm"], help=textwrap.dedent(""" Units of png plotting device sent as an argument to *png* in R. One of ["px", "in", "cm", "mm"].""")) @argument( '-r', '--res', type=int, help=textwrap.dedent(""" Resolution of png plotting device sent as an argument to *png* in R. Defaults to 72 if *units* is one of ["in", "cm", "mm"].""") ) @argument( '--type', type=str, choices=['cairo', 'cairo-png', 'Xlib', 'quartz'], help=textwrap.dedent(""" Type device used to generate the figure. """)) @argument( '-c', '--converter', default=None, help=textwrap.dedent(""" Name of local converter to use. A converter contains the rules to convert objects back and forth between Python and R. If not specified/None, the defaut converter for the magic\'s module is used (that is rpy2\'s default converter + numpy converter + pandas converter if all three are available).""")) @argument( '-d', '--display', default=None, help=textwrap.dedent(""" Name of function to use to display the output of an R cell (the last object or function call that does not have a left-hand side assignment). That function will have the signature `(robject, args)` where `robject` is the R objects that is an output of the cell, and `args` a namespace with all parameters passed to the cell. """)) @argument( 'code', nargs='*', ) @needs_local_scope @line_cell_magic @no_var_expand def R(self, line, cell=None, local_ns=None): """ Execute code in R, optionally returning results to the Python runtime. In line mode, this will evaluate an expression and convert the returned value to a Python object. The return value is determined by rpy2's behaviour of returning the result of evaluating the final expression. Multiple R expressions can be executed by joining them with semicolons:: In [9]: %R X=c(1,4,5,7); sd(X); mean(X) Out[9]: array([ 4.25]) In cell mode, this will run a block of R code. The resulting value is printed if it would be printed when evaluating the same code within a standard R REPL. Nothing is returned to python by default in cell mode:: In [10]: %%R ....: Y = c(2,4,3,9) ....: summary(lm(Y~X)) Call: lm(formula = Y ~ X) Residuals: 1 2 3 4 0.88 -0.24 -2.28 1.64 Coefficients: Estimate Std. Error t value Pr(>|t|) (Intercept) 0.0800 2.3000 0.035 0.975 X 1.0400 0.4822 2.157 0.164 Residual standard error: 2.088 on 2 degrees of freedom Multiple R-squared: 0.6993,Adjusted R-squared: 0.549 F-statistic: 4.651 on 1 and 2 DF, p-value: 0.1638 In the notebook, plots are published as the output of the cell:: %R plot(X, Y) will create a scatter plot of X bs Y. If cell is not None and line has some R code, it is prepended to the R code in cell. Objects can be passed back and forth between rpy2 and python via the -i -o flags in line:: In [14]: Z = np.array([1,4,5,10]) In [15]: %R -i Z mean(Z) Out[15]: array([ 5.]) In [16]: %R -o W W=Z*mean(Z) Out[16]: array([ 5., 20., 25., 50.]) In [17]: W Out[17]: array([ 5., 20., 25., 50.]) The return value is determined by these rules: * If the cell is not None (i.e., has contents), the magic returns None. * If the final line results in a NULL value when evaluated by rpy2, then None is returned. * No attempt is made to convert the final value to a structured array. Use %Rget to push a structured array. * If the -n flag is present, there is no return value. * A trailing ';' will also result in no return value as the last value in the line is an empty string. """ args = parse_argstring(self.R, line) # arguments 'code' in line are prepended to # the cell lines if cell is None: code = '' return_output = True line_mode = True else: code = cell return_output = False line_mode = False code = ' '.join(args.code) + code # if there is no local namespace then default to an empty dict if local_ns is None: local_ns = {} converter = self._find_converter(args.converter, local_ns) if args.input: with localconverter(converter) as cv: for arg in ','.join(args.input).split(','): self._import_name_into_r(arg, ro.globalenv, local_ns) if args.display: try: cell_display = local_ns[args.display] except KeyError: try: cell_display = self.shell.user_ns[args.display] except KeyError: raise NameError("name '%s' is not defined" % args.display) else: cell_display = CELL_DISPLAY_DEFAULT tmpd = self.setup_graphics(args) text_output = '' display_data = [] try: if line_mode: for line in code.split(';'): text_result, result, visible = self.eval(line) text_output += text_result if text_result: # The last line printed something to the console so # we won't return it. return_output = False else: text_result, result, visible = self.eval(code) text_output += text_result if visible: with contextlib.ExitStack() as stack: obj_in_module = (rpy2.rinterface_lib .callbacks .obj_in_module) if self.cache_display_data: stack.enter_context( obj_in_module(rpy2.rinterface_lib .callbacks, 'consolewrite_print', self.write_console_regular) ) stack.enter_context( obj_in_module( rpy2.rinterface_lib.callbacks, 'consolewrite_warnerror', self.write_console_regular ) ) stack.enter_context( obj_in_module( rpy2.rinterface_lib.callbacks, '_WRITECONSOLE_EXCEPTION_LOG', '%s') ) cell_display(result, args) text_output += self.flush() except RInterpreterError as e: # TODO: Maybe we should make this red or something? print(e.stdout) if not e.stdout.endswith(e.err): print(e.err) raise e finally: if self.device in DEVICES_STATIC: ro.r('dev.off()') if text_output: # display_data.append(('RMagic.R', {'text/plain':text_output})) displaypub.publish_display_data( source='RMagic.R', data={'text/plain': text_output}) # publish figures generated by R. if self.device in DEVICES_STATIC_RASTER: for _ in display_figures(tmpd, format=self.device): if self.cache_display_data: display_data.append(_) elif self.device == 'svg': for _ in display_figures_svg(tmpd): if self.cache_display_data: display_data.append(_) # kill the temporary directory - currently created only for "svg" # and ("png"|"jpeg") (else it's None). if tmpd: rmtree(tmpd) if args.output: with localconverter(converter) as cv: for output in ','.join(args.output).split(','): output_ipy = ro.globalenv.find(output) self.shell.push({output: output_ipy}) # this will keep a reference to the display_data # which might be useful to other objects who happen to use # this method if self.cache_display_data: self.display_cache = display_data # We're in line mode and return_output is still True, # so return the converted result if return_output and not args.noreturn: if result is not ri.NULL: with localconverter(converter) as cv: res = cv.rpy2py(result) return res __doc__ = __doc__.format( R_DOC='{0}{1}'.format(' '*8, RMagics.R.__doc__), RPUSH_DOC='{0}{1}'.format(' '*8, RMagics.Rpush.__doc__), RPULL_DOC='{0}{1}'.format(' '*8, RMagics.Rpull.__doc__), RGET_DOC='{0}{1}'.format(' '*8, RMagics.Rget.__doc__) ) def load_ipython_extension(ip): """Load the extension in IPython.""" ip.register_magics(RMagics) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface.py0000644000175100001770000013522614543120705015510 0ustar00runnerdockerimport abc import atexit import contextlib import contextvars import csv import enum import functools import inspect import os import math import platform import signal import subprocess import textwrap import threading import typing import warnings from typing import Union from rpy2.rinterface_lib import openrlib import rpy2.rinterface_lib._rinterface_capi as _rinterface import rpy2.rinterface_lib.embedded as embedded import rpy2.rinterface_lib.conversion as conversion from rpy2.rinterface_lib.conversion import _cdata_res_to_rinterface import rpy2.rinterface_lib.memorymanagement as memorymanagement from rpy2.rinterface_lib import na_values from rpy2.rinterface_lib.sexp import NULL from rpy2.rinterface_lib.sexp import NULLType import rpy2.rinterface_lib.bufferprotocol as bufferprotocol from rpy2.rinterface_lib import sexp from rpy2.rinterface_lib.sexp import CharSexp # noqa: F401 from rpy2.rinterface_lib.sexp import RTYPES from rpy2.rinterface_lib.sexp import SexpVector from rpy2.rinterface_lib.sexp import StrSexpVector from rpy2.rinterface_lib.sexp import Sexp from rpy2.rinterface_lib.sexp import SexpEnvironment from rpy2.rinterface_lib.sexp import unserialize # noqa: F401 from rpy2.rinterface_lib.sexp import emptyenv from rpy2.rinterface_lib.sexp import baseenv from rpy2.rinterface_lib.sexp import globalenv if os.name == 'nt': import rpy2.rinterface_lib.embedded_mswin as embedded_mswin embedded._initr = embedded_mswin._initr_win32 R_NilValue = openrlib.rlib.R_NilValue endr = embedded.endr evaluation_context = contextvars.ContextVar('evaluation_context', default=globalenv) class _ENVVAR_ACTION(enum.Enum): KEEP_WARN = 0 REPLACE_WARN = 1 KEEP_NOWARN = 2 REPLACE_NOWARN = 3 _DEFAULT_ENVVAR_ACTION: _ENVVAR_ACTION = _ENVVAR_ACTION.REPLACE_WARN def get_evaluation_context() -> SexpEnvironment: """Get the frame (environment) in which R code is currently evaluated.""" warnings.warn('This function is deprecated. ' 'Use evaluation_context directly.', DeprecationWarning, stacklevel=2) return evaluation_context.get() @contextlib.contextmanager def local_context( env: typing.Optional[SexpEnvironment] = None, use_rlock: bool = True ) -> typing.Iterator[SexpEnvironment]: """Local context for the evaluation of R code. Args: - env: an environment to use as a context. If not specified (None, the default), a child environment to the current context is created. - use_rlock: whether to use a threading lock (see the documentation about "rlock". The default is True. Returns: Yield the environment (passed to env, or created). """ parent_frame = evaluation_context.get() if env is None: env = baseenv['new.env']( baseenv['parent.frame']() if parent_frame is None else parent_frame) try: if use_rlock: with openrlib.rlock: token = evaluation_context.set(env) yield env else: token = evaluation_context.set(env) yield env finally: evaluation_context.reset(token) def _sigint_handler(sig, frame): raise KeyboardInterrupt() @_cdata_res_to_rinterface def parse(text: str, num: int = -1): """Parse a string as R code. :param text: A string with R code to parse. :param num: The maximum number of lines to parse. If -1, no limit is applied. """ if not isinstance(text, str): raise TypeError('text must be a string.') robj = StrSexpVector([text]) with memorymanagement.rmemory() as rmemory: res = _rinterface._parse(robj.__sexp__._cdata, num, rmemory) return res def evalr_expr( expr: 'ExprSexpVector', envir: typing.Union[ None, 'SexpEnvironment', 'NULLType', 'ListSexpVector', 'PairlistSexpVector', int, '_MissingArgType'] = None, enclos: typing.Union[ None, 'ListSexpVector', 'PairlistSexpVector', 'NULLType', '_MissingArgType'] = None ) -> sexp.Sexp: """Evaluate an R expression. :param expr: An R expression. :param envir: An optional R environment in which the evaluation will happen. If None, which is the default, the environment in the context variable `evaluation_context` is used. :param enclos: An enclosure. This is only relevant when envir is a list, a pairlist, or a data.frame. It specifies where to look for objects not found in envir. :return: The R objects resulting from the evaluation of the code""" if envir is None: envir = evaluation_context.get() if enclos is None: enclos = MissingArg res = baseenv['eval'](expr, envir=envir, enclos=enclos) return res def evalr_expr_with_visible( expr: 'ExprSexpVector', envir: typing.Union[ None, 'SexpEnvironment'] = None ) -> 'ListSexpVector': """Evaluate an R expression and return value and visibility flag. :param expr: An R expression. :param envir: An environment in which the expression will be evaluated. :return: An R list with (value, visibility) where visibility is an R boolean. """ if envir is None: envir = evaluation_context.get() assert isinstance(envir, SexpEnvironment) error_occured = _rinterface.ffi.new('int *', 0) with memorymanagement.rmemory() as rmemory: r_call_nested = rmemory.protect( openrlib.rlib.Rf_lang2( baseenv['eval'].__sexp__._cdata, expr.__sexp__._cdata) ) r_call = rmemory.protect( openrlib.rlib.Rf_lang2( baseenv['withVisible'].__sexp__._cdata, r_call_nested) ) r_res = rmemory.protect( openrlib.rlib.R_tryEval( r_call, envir.__sexp__._cdata, # call context. error_occured) ) if error_occured[0]: raise embedded.RRuntimeError(_rinterface._geterrmessage()) res = conversion._cdata_to_rinterface(r_res) assert isinstance(res, ListSexpVector) return res def evalr( source: str, maxlines: int = -1, envir: typing.Union[ None, 'SexpEnvironment', 'NULLType', 'ListSexpVector', 'PairlistSexpVector', int, '_MissingArgType'] = None, enclos: typing.Union[ None, 'ListSexpVector', 'PairlistSexpVector', 'NULLType', '_MissingArgType'] = None ) -> sexp.Sexp: """Evaluate a string as R code. Evaluate a string as R just as it would happen when writing code in an R terminal. :param str text: A string to be evaluated as R code, or an R expression. :param int maxlines: The maximum number of lines to parse. If -1, no limit is applied. :param envir: An optional R environment in which the evaluation will happen. :param enclos: An enclosure. This is only relevant when envir is a list, a pairlist, or a data.frame. It specifies where to look for objects not found in envir. :return: The R objects resulting from the evaluation of the code""" expr = parse(source, num=maxlines) res = evalr_expr(expr, envir=envir, enclos=enclos) return res def vector_memoryview(obj: sexp.SexpVector, sizeof_str: str, cast_str: str) -> memoryview: """ :param obj: R vector :param str sizeof_str: Type in a string to use with ffi.sizeof() (for example "int") :param str cast_str: Type in a string to use with memoryview.cast() (for example "i") """ b = openrlib.ffi.buffer( obj._R_GET_PTR(obj.__sexp__._cdata), openrlib.ffi.sizeof(sizeof_str) * len(obj)) shape = bufferprotocol.getshape(obj.__sexp__._cdata) # One could have expected to only need builtin Python # and do something like # ``` # mv = memoryview(b).cast(cast_str, shape, order='F') # ``` # but Python does not handle FORTRAN-ordered arrays without having # to write C extensions (see # https://bugs.python.org/issue34778 is not resolved). # Having numpy a requirement just for this is a problem so a # C function to swap the strides in place was written try: import rpy2.rinterface_lib._bufferprotocol as bp # type: ignore mv = memoryview(b).cast(cast_str, shape) bp.memoryview_swapstrides(mv) except ImportError: import numpy # type: ignore a = numpy.frombuffer(b, dtype=cast_str).reshape(shape, order='F') # The typed signature for memoryview does not help the static # type checker verify that a numpy.ndarray implement the buffer # protocol. Type checking is ignored to avoid a check error. mv = memoryview(a) # type: ignore return mv class SexpSymbol(sexp.Sexp): """An unevaluated R symbol.""" def __init__( self, obj: Union[Sexp, _rinterface.SexpCapsule, _rinterface.UninitializedRCapsule, str] ): if isinstance(obj, Sexp) or isinstance(obj, _rinterface.CapsuleBase): super().__init__(obj) elif isinstance(obj, str): name_cdata = _rinterface.ffi.new('char []', obj.encode('utf-8')) sexp = _rinterface.SexpCapsule( openrlib.rlib.Rf_install(name_cdata)) super().__init__(sexp) else: raise TypeError( 'The constructor must be called ' 'with that is an instance of rpy2.rinterface.sexp.Sexp ' 'or an instance of rpy2.rinterface._rinterface.SexpCapsule') def __str__(self) -> str: return conversion._cchar_to_str( openrlib._STRING_VALUE( self._sexpobject._cdata ), 'utf-8' ) class _MissingArgType(SexpSymbol, metaclass=sexp.SingletonABC): """Missing argument in a call to an R function. When evaluating a function, arguments that are not specified can take a default value when named, otherwise they will take a special value that indicates that they are missing.""" def __init__(self): if embedded.isready(): tmp = sexp.Sexp( _rinterface.UnmanagedSexpCapsule( openrlib.rlib.R_MissingArg ) ) else: tmp = sexp.Sexp( _rinterface.UninitializedRCapsule(RTYPES.SYMSXP.value) ) super().__init__(tmp) def __bool__(self) -> bool: """This is always False.""" return False @property def __sexp__(self) -> typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']: return self._sexpobject @__sexp__.setter def __sexp__(self, value: typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']) -> None: raise TypeError('The capsule for the R object cannot be modified.') MissingArg = _MissingArgType() class SexpPromise(Sexp): @_cdata_res_to_rinterface def eval(self, env: typing.Optional[SexpEnvironment] = None) -> sexp.Sexp: """"Evalute the R "promise". :param env: The environment in which to evaluate the promise. """ if env is None: env = globalenv return openrlib.rlib.Rf_eval(self.__sexp__._cdata, env) class SexpVectorWithNumpyInterface(SexpVector): """Numpy-specific API for accessing the content of a numpy array. This interface implements version 3 of Numpy's `__array_interface__` and is only available / possible for some of the R vectors.""" @property @abc.abstractmethod def _NP_TYPESTR(self) -> str: pass @property def __array_interface__( self ) -> dict: """Return an `__array_interface__` version 3. Note that the pointer returned in the items 'data' corresponds to a memory area under R's memory management and that it will become invalid once the area once R frees the object. It is safer to keep the rpy2 object proxying the R object alive for the duration the pointer is used in Python / numpy.""" shape = bufferprotocol.getshape(self.__sexp__._cdata) data = openrlib.ffi.buffer(self._R_GET_PTR(self.__sexp__._cdata)) strides = bufferprotocol.getstrides(self.__sexp__._cdata, shape, self._R_SIZEOF_ELT) return {'shape': shape, 'typestr': self._NP_TYPESTR, 'strides': strides, 'data': data, 'version': 3} @classmethod def _check_C_compatible(cls, mview): return ( mview.itemsize == cls._R_SIZEOF_ELT and ( cls._NP_TYPESTR == '|u1' or cls._NP_TYPESTR.endswith(mview.format) ) ) class ByteSexpVector(SexpVectorWithNumpyInterface): """Array of bytes. This is the R equivalent to a Python :class:`bytesarray`. """ _R_TYPE = openrlib.rlib.RAWSXP _R_SIZEOF_ELT = _rinterface.ffi.sizeof('char') _NP_TYPESTR = '|u1' _R_GET_PTR = staticmethod(openrlib.RAW) @staticmethod def _CAST_IN(x: typing.Any) -> int: if isinstance(x, int): if x > 255: raise ValueError('byte must be in range(0, 256)') res = x elif isinstance(x, (bytes, bytearray)): if len(x) != 1: raise ValueError('byte must be a single character') res = ord(x) else: raise ValueError('byte must be an integer [0, 255] or a ' 'single byte character') return res @staticmethod def _R_VECTOR_ELT(x, i: int) -> None: return openrlib.RAW(x)[i] @staticmethod def _R_SET_VECTOR_ELT(x, i: int, val) -> None: openrlib.RAW(x)[i] = val def __getitem__(self, i: Union[int, slice]) -> Union[int, 'ByteSexpVector']: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) res = openrlib.RAW_ELT(cdata, i_c) elif isinstance(i, slice): res = type(self).from_iterable( [openrlib.RAW_ELT( cdata, i_c ) for i_c in range(*i.indices(len(self))) ] ) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) openrlib.RAW(cdata)[i_c] = self._CAST_IN(value) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): if v > 255: raise ValueError('byte must be in range(0, 256)') openrlib.RAW(cdata)[i_c] = self._CAST_IN(v) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) class BoolSexpVector(SexpVectorWithNumpyInterface): """Array of booleans. Note that R is internally storing booleans as integers to allow an additional "NA" value to represent missingness.""" _R_TYPE = openrlib.rlib.LGLSXP _R_SIZEOF_ELT = _rinterface.ffi.sizeof('Rboolean') _NP_TYPESTR = '|i' _R_VECTOR_ELT = openrlib.LOGICAL_ELT _R_SET_VECTOR_ELT = openrlib.SET_LOGICAL_ELT _R_GET_PTR = staticmethod(openrlib.LOGICAL) @staticmethod def _CAST_IN(x): if x is None or x == openrlib.rlib.R_NaInt: return NA_Logical else: return bool(x) def __getitem__(self, i: Union[int, slice]) -> Union[bool, 'sexp.NALogicalType', 'BoolSexpVector']: res: Union[bool, 'sexp.NALogicalType', 'BoolSexpVector'] cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) elt = openrlib.LOGICAL_ELT(cdata, i_c) res = (na_values.NA_Logical # type: ignore if elt == NA_Logical else bool(elt)) elif isinstance(i, slice): res = type(self).from_iterable( [openrlib.LOGICAL_ELT(cdata, i_c) for i_c in range(*i.indices(len(self)))] ) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) openrlib.SET_LOGICAL_ELT(cdata, i_c, int(value)) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): openrlib.SET_LOGICAL_ELT(cdata, i_c, int(v)) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) def memoryview(self) -> memoryview: return vector_memoryview(self, 'int', 'i') def nullable_int(v): if type(v) is float and math.isnan(v): return openrlib.rlib.R_NaInt else: return int(v) class IntSexpVector(SexpVectorWithNumpyInterface): _R_TYPE = openrlib.rlib.INTSXP _R_SET_VECTOR_ELT = openrlib.SET_INTEGER_ELT _R_VECTOR_ELT = openrlib.INTEGER_ELT _R_SIZEOF_ELT = _rinterface.ffi.sizeof('int') _NP_TYPESTR = '|i' _R_GET_PTR = staticmethod(openrlib.INTEGER) _CAST_IN = staticmethod(nullable_int) def __getitem__(self, i: Union[int, slice]) -> Union[int, 'IntSexpVector']: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) res = openrlib.INTEGER_ELT(cdata, i_c) if res == NA_Integer: res = NA_Integer elif isinstance(i, slice): res = type(self).from_iterable( [openrlib.INTEGER_ELT( cdata, i_c ) for i_c in range(*i.indices(len(self)))] ) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) openrlib.SET_INTEGER_ELT(cdata, i_c, int(value)) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): openrlib.SET_INTEGER_ELT(cdata, i_c, int(v)) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) def memoryview(self) -> memoryview: return vector_memoryview(self, 'int', 'i') class FloatSexpVector(SexpVectorWithNumpyInterface): _R_TYPE = openrlib.rlib.REALSXP _R_VECTOR_ELT = openrlib.REAL_ELT _R_SET_VECTOR_ELT = openrlib.SET_REAL_ELT _R_SIZEOF_ELT = _rinterface.ffi.sizeof('double') _NP_TYPESTR = '|d' _CAST_IN = staticmethod(float) _R_GET_PTR = staticmethod(openrlib.REAL) def __getitem__( self, i: Union[int, slice] ) -> Union[float, 'FloatSexpVector']: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) res = openrlib.REAL_ELT(cdata, i_c) elif isinstance(i, slice): res = type(self).from_iterable( [openrlib.REAL_ELT( cdata, i_c) for i_c in range(*i.indices(len(self)))] ) else: raise TypeError('Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) openrlib.SET_REAL_ELT(cdata, i_c, float(value)) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): openrlib.SET_REAL_ELT(cdata, i_c, float(v)) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) def memoryview(self) -> memoryview: return vector_memoryview(self, 'double', 'd') class ComplexSexpVector(SexpVector): _R_TYPE = openrlib.rlib.CPLXSXP _R_GET_PTR = staticmethod(openrlib.COMPLEX) _R_SIZEOF_ELT = _rinterface.ffi.sizeof('Rcomplex') @staticmethod def _R_VECTOR_ELT(x, i): return openrlib.COMPLEX(x)[i] @staticmethod def _R_SET_VECTOR_ELT(x, i, v): openrlib.COMPLEX(x).__setitem__(i, v) @staticmethod def _CAST_IN(x): if isinstance(x, complex): res = (x.real, x.imag) else: try: res = (x.r, x.i) except AttributeError: raise TypeError( 'Unable to turn value into an R complex number.' ) return res def __getitem__( self, i: Union[int, slice] ) -> Union[complex, 'ComplexSexpVector']: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) _ = openrlib.COMPLEX_ELT(cdata, i_c) res = complex(_.r, _.i) elif isinstance(i, slice): res = type(self).from_iterable( [openrlib.COMPLEX_ELT( cdata, i_c) for i_c in range(*i.indices(len(self)))] ) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) openrlib.COMPLEX(cdata)[i_c] = self._CAST_IN(value) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): openrlib.COMPLEX(cdata)[i_c] = self._CAST_IN(v) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) class ListSexpVector(SexpVector): """R list. An R list an R vector (array) that is similar to a Python list in the sense that items in the list can be of any type, whereas most other R vectors are homogeneous (all items are of the same type). """ _R_TYPE = openrlib.rlib.VECSXP _R_GET_PTR = staticmethod(openrlib._VECTOR_PTR) _R_SIZEOF_ELT = None _R_VECTOR_ELT = openrlib.rlib.VECTOR_ELT _R_SET_VECTOR_ELT = openrlib.rlib.SET_VECTOR_ELT _CAST_IN = staticmethod(conversion._get_cdata) class PairlistSexpVector(SexpVector): """R pairlist. A R pairlist is rarely used outside of R's internal libraries and a relatively small number of use cases. It is essentially a LISP-like list of (name, value) pairs. """ _R_TYPE = openrlib.rlib.LISTSXP _R_GET_PTR = None _R_SIZEOF_ELT = None _R_VECTOR_ELT = None _R_SET_VECTOR_ELT = None _CAST_IN = staticmethod(conversion._get_cdata) def __getitem__(self, i: Union[int, slice]) -> Sexp: cdata = self.__sexp__._cdata rlib = openrlib.rlib if isinstance(i, int): # R-exts says that it is converted to a VECSXP when subsetted. i_c = _rinterface._python_index_to_c(cdata, i) item_cdata = rlib.Rf_nthcdr(cdata, i_c) with memorymanagement.rmemory() as rmemory: res_cdata = rmemory.protect( rlib.Rf_allocVector(RTYPES.VECSXP, 1)) rlib.SET_VECTOR_ELT( res_cdata, 0, rlib.CAR( item_cdata )) res_name = rmemory.protect( rlib.Rf_allocVector(RTYPES.STRSXP, 1)) item_cdata_name = rlib.PRINTNAME(rlib.TAG(item_cdata)) if _rinterface._TYPEOF(item_cdata_name) != rlib.NILSXP: rlib.SET_STRING_ELT( res_name, 0, item_cdata_name) rlib.Rf_namesgets(res_cdata, res_name) res = conversion._cdata_to_rinterface(res_cdata) elif isinstance(i, slice): iter_indices = range(*i.indices(len(self))) n = len(iter_indices) with memorymanagement.rmemory() as rmemory: res_cdata = rmemory.protect( rlib.Rf_allocVector( self._R_TYPE, n) ) iter_res_cdata = res_cdata prev_i = 0 lst_cdata = self.__sexp__._cdata for i in iter_indices: if i >= len(self): raise IndexError('index out of range') lst_cdata = rlib.Rf_nthcdr(lst_cdata, i - prev_i) prev_i = i rlib.SETCAR(iter_res_cdata, rlib.CAR(lst_cdata)) rlib.SET_TAG(iter_res_cdata, rlib.TAG(lst_cdata)) iter_res_cdata = rlib.CDR(iter_res_cdata) res = conversion._cdata_to_rinterface(res_cdata) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res @classmethod @_cdata_res_to_rinterface def from_iterable(cls, iterable, cast_in=None): raise NotImplementedError() class ExprSexpVector(SexpVector): _R_TYPE = openrlib.rlib.EXPRSXP _R_GET_PTR = None _CAST_IN = None _R_SIZEOF_ELT = None _R_VECTOR_ELT = openrlib.rlib.VECTOR_ELT _R_SET_VECTOR_ELT = None _str2lang = None def _get_str2lang(): global _str2lang if _str2lang is None: try: _str2lang = baseenv['str2lang'] except KeyError: # TODO: This exists to ensure compatibility with # older versions of R. If should be removed when # older versions of R are no longer supported. _str2lang = evalr('function(s) ' 'base::parse(text=s, keep.source=FALSE)[[1]]') return _str2lang LangSexpVector_VT = typing.TypeVar('LangSexpVector_VT', bound='LangSexpVector') class LangSexpVector(SexpVector): """An R language object. To create from a (Python) string containing R code use the classmethod `from_string`.""" _R_TYPE = openrlib.rlib.LANGSXP _R_GET_PTR = None _CAST_IN = None _R_SIZEOF_ELT = None _R_VECTOR_ELT = None _R_SET_VECTOR_ELT = None @_cdata_res_to_rinterface def __getitem__(self, i: int): cdata = self.__sexp__._cdata i_c = _rinterface._python_index_to_c(cdata, i) return openrlib.rlib.CAR( openrlib.rlib.Rf_nthcdr(cdata, i_c) ) def __setitem__(self, i: typing.Union[int, slice], value: sexp.SupportsSEXP) -> None: if isinstance(i, slice): raise NotImplementedError( 'Assigning slices to LangSexpVectors is not yet implemented.' ) cdata = self.__sexp__._cdata i_c = _rinterface._python_index_to_c(cdata, i) openrlib.rlib.SETCAR( openrlib.rlib.Rf_nthcdr(cdata, i_c), value.__sexp__._cdata ) @classmethod def from_string( cls: typing.Type[LangSexpVector_VT], s: str ) -> LangSexpVector_VT: """Create an R language object from a string. This creates an unevaluated R language object. Args: s: R source code in a string. Returns: An instance of the class the method is called from. """ return cls(_get_str2lang()(s)) class SexpClosure(Sexp): @_cdata_res_to_rinterface def __call__(self, *args, **kwargs) -> Sexp: error_occured = _rinterface.ffi.new('int *', 0) with memorymanagement.rmemory() as rmemory: call_r = rmemory.protect( _rinterface.build_rcall(self.__sexp__._cdata, args, kwargs.items())) call_context = evaluation_context.get() res = rmemory.protect( openrlib.rlib.R_tryEval( call_r, call_context.__sexp__._cdata, error_occured) ) if error_occured[0]: raise embedded.RRuntimeError(_rinterface._geterrmessage()) return res @_cdata_res_to_rinterface def rcall(self, keyvals, environment: typing.Optional[SexpEnvironment] = None): """Call/evaluate an R function. Args: - keyvals: a sequence of key/value (name/parameter) pairs. A name/parameter that is None will indicated an unnamed parameter. Like in R, keys/names do not have to be unique, partial matching can be used, and named/unnamed parameters can occur at any position in the sequence. - environment: an optional R environment to evaluate the function. """ # TODO: check keyvals are pairs ? if environment is None: environment = evaluation_context.get() assert isinstance(environment, SexpEnvironment) error_occured = _rinterface.ffi.new('int *', 0) with memorymanagement.rmemory() as rmemory: call_r = rmemory.protect( _rinterface.build_rcall(self.__sexp__._cdata, [], keyvals)) res = rmemory.protect( openrlib.rlib.R_tryEval(call_r, environment.__sexp__._cdata, error_occured)) if error_occured[0]: raise embedded.RRuntimeError(_rinterface._geterrmessage()) return res @property # type: ignore @_cdata_res_to_rinterface def closureenv(self) -> SexpEnvironment: """Closure of the R function.""" return openrlib.rlib.CLOENV(self.__sexp__._cdata) class SexpS4(Sexp): """R "S4" object. S4 objects as one of the available forms of Object-Oriented Programming in R.""" pass # TODO: clean up def make_extptr(obj, tag, protected): if protected is None: cdata_protected = openrlib.rlib.R_NilValue else: try: cdata_protected = protected.__sexp__._cdata except AttributeError: raise TypeError('Argument protected must inherit from %s' % type(Sexp)) ptr = _rinterface.ffi.new_handle(obj) with memorymanagement.rmemory() as rmemory: cdata = rmemory.protect( openrlib.rlib.R_MakeExternalPtr( ptr, tag, cdata_protected)) openrlib.rlib.R_RegisterCFinalizer( cdata, (_rinterface._capsule_finalizer_c if _rinterface._capsule_finalizer_c else _rinterface._capsule_finalizer)) res = _rinterface.SexpCapsuleWithPassenger(cdata, obj, ptr) return res class SexpExtPtr(Sexp): """R "External Pointer" object.""" TYPE_TAG = 'Python' @classmethod def from_pyobject(cls, func, tag: str = TYPE_TAG, protected=None): if not isinstance(tag, str): raise TypeError('The tag must be a string.') scaps = make_extptr(func, conversion._str_to_charsxp(cls.TYPE_TAG), protected) res = cls(scaps) if tag != cls.TYPE_TAG: res.TYPE_TAG = tag return res # TODO: Only use rinterface-level ? conversion._R_RPY2_MAP.update({ openrlib.rlib.NILSXP: NULLType, openrlib.rlib.EXPRSXP: ExprSexpVector, openrlib.rlib.LANGSXP: LangSexpVector, openrlib.rlib.ENVSXP: SexpEnvironment, openrlib.rlib.RAWSXP: ByteSexpVector, openrlib.rlib.LGLSXP: BoolSexpVector, openrlib.rlib.INTSXP: IntSexpVector, openrlib.rlib.REALSXP: FloatSexpVector, openrlib.rlib.CPLXSXP: ComplexSexpVector, openrlib.rlib.STRSXP: StrSexpVector, openrlib.rlib.VECSXP: ListSexpVector, openrlib.rlib.LISTSXP: PairlistSexpVector, openrlib.rlib.CLOSXP: SexpClosure, openrlib.rlib.BUILTINSXP: SexpClosure, openrlib.rlib.SPECIALSXP: SexpClosure, openrlib.rlib.EXTPTRSXP: SexpExtPtr, openrlib.rlib.SYMSXP: SexpSymbol, openrlib.rlib.S4SXP: SexpS4 }) conversion._R_RPY2_DEFAULT_MAP = Sexp conversion._PY_RPY2_MAP.update({ int: conversion._int_to_sexp, float: conversion._float_to_sexp, complex: conversion._complex_to_sexp }) conversion._PY_R_MAP.update({ _rinterface.ffi.CData: False, # integer int: conversion._int_to_sexp, sexp.NAIntegerType: conversion._int_to_sexp, # float float: conversion._float_to_sexp, sexp.NARealType: conversion._float_to_sexp, # boolean bool: conversion._bool_to_sexp, sexp.NALogicalType: conversion._bool_to_sexp, # string str: conversion._str_to_sexp, sexp.CharSexp: None, sexp.NACharacterType: None, # complex complex: conversion._complex_to_sexp, sexp.NAComplexType: conversion._complex_to_sexp, # None type(None): lambda x: openrlib.rlib.R_NilValue}) def vector(iterable, rtype: RTYPES) -> SexpVector: """Create an R vector. While the different types of R vectors all have their own class, the creation of array in Python is often available through factory function that accept the type of the array as a parameters. This function is providing a similar functionality for R vectors.""" error = False try: cls = conversion._R_RPY2_MAP[rtype] except KeyError: error = True if not error and not issubclass(cls, SexpVector): error = True if error: raise ValueError( 'Unable to build a vector from type "%s"' % RTYPES(rtype)) return cls.from_iterable(iterable) class RRuntimeWarning(RuntimeWarning): pass MissingArg = _MissingArgType() NA_Character = None NA_Integer = None NA_Logical = None NA = None NA_Real = None NA_Complex = None def initr_simple() -> typing.Optional[int]: """Initialize R's embedded C library.""" with openrlib.rlock: status = embedded._initr() atexit.register(endr, 0) _rinterface._register_external_symbols() _post_initr_setup() return status def initr_checkenv( interactive: typing.Optional[bool] = None, _want_setcallbacks: bool = True, _c_stack_limit: typing.Optional[int] = None ) -> typing.Optional[int]: """Initialize the embedded R. :param interactive: Should R run in interactive or non-interactive mode? if `None` the value in `_DEFAULT_R_INTERACTIVE` will be used. :param _want_setcallbacks: Should custom rpy2 callbacks for R frontends be set?. :param _c_stack_limit: Limit for the C Stack. if `None` the value in `_DEFAULT_C_STACK_LIMIT` will be used. """ # Force the internal initialization flag if there is an environment # variable that indicates that R was already initialized in the current # process. status = None with openrlib.rlock: _setrenvvars(_DEFAULT_ENVVAR_ACTION) if embedded.is_r_externally_initialized(): embedded._setinitialized() else: status = embedded._initr(interactive=interactive, _want_setcallbacks=_want_setcallbacks, _c_stack_limit=_c_stack_limit) embedded.set_python_process_info() _rinterface._register_external_symbols() _post_initr_setup() return status initr = initr_checkenv def _update_R_ENC_PY(): conversion._R_ENC_PY[openrlib.rlib.CE_UTF8] = 'utf-8' l10n_info = tuple(baseenv['l10n_info']()) if platform.system() == 'Windows': val_latin1 = 'cp{:d}'.format(l10n_info[3][0]) else: val_latin1 = 'latin1' conversion._R_ENC_PY[openrlib.rlib.CE_LATIN1] = val_latin1 if l10n_info[1]: val_native = conversion._R_ENC_PY[openrlib.rlib.CE_UTF8] else: val_native = val_latin1 conversion._R_ENC_PY[openrlib.rlib.CE_NATIVE] = val_native conversion._R_ENC_PY[openrlib.rlib.CE_ANY] = 'utf-8' # TODO: This function could be used by situation.py. May be better to # place elsewhere. def _getrenvvars( baselinevars: typing.Optional[typing.MutableMapping[str, str]] = None, r_home: typing.Optional[str] = None ) -> typing.Tuple[typing.Tuple[str, str], ...]: """Get the environment variables defined by the R front-end script.""" if baselinevars is None: baselinevars = os.environ if r_home is None: r_home = openrlib.R_HOME if r_home is None: raise RuntimeError('Unable to determine R_HOME.') cmd = ( os.path.join(r_home, 'bin', 'Rscript'), '-e', 'x<-Sys.getenv();y<-as.character(x);names(y)<-names(x);' 'write.csv(y)' ) envvars = subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.PIPE) res = [] reader = csv.reader(row for row in envvars.split(os.linesep) if row != '') # Skip column names. next(reader) for k, v in reader: if ( (k not in baselinevars) or (baselinevars[k] != v) ): res.append((k, v)) return tuple(res) def _setrenvvars(action: _ENVVAR_ACTION): new_envvars = {} for k, v in _getrenvvars(): if k in os.environ: if ( action in (_ENVVAR_ACTION.KEEP_WARN, _ENVVAR_ACTION.KEEP_NOWARN) ): if action is _ENVVAR_ACTION.KEEP_WARN: warnings.warn( f'Environment variable "{k}" redefined by R but ignored.' ) continue elif ( action in (_ENVVAR_ACTION.REPLACE_WARN, _ENVVAR_ACTION.REPLACE_NOWARN) ): if action is _ENVVAR_ACTION.REPLACE_WARN: warnings.warn( f'Environment variable "{k}" redefined by R and overriding ' 'existing variable.' ) new_envvars[k] = v os.environ.update(new_envvars) def _post_initr_setup() -> None: emptyenv.__sexp__ = _rinterface.SexpCapsule(openrlib.rlib.R_EmptyEnv) baseenv.__sexp__ = _rinterface.SexpCapsule(openrlib.rlib.R_BaseEnv) globalenv.__sexp__ = _rinterface.SexpCapsule(openrlib.rlib.R_GlobalEnv) NULL._sexpobject = _rinterface.UnmanagedSexpCapsule( openrlib.rlib.R_NilValue ) MissingArg._sexpobject = _rinterface.UnmanagedSexpCapsule( openrlib.rlib.R_MissingArg ) global NA_Character na_values.NA_Character = sexp.NACharacterType() NA_Character = na_values.NA_Character global NA_Integer na_values.NA_Integer = sexp.NAIntegerType(openrlib.rlib.R_NaInt) NA_Integer = na_values.NA_Integer global NA_Logical, NA na_values.NA_Logical = sexp.NALogicalType(openrlib.rlib.R_NaInt) NA_Logical = na_values.NA_Logical NA = NA_Logical global NA_Real na_values.NA_Real = sexp.NARealType(openrlib.rlib.R_NaReal) NA_Real = na_values.NA_Real global NA_Complex na_values.NA_Complex = sexp.NAComplexType( _rinterface.ffi.new( 'Rcomplex *', [openrlib.rlib.R_NaReal, openrlib.rlib.R_NaReal]) ) NA_Complex = na_values.NA_Complex warn_about_thread = False if threading.current_thread() is threading.main_thread(): try: signal.signal(signal.SIGINT, _sigint_handler) except ValueError as ve: if str(ve) == 'signal only works in main thread': warn_about_thread = True else: raise ve else: warn_about_thread = True if warn_about_thread: warnings.warn( textwrap.dedent( """R is not initialized by the main thread. Its taking over SIGINT cannot be reversed here, and as a consequence the embedded R cannot be interrupted with Ctrl-C. Consider (re)setting the signal handler of your choice from the main thread.""" ) ) _update_R_ENC_PY() def _find_first(nodes, of_type): for node in nodes: if isinstance(node, of_type): return node raise ValueError( f'No node of type {of_type}' ) def rternalize( function: typing.Optional[typing.Callable] = None, *, signature: bool = False ) -> typing.Union[SexpClosure, functools.partial]: """ Make a Python function callable from R. Takes an arbitrary Python function and wrap it in such a way that it can be called from the R side. This factory can be used as a decorator, and has an optional named argument "signature" that can be True or False (default is False). When True, the R function wrapping the Python one will have a matching signature or a close one, as detailed below. The Python ellipsis arguments`*args` and `**kwargs` are translated to the R ellipsis `...`. For example: .. code-block:: python @rternalize(signature=True) def foo(x, *args, y=2): pass will be visible in R with the signature: .. code-block:: r function (x, ..., y) The only limitation is that whenever `*args` and `**kwargs` are both present in the Python declaration they must be consecutive. For example: .. code-block:: python def foo(x, *args, y=2, **kwargs): pass is a valid Python declaration. However, it cannot be "rternalized" because the R ellipsis can only appear at most once in the signature of a given function. Trying to apply the decorator `rternalize` would raise an exception. The following Python function can be "rternalized": .. code-block:: python def foo(x, *args, **kwargs): pass It is visible to R with the signature .. code-block:: r function (x, ...) Python function definitions can allow the optional naming of required arguments. The mapping of signatures between Python and R is then quasi-indentical since R does it for unnamed arguments. The check that all arguments are present is still performed on the Python side. Example: .. code-block:: python @rternalize(signature=True) def foo(x, *, y, z): print(f'x: {x[0]}, y: {y[0]}, z: {z[0]}') return ri.NULL >>> _ = foo(1, 2, 3) x: 1, y: 2, z: 3 ValueErro: None >>> _ = foo(1) TypeError: rternalized foo() missing 2 required keyword-only arguments: 'y' and 'z' >>> _ = foo(1, z=2, y=3) x: 1, y: 3, z: 2 >>> _ = foo(1, z=2, y=3) x: 1, y: 3, z: 2 Note that whenever the Python function has an ellipsis (either `*args` or `**kwargs`) earlier parameters in the signature that are positional-or-keyword are considered to be positional arguments in a function call. :param function: A Python callable object. This is a positional argument with a default value `None` to allow the decorator function without parentheses when optional argument is not wanted. :return: A wrapped R object that can be use like any other rpy2 object. """ if not embedded.isinitialized(): raise embedded.RNotReadyError('The embedded R is not yet initialized.') if function is None: return functools.partial(rternalize, signature=signature) assert callable(function) rpy_fun = SexpExtPtr.from_pyobject(function) if not signature: template = parse(""" function(...) { .External(".Python", foo, ...); } """) template[0][2][1][2] = rpy_fun else: has_ellipsis = None params_r_sig = [] for p_i, (name, param) in enumerate( inspect.signature(function).parameters.items() ): if ( param.kind is inspect.Parameter.VAR_POSITIONAL or param.kind is inspect.Parameter.VAR_KEYWORD ): if has_ellipsis: if has_ellipsis != (p_i - 1): raise ValueError( 'R functions can only have one ellipsis. ' 'As consequence your Python function must have *args ' 'and **kwargs that are consecutive in function ' 'signature.' ) else: # The ellipsis can only be added once. has_ellipsis = p_i params_r_sig.append('...') else: params_r_sig.append(name) r_func_args = ', '.join(params_r_sig) r_src = f""" function({r_func_args}) {{ py_func <- RPY2_FUN_PLACEHOLDER lst_args <- base::as.list(base::match.call()[-1]) RPY2_ARGUMENTS <- base::c( base::list( ".Python", py_func ), lst_args ) res <- base::do.call( base::.External, RPY2_ARGUMENTS ); res }} """ template = parse(r_src) function_definition = _find_first(template, of_type=LangSexpVector) function_body = _find_first(function_definition, of_type=LangSexpVector) rpy_fun_node = function_body[1] assert str(rpy_fun_node[2]) == 'RPY2_FUN_PLACEHOLDER' rpy_fun_node[2] = rpy_fun # TODO: use lower-level eval ? res = baseenv['eval'](template) # TODO: hack to prevent the nested function from having its # refcount down to zero when returning res.__nested_sexp__ = rpy_fun.__sexp__ return res def process_revents() -> None: """Process R events. Calling this function a regular interval can help ensure that R is processing input events (e.g., resizing of a window with graphics).""" openrlib.rlib.rpy2_runHandlers(openrlib.rlib.R_InputHandlers) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8579466 rpy2-3.5.15/rpy2/rinterface_lib/0000755000175100001770000000000014543120757015762 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/RPY2.h0000644000175100001770000000017714543120705016665 0ustar00runnerdocker/* R symbol, environment, and arbitrary data */ struct RPY2_sym_env_data { SEXP symbol; SEXP environment; SEXP data; }; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/R_API.h0000644000175100001770000003107314543120705017022 0ustar00runnerdockertypedef unsigned int SEXPTYPE; const unsigned int NILSXP = 0; const unsigned int SYMSXP = 1; const unsigned int LISTSXP = 2; const unsigned int CLOSXP = 3; const unsigned int ENVSXP = 4; const unsigned int PROMSXP = 5; const unsigned int LANGSXP = 6; const unsigned int SPECIALSXP = 7; const unsigned int BUILTINSXP = 8; const unsigned int CHARSXP = 9; const unsigned int LGLSXP = 10; const unsigned int INTSXP = 13; const unsigned int REALSXP = 14; const unsigned int CPLXSXP = 15; const unsigned int STRSXP = 16; const unsigned int DOTSXP = 17; const unsigned int ANYSXP = 18; const unsigned int VECSXP = 19; const unsigned int EXPRSXP = 20; const unsigned int BCODESXP = 21; const unsigned int EXTPTRSXP = 22; const unsigned int WEAKREFSXP = 23; const unsigned int RAWSXP = 24; const unsigned int S4SXP = 25; const unsigned int NEWSXP = 30; const unsigned int FREESXP = 31; const unsigned int FUNSXP = 99; /* include/R_exts/Complex.h */ typedef struct { double r; double i; } Rcomplex; /* include/Rinternals.h */ typedef int R_len_t; #ifdef RPY2_RLEN_LONG typedef ptrdiff_t R_xlen_t; #else /* RPY2_RLEN_LONG */ typedef int R_xlen_t; #endif /* RPY2_RLEN_LONG */ /* R_ext/Arith.h */ extern double R_NaN; /* IEEE NaN */ extern double R_PosInf; /* IEEE Inf */ extern double R_NegInf; /* IEEE -Inf */ extern double R_NaReal; /* NA_REAL: IEEE */ extern int R_NaInt; /* NA_INTEGER:= INT_MIN currently */ typedef unsigned char Rbyte; struct symsxp_struct { struct SEXPREC *pname; struct SEXPREC *value; struct SEXPREC *internal; }; struct listsxp_struct { struct SEXPREC *carval; struct SEXPREC *cdrval; struct SEXPREC *tagval; }; struct envsxp_struct { struct SEXPREC *frame; struct SEXPREC *enclos; struct SEXPREC *hashtab; }; struct closxp_struct { struct SEXPREC *formals; struct SEXPREC *body; struct SEXPREC *env; }; struct promsxp_struct { struct SEXPREC *value; struct SEXPREC *expr; struct SEXPREC *env; }; typedef struct SEXPREC *SEXP; struct sxpinfo_struct { SEXPTYPE type : 5; unsigned int scalar: 1; unsigned int alt : 1; unsigned int obj : 1; unsigned int gp : 16; unsigned int mark : 1; unsigned int debug : 1; unsigned int trace : 1; unsigned int spare : 1; unsigned int gcgen : 1; unsigned int gccls : 3; unsigned int named : 16; unsigned int extra : 32; }; struct primsxp_struct { int offset; }; struct vecsxp_struct { R_xlen_t length; R_xlen_t truelength; }; typedef struct SEXPREC { /* defined as a macro SEXPREC_HEADER in R headers */ struct sxpinfo_struct sxpinfo; struct SEXPREC *attrib; struct SEXPREC *gengc_next_node, *gengc_prev_node; /* --- */ union { struct primsxp_struct primsxp; struct symsxp_struct symsxp; struct listsxp_struct listsxp; struct envsxp_struct envsxp; struct closxp_struct closxp; struct promsxp_struct promsxp; } u; } SEXPREC; typedef struct { /* defined as a macro SEXPREC_HEADER in R headers */ struct sxpinfo_struct sxpinfo; struct SEXPREC *attrib; struct SEXPREC *gengc_next_node, *gengc_prev_node; /* --- */ struct vecsxp_struct vecsxp; } VECTOR_SEXPREC, *VECSEXP; typedef union { VECTOR_SEXPREC s; double align; } SEXPREC_ALIGN; const char* R_CHAR(SEXP x); /* include/R_ext/Boolean.h */ typedef enum { FALSE = 0, TRUE } Rboolean; /* include/Rembedded.h */ int Rf_initialize_R(int ac, char **av); extern int Rf_initEmbeddedR(int argc, char *argv[]); extern void R_RunExitFinalizers(void); extern void Rf_KillAllDevices(void); extern void R_CleanTempDir(void); extern void Rf_endEmbeddedR(int fatal); /* include/R_ext/Memory.h */ void R_gc(void); /* include/Rinternals.h */ void R_ClearExternalPtr(SEXP s); void R_dot_Last(void); /* src/include/Rinlinedfunc.h */ void* DATAPTR(SEXP x); /* include/Rinternals.h */ SEXP (TAG)(SEXP e); SEXP SET_TAG(SEXP x, SEXP y); SEXP (CDR)(SEXP e); SEXP SETCDR(SEXP x, SEXP y); SEXP Rf_nthcdr(SEXP, int); SEXP (CAR)(SEXP e); SEXP SETCAR(SEXP x, SEXP y); SEXP Rf_getAttrib(SEXP sexp, SEXP what); SEXP Rf_setAttrib(SEXP x, SEXP what, SEXP n); SEXP Rf_namesgets(SEXP, SEXP); int R_has_slot(SEXP sexp, SEXP name); SEXP R_do_slot(SEXP sexp, SEXP name); SEXP R_do_slot_assign(SEXP sexp, SEXP name, SEXP value); SEXP (ATTRIB)(SEXP x); SEXP Rf_asChar(SEXP sexp); SEXP Rf_allocList(int n); SEXP Rf_allocVector(SEXPTYPE sexp_t, R_xlen_t n); SEXP Rf_elt(SEXP, int); typedef void (*R_CFinalizer_t)(SEXP); void R_RegisterCFinalizer(SEXP s, R_CFinalizer_t fun); typedef void* (*DL_FUNC)(); SEXP R_MakeExternalPtr(DL_FUNC p, SEXP tag, SEXP prot); SEXP Rf_lang1(SEXP); SEXP Rf_lang2(SEXP, SEXP); SEXP Rf_lang3(SEXP, SEXP, SEXP); SEXP Rf_lang4(SEXP, SEXP, SEXP, SEXP); SEXP Rf_lang5(SEXP, SEXP, SEXP, SEXP, SEXP); SEXP Rf_lang6(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); R_len_t Rf_length(SEXP x); SEXP Rf_ScalarComplex(Rcomplex c); SEXP Rf_ScalarInteger(int n); SEXP Rf_ScalarLogical(int n); SEXP Rf_ScalarRaw(Rbyte b); SEXP Rf_ScalarReal(double f); SEXP Rf_ScalarString(SEXP s); void *(STDVEC_DATAPTR)(SEXP x); /* Integer.*/ int (INTEGER_ELT)(SEXP x, R_xlen_t i); void SET_INTEGER_ELT(SEXP x, R_xlen_t i, int v); int *(INTEGER)(SEXP x); /* Double / real. */ double (REAL_ELT)(SEXP x, R_xlen_t i); void SET_REAL_ELT(SEXP x, R_xlen_t i, double v); double *(REAL)(SEXP x); /* Boolean / logical. */ int (LOGICAL_ELT)(SEXP x, R_xlen_t i); void SET_LOGICAL_ELT(SEXP x, R_xlen_t i, int v); int *(LOGICAL)(SEXP x); /* Complex. */ Rcomplex (COMPLEX_ELT)(SEXP x, R_xlen_t i); Rcomplex *(COMPLEX)(SEXP x); /* Bytes / raw. */ Rbyte *(RAW)(SEXP x); Rbyte (RAW_ELT)(SEXP x, R_xlen_t i); SEXP (STRING_ELT)(SEXP x, R_xlen_t i); void SET_STRING_ELT(SEXP x, R_xlen_t i, SEXP v); SEXP (VECTOR_ELT)(SEXP x, R_xlen_t i); SEXP SET_VECTOR_ELT(SEXP x, R_xlen_t i, SEXP v); SEXP (CLOENV)(SEXP x); SEXP Rf_eval(SEXP, SEXP); SEXP R_tryEval(SEXP, SEXP, int*); Rboolean R_ToplevelExec(void (*fun)(void *), void *data); SEXP R_tryCatchError(SEXP (*fun)(void *data), void *data, SEXP (*hndlr)(SEXP cond, void *hdata), void *hdata); SEXP Rf_findFun(SEXP sym, SEXP env); // SEXP Rf_findFun3(SEXP, SEXP, SEXP); SEXP Rf_findVar(SEXP sym, SEXP env); SEXP Rf_findVarInFrame(SEXP env, SEXP sym); SEXP Rf_findVarInFrame3(SEXP, SEXP, Rboolean); R_xlen_t Rf_xlength(SEXP); SEXP R_lsInternal(SEXP, Rboolean); SEXP Rf_duplicate(SEXP s); SEXP Rf_defineVar(SEXP sym, SEXP s, SEXP env); /* src/include/Rinlinedfunc.h */ SEXP Rf_protect(SEXP s); void Rf_unprotect(int n); void R_PreserveObject(SEXP s); void R_ReleaseObject(SEXP s); extern SEXP R_NaString; /* a CHARSXP */ extern SEXP R_BlankString; extern SEXP R_BlankScalarString; extern SEXP R_GlobalEnv; extern SEXP R_EmptyEnv; Rboolean R_EnvironmentIsLocked(SEXP env); extern SEXP R_BaseEnv; extern SEXP R_BaseNamespace; extern SEXP R_NilValue; extern SEXP R_UnboundValue; extern SEXP R_MissingArg; Rboolean (Rf_isNull)(SEXP s); Rboolean (Rf_isList)(SEXP s); SEXP Rf_install(const char *); SEXP Rf_installChar(SEXP x); SEXP Rf_mkChar(const char *); SEXP Rf_mkString(const char *); typedef enum { CE_NATIVE = 0, CE_UTF8 = 1, CE_LATIN1 = 2, CE_BYTES = 3, CE_SYMBOL = 5, CE_ANY = 99 } cetype_t; cetype_t Rf_getCharCE(SEXP); SEXP Rf_mkCharCE(const char *, cetype_t); SEXP Rf_mkCharLenCE(const char *, int n, cetype_t encoding); typedef enum { Bytes = 0, Chars = 1, Width = 2 } nchar_type; int R_nchar(SEXP string, nchar_type type_, Rboolean allowNA, Rboolean keepNA, const char* msg_name); SEXP (PRINTNAME)(SEXP x); SEXP (FRAME)(SEXP x); SEXP (ENCLOS)(SEXP x); SEXP (HASHTAB)(SEXP x); int (ENVFLAGS)(SEXP x); void (SET_ENVFLAGS)(SEXP x, int v); void SET_FRAME(SEXP x, SEXP v); void SET_ENCLOS(SEXP x, SEXP v); void SET_HASHTAB(SEXP x, SEXP v); /* include/Rdefines.h */ extern SEXP R_ClassSymbol; extern SEXP R_NameSymbol; extern SEXP R_DimSymbol; /* include/Rinternals.h */ Rboolean (Rf_isSymbol)(SEXP s); /* include/R_ext/Parse.h */ typedef enum { PARSE_NULL, PARSE_OK, PARSE_INCOMPLETE, PARSE_ERROR, PARSE_EOF } ParseStatus; SEXP R_ParseVector(SEXP text, int num, ParseStatus *status, SEXP srcfile); /* include/Defn.h */ extern int R_interrupts_pending; /* include/Rinterface.h */ extern Rboolean R_Interactive ; extern void* R_GlobalContext; extern int R_SignalHandlers; extern uintptr_t R_CStackLimit; extern uintptr_t R_CStackStart; typedef enum { SA_NORESTORE,/* = 0 */ SA_RESTORE, SA_DEFAULT,/* was === SA_RESTORE */ SA_NOSAVE, SA_SAVE, SA_SAVEASK, SA_SUICIDE } SA_TYPE; #ifdef OSNAME_NT char *getDLLVersion(void); extern char *getRUser(void); extern char *get_R_HOME(void); void setup_term_ui(void); extern int UserBreak; extern Rboolean AllDevicesKilled; void editorcleanall(void); extern int GA_initapp(int, char **); extern void GA_appcleanup(void); void readconsolecfg(void); typedef int (*blah1) (const char *, char *, int, int); typedef void (*blah2) (const char *, int); typedef void (*blah3) (void); typedef void (*blah4) (const char *); /* Return value here is expected to be 1 for Yes, -1 for No and * 0 for Cancel: symbolic constants in graphapp.h */ typedef int (*blah5) (const char *); typedef void (*blah6) (int); typedef void (*blah7) (const char *, int, int); typedef enum {RGui, RTerm, LinkDLL} UImode; #else /* OSNAME_NT */ #endif /* OSNAME_NT */ /* preprocess-define-begin */ #define R_SIZE_T size_t /* preprocess-define-end */ typedef struct { Rboolean R_Quiet; Rboolean R_Slave; Rboolean R_Interactive; Rboolean R_Verbose; Rboolean LoadSiteFile; Rboolean LoadInitFile; Rboolean DebugInitFile; SA_TYPE RestoreAction; SA_TYPE SaveAction; R_SIZE_T vsize; R_SIZE_T nsize; R_SIZE_T max_vsize; R_SIZE_T max_nsize; R_SIZE_T ppsize; int NoRenviron; #ifdef OSNAME_NT char *rhome; /* R_HOME */ char *home; /* HOME */ blah1 ReadConsole; blah2 WriteConsole; blah3 CallBack; blah4 ShowMessage; blah5 YesNoCancel; blah6 Busy; UImode CharacterMode; blah7 WriteConsoleEx; /* used only if WriteConsole is NULL */ #else /* OSNAME_NT */ #endif /* OSNAME_NT */ } structRstart; typedef structRstart *Rstart; void R_DefParams(Rstart rs); void R_SetParams(Rstart rs); /* void R_SizeFromEnv(Rstart rs); void R_common_command_line(int *n, char **argv, Rstart rs); */ void R_set_command_line_arguments(int argc, char **argv); void setup_Rmainloop(void); void run_Rmainloop(void); void Rf_mainloop(void); extern FILE *R_Consolefile; extern FILE *R_Outputfile; #ifdef R_INTERFACE_PTRS extern void (*ptr_R_Suicide)(const char *); extern void (*ptr_R_ShowMessage)(const char *); extern int (*ptr_R_ReadConsole)(const char *, unsigned char *, int, int); extern void (*ptr_R_WriteConsole)(const char *, int); extern void (*ptr_R_WriteConsoleEx)(const char *, int, int); extern void (*ptr_R_ResetConsole)(void); extern void (*ptr_R_FlushConsole)(void); extern void (*ptr_R_ClearerrConsole)(void); extern void (*ptr_R_Busy)(int); extern void (*ptr_R_CleanUp)(SA_TYPE, int, int); extern int (*ptr_R_ShowFiles)(int, const char **, const char **, const char *, Rboolean, const char *); extern int (*ptr_R_ChooseFile)(int, char *, int); extern int (*ptr_R_EditFile)(const char *); extern void (*ptr_R_loadhistory)(SEXP, SEXP, SEXP, SEXP); extern void (*ptr_R_savehistory)(SEXP, SEXP, SEXP, SEXP); extern void (*ptr_R_addhistory)(SEXP, SEXP, SEXP, SEXP); // added in 3.0.0 extern int (*ptr_R_EditFiles)(int, const char **, const char **, const char *); // naming follows earlier versions in R.app extern SEXP (*ptr_do_selectlist)(SEXP, SEXP, SEXP, SEXP); extern SEXP (*ptr_do_dataentry)(SEXP, SEXP, SEXP, SEXP); extern SEXP (*ptr_do_dataviewer)(SEXP, SEXP, SEXP, SEXP); extern void (*ptr_R_ProcessEvents)(void); #endif /* R_INTERFACE_PTRS */ typedef unsigned int R_NativePrimitiveArgType; typedef struct { const char *name; DL_FUNC fun; int numArgs; R_NativePrimitiveArgType *types; } R_CMethodDef; typedef R_CMethodDef R_FortranMethodDef; typedef struct { const char *name; DL_FUNC fun; int numArgs; } R_CallMethodDef; typedef R_CallMethodDef R_ExternalMethodDef; typedef struct _DllInfo DllInfo; int R_registerRoutines(DllInfo *info, const R_CMethodDef * const croutines, const R_CallMethodDef * const callRoutines, const R_FortranMethodDef * const fortranRoutines, const R_ExternalMethodDef * const externalRoutines); DllInfo *R_getEmbeddingDllInfo(void); void *R_ExternalPtrAddr(SEXP s); ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/R_API_eventloop.c0000644000175100001770000000023514543120705021104 0ustar00runnerdocker# include "R_API.h" # include "R_API_eventloop.h" void rpy2_runHandlers(InputHandler *handlers) {{ R_runHandlers(handlers, R_checkActivity(0, 1)); }}; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/R_API_eventloop.h0000644000175100001770000000336214543120705021115 0ustar00runnerdocker#ifdef CFFI_SOURCE #else /* CFFI_SOURCE */ #ifdef OSNAME_NT #else # include /* for fd_set */ #endif #endif /* CFFI_SOURCE */ typedef void (*InputHandlerProc)(void *userData); typedef struct _InputHandler InputHandler; typedef struct _InputHandler { int activity; int fileDescriptor; InputHandlerProc handler; struct _InputHandler *next; /* Whether we should be listening to this file descriptor or not. */ int active; /* Data that can be passed to the routine as its only argument. This might be a user-level function or closure when we implement a callback to R mechanism. */ void *userData; } InputHandler; extern InputHandler *initStdinHandler(void); extern InputHandler *addInputHandler(InputHandler *handlers, int fd, InputHandlerProc handler, int activity); extern InputHandler *getInputHandler(InputHandler *handlers, int fd); extern int removeInputHandler(InputHandler **handlers, InputHandler *it); #ifdef OSNAME_NT #else extern InputHandler *R_InputHandlers; extern void (* R_PolledEvents)(void); #endif extern int R_wait_usec; #ifdef CFFI_SOURCE #else /* CFFI_SOURCE */ /* The definitions below require fd_set, which is only defined through * the include of sys/select.h . */ extern InputHandler *getSelectedHandler(InputHandler *handlers, fd_set *mask); #ifdef OSNAME_NT #else extern fd_set *R_checkActivity(int usec, int ignore_stdin); extern fd_set *R_checkActivityEx(int usec, int ignore_stdin, void (*intr)(void)); extern void R_runHandlers(InputHandler *handlers, fd_set *mask); #endif extern int R_SelectEx(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout, void (*intr)(void)); #endif /* CFFI_SOURCE */ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/__init__.py0000644000175100001770000000000014543120705020052 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/_bufferprotocol.c0000644000175100001770000000276114543120705021317 0ustar00runnerdocker#define PY_SSIZE_T_CLEAN #include static PyObject * memoryview_swapstrides(PyObject *self, PyObject *args) { Py_buffer *view; PyObject *memoryview; if (!PyArg_ParseTuple(args, "O", &memoryview)) { return NULL; } if (!PyMemoryView_Check(memoryview)) { PyErr_SetString(PyExc_ValueError, "obj must be a memoryview."); return NULL; } view = PyMemoryView_GET_BUFFER(memoryview); if (!PyBuffer_IsContiguous(view, 'A')) { PyErr_SetString(PyExc_ValueError, "obj must have a continuous buffer."); return NULL; } int ndim = view->ndim; Py_ssize_t prod_prev_dim = 1; for (Py_ssize_t i=0; i < ndim; i++) { view->strides[i] = view->itemsize*prod_prev_dim; prod_prev_dim *= view->shape[i]; } ((PyMemoryViewObject *)memoryview)->flags |= PyBUF_F_CONTIGUOUS; return Py_None; }; static PyMethodDef _BufferProtocolMethods[] = { {"memoryview_swapstrides", memoryview_swapstrides, METH_VARARGS, "memoryview_swapstrides(obj). Swap the strides to be Fortran-ordered."}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef _bufferprotocolmodule = { PyModuleDef_HEAD_INIT, "_bufferprotocol", "Utility module to implement buffer protocol functions missing from Python.\n" "\n" "This provides Python-level wrapper for what is otherwise only accessible\n" "from Python's C-API.", //_bufferprotocol_doc, -1, _BufferProtocolMethods }; PyMODINIT_FUNC PyInit__bufferprotocol(void) { return PyModule_Create(&_bufferprotocolmodule); }; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/_rinterface_capi.py0000644000175100001770000005577114543120705021621 0ustar00runnerdocker# TODO: make it cffi-buildable with conditional function definition # (Python if ABI, C if API) import abc import enum import inspect import logging from typing import Tuple import typing import warnings from rpy2.rinterface_lib import ffi_proxy from rpy2.rinterface_lib import openrlib from rpy2.rinterface_lib import conversion from rpy2.rinterface_lib import embedded from rpy2.rinterface_lib import memorymanagement from _cffi_backend import FFI # type: ignore logger = logging.getLogger(__name__) ffi = openrlib.ffi # TODO: How can I reliably get MAX_INT from limits.h ? _MAX_INT: int = 2**32-1 _R_PRESERVED = dict() # type: typing.Dict[int, int] _PY_PASSENGER = dict() FFI_MODE = ffi_proxy.get_ffi_mode(openrlib._rinterface_cffi) def get_rid(cdata: FFI.CData) -> int: """Get the identifier for the R object. This is intended to be like Python's `id()`, but for R objects mapped by rpy2.""" return int(ffi.cast('uintptr_t', cdata)) def protected_rids() -> Tuple[Tuple[int, int], ...]: """Sequence of R IDs protected from collection by rpy2.""" keys = tuple(_R_PRESERVED.keys()) res = [] for k in keys: v = _R_PRESERVED.get(k) if v: res.append((get_rid(k), v)) return tuple(res) def is_cdata_sexp(obj: typing.Any) -> bool: """Is the object a cffi `CData` object pointing to an R object ?""" if (isinstance(obj, FFI.CData) and ffi.typeof(obj).cname == 'struct SEXPREC *'): return True else: return False def _preserve(cdata: FFI.CData) -> int: addr = int(ffi.cast('uintptr_t', cdata)) count = _R_PRESERVED.get(addr, 0) if count == 0: openrlib.rlib.R_PreserveObject(cdata) _R_PRESERVED[addr] = count + 1 return addr def _release(cdata: FFI.CData) -> None: addr = int(ffi.cast('uintptr_t', cdata)) count = _R_PRESERVED[addr] - 1 if count == 0: del _R_PRESERVED[addr] openrlib.rlib.R_ReleaseObject(cdata) else: _R_PRESERVED[addr] = count @ffi_proxy.callback(ffi_proxy._capsule_finalizer_def, openrlib._rinterface_cffi) def _capsule_finalizer(cdata: FFI.CData) -> None: try: openrlib.rlib.R_ClearExternalPtr(cdata) except Exception as e: warnings.warn('Exception downgraded to warning: %s' % str(e)) # ABI and API modes differs in what is the exact callback object to be # passed to C code. if hasattr(openrlib._rinterface_cffi, 'lib'): _capsule_finalizer_c = openrlib._rinterface_cffi.lib._capsule_finalizer else: _capsule_finalizer_c = None class CapsuleBase: _cdata: FFI.CData @property def typeof(self) -> int: return _TYPEOF(self._cdata) @property def rid(self) -> int: return get_rid(self._cdata) class UninitializedRCapsule(CapsuleBase): @property def _cdata(self): raise RuntimeError('The embedded R is not initialized.') @property def typeof(self) -> int: return self._typeof def __init__(self, typeof): self._typeof = typeof class UnmanagedSexpCapsule(CapsuleBase): __slots__ = ('_cdata', ) def __init__(self, cdata: FFI.CData): assert is_cdata_sexp(cdata) self._cdata = cdata class SexpCapsule(CapsuleBase): __slots__ = ('_cdata', ) def __init__(self, cdata: FFI.CData): assert is_cdata_sexp(cdata) _preserve(cdata) self._cdata = cdata def __del__(self): try: _release(self._cdata) except Exception as e: # _release can be None while capsules when Python is terminating # and R is being shutdown, resulting in a race condition when # freeing python objects. if _release is None: pass else: raise e class SexpCapsuleWithPassenger(SexpCapsule): __slots__ = ('_cdata', '_passenger') def __init__(self, cdata: FFI.CData, passenger, ptr): assert is_cdata_sexp(cdata) addr = _preserve(cdata) _PY_PASSENGER[addr] = passenger self._cdata = cdata self._passenger = ptr def __del__(self): addr = get_rid(self._cdata) _release(self._cdata) if addr not in _PY_PASSENGER: del _PY_PASSENGER[addr] class SupportsSEXP(object, metaclass=abc.ABCMeta): @abc.abstractproperty def __sexp__(self): pass def _findvar(symbol, r_environment): rlib = openrlib.rlib return rlib.Rf_findVar(symbol, r_environment) def _findfun(symbol, r_environment): rlib = openrlib.rlib return rlib.Rf_findFun(symbol, r_environment) @ffi_proxy.callback(ffi_proxy._exec_findvar_in_frame_def, openrlib._rinterface_cffi) def _exec_findvar_in_frame(cdata): cdata_struct = openrlib.ffi.cast('struct RPY2_sym_env_data *', cdata) res = openrlib.rlib.Rf_findVarInFrame( cdata_struct.environment, cdata_struct.symbol ) cdata_struct.data = res return def findvar_in_frame(rho, symbol): """Safer wrapper around Rf_findVarInFrame() Run the function Rf_findVarInFrame() in R's C-API through R_ToplevelExec(). Note: All arguments, and the object returned, are C-level R objects. Args: - rho: An R environment. - symbol: An R symbol (as returned by Rf_install()) Returns: The object found. """ # One would expect this to be like # res = _rinterface._findfun(symbol, self.__sexp__._cdata) # but R's findfun will segfault if an error occurs while # accessing the matching object in the environment. exec_data = ffi.new( 'struct RPY2_sym_env_data *', [symbol, rho, openrlib.rlib.R_NilValue] ) _ = openrlib.rlib.R_ToplevelExec( openrlib.rlib._exec_findvar_in_frame, exec_data ) if _ != openrlib.rlib.TRUE: raise embedded.RRuntimeError( 'R C-API Rf_findVarInFrame()' ) return exec_data.data if FFI_MODE is ffi_proxy.InterfaceType.ABI: findvar_in_frame_wrap = openrlib.rlib.Rf_findVarInFrame elif FFI_MODE is ffi_proxy.InterfaceType.API: findvar_in_frame_wrap = findvar_in_frame else: raise ImportError('cffi mode unknown: %s' % FFI_MODE) def _GET_DIM(robj): res = openrlib.rlib.Rf_getAttrib(robj, openrlib.rlib.R_DimSymbol) return res def _GET_DIMNAMES(robj) -> FFI.CData: res = openrlib.rlib.Rf_getAttrib(robj, openrlib.rlib.R_DimNames) return res def _GET_LEVELS(robj: FFI.CData) -> FFI.CData: res = openrlib.rlib.Rf_getAttrib(robj, openrlib.rlib.R_LevelsSymbol) return res def _GET_NAMES(robj: FFI.CData) -> FFI.CData: res = openrlib.rlib.Rf_getAttrib(robj, openrlib.rlib.R_NamesSymbol) return res def _TYPEOF(robj: FFI.CData) -> int: return robj.sxpinfo.type def _SET_TYPEOF(robj: FFI.CData, v: int): robj.sxpinfo.type = v def _NAMED(robj: FFI.CData) -> int: return robj.sxpinfo.named def _string_getitem(cdata: FFI.CData, i: int) -> typing.Optional[str]: elt = openrlib.rlib.STRING_ELT(cdata, i) if elt == openrlib.rlib.R_NaString: res = None else: res = conversion._cchar_to_str( openrlib.rlib.R_CHAR(elt), conversion._R_ENC_PY[openrlib.rlib.Rf_getCharCE(elt)] ) return res # TODO: still used ? def _string_setitem(cdata: FFI.CData, i: int, CE: int, value_b) -> None: rlib = openrlib.rlib rlib.SET_STRING_ELT( cdata, i, rlib.Rf_mkCharCE(value_b, CE) ) def _has_slot(cdata: FFI.CData, name_b) -> bool: res = openrlib.rlib.R_has_slot(cdata, name_b) return bool(res) def build_rcall(rfunction, args=[], kwargs=[]): rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: rcall = rmemory.protect( rlib.Rf_allocList(len(args)+len(kwargs)+1) ) _SET_TYPEOF(rcall, rlib.LANGSXP) rlib.SETCAR(rcall, rfunction) item = rlib.CDR(rcall) for val in args: cdata = rmemory.protect(conversion._get_cdata(val)) rlib.SETCAR(item, cdata) item = rlib.CDR(item) for key, val in kwargs: if key is not None: _assert_valid_slotname(key) rlib.SET_TAG( item, rlib.Rf_install(conversion._str_to_cchar(key))) cdata = rmemory.protect(conversion._get_cdata(val)) rlib.SETCAR(item, cdata) item = rlib.CDR(item) return rcall def _evaluated_promise(function): def _(*args, **kwargs): robj = function(*args, **kwargs) if _TYPEOF(robj) == openrlib.rlib.PROMSXP: robj = openrlib.rlib.Rf_eval( robj, openrlib.rlib.R_GlobalEnv) return robj return _ def _python_index_to_c(cdata: FFI.CData, i: int) -> int: """Compute the C value for a Python-style index. The function will translate a Python-style index that can be either positive or negative, if the later to indicate that indexing is relative to the end of the sequence, into a strictly positive or null index as use in C. Raises an exception IndexError if out of bounds.""" size = openrlib.rlib.Rf_xlength(cdata) if i < 0: # x = [0,1,2,3] # x[-3] = x[size + (-3)] = x[4 - 3] = x[1] = 1 # x[-2] = x[size + (-2)] = x[4 - 2] = x[2] = 2 i = size + i if i >= size or i < 0: raise IndexError('index out of range') return i # TODO: make it a general check for names or symbols ? def _assert_valid_slotname(name: str) -> None: if not isinstance(name, str): raise ValueError('Invalid name %s' % repr(name)) elif len(name) == 0: raise ValueError('The name cannot be an empty string') def _list_attrs(cdata: FFI.CData) -> FFI.CData: rlib = openrlib.rlib attrs = rlib.ATTRIB(cdata) nvalues = rlib.Rf_length(attrs) if rlib.Rf_isList(cdata): namesattr = rlib.Rf_getAttrib(cdata, rlib.R_NamesSymbol) if namesattr != rlib.R_NilValue: nvalues += 1 else: namesattr = rlib.R_NilValue with memorymanagement.rmemory() as rmemory: names = rmemory.protect( rlib.Rf_allocVector(rlib.STRSXP, nvalues)) attr_i = 0 if namesattr != rlib.R_NilValue: rlib.SET_STRING_ELT(names, attr_i, rlib.PRINTNAME(rlib.R_NamesSymbol)) attr_i += 1 while attrs != rlib.R_NilValue: tag = rlib.TAG(attrs) if _TYPEOF(tag) == rlib.SYMSXP: rlib.SET_STRING_ELT(names, attr_i, rlib.PRINTNAME(tag)) else: rlib.SET_STRING_ELT( names, attr_i, rlib.R_BlankString ) attrs = rlib.CDR(attrs) attr_i += 1 return names def _remove(name: FFI.CData, env: FFI.CData, inherits) -> FFI.CData: rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: internal = rmemory.protect( rlib.Rf_install(conversion._str_to_cchar('.Internal'))) remove = rmemory.protect( rlib.Rf_install(conversion._str_to_cchar('remove'))) args = rmemory.protect(rlib.Rf_lang4(remove, name, env, inherits)) call = rmemory.protect(rlib.Rf_lang2(internal, args)) # TODO: use tryEval instead and catch errors. res = rlib.Rf_eval(call, rlib.R_GlobalEnv) return res def _geterrmessage() -> str: rlib = openrlib.rlib # TODO: use isString and installTrChar with memorymanagement.rmemory() as rmemory: symbol = rmemory.protect( rlib.Rf_install( conversion._str_to_cchar('geterrmessage')) ) geterrmessage = _findvar( symbol, rlib.R_GlobalEnv) call_r = rlib.Rf_lang1(geterrmessage) res = rmemory.protect( rlib.Rf_eval(call_r, rlib.R_GlobalEnv) ) res = _string_getitem(res, 0) return res def serialize(cdata: FFI.CData, cdata_env: FFI.CData) -> FFI.CData: """Serialize an R object to an R string. Note that the R string returned is *not* protected from the R garbage collection.""" rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: sym_serialize = rmemory.protect( rlib.Rf_install(conversion._str_to_cchar('serialize')) ) func_serialize = rmemory.protect( _findfun(sym_serialize, rlib.R_BaseEnv)) r_call = rmemory.protect( rlib.Rf_lang3(func_serialize, cdata, rlib.R_NilValue) ) error_occured = ffi.new('int *', 0) res = rlib.R_tryEval(r_call, cdata_env, error_occured) if error_occured[0]: raise embedded.RRuntimeError(_geterrmessage()) return res def unserialize(cdata: FFI.CData, cdata_env: FFI.CData) -> FFI.CData: """Unserialize an R string to an R object. Note that the R object returned is *not* protected from the R garbage collection.""" rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: sym_unserialize = rmemory.protect( rlib.Rf_install( conversion._str_to_cchar('unserialize')) ) func_unserialize = rmemory.protect(_findfun(sym_unserialize, rlib.R_BaseEnv)) r_call = rmemory.protect( rlib.Rf_lang2(func_unserialize, cdata) ) error_occured = ffi.new('int *', 0) res = rlib.R_tryEval(r_call, cdata_env, error_occured) if error_occured[0]: raise embedded.RRuntimeError(_geterrmessage()) return res @ffi_proxy.callback(ffi_proxy._evaluate_in_r_def, openrlib._rinterface_cffi) def _evaluate_in_r(rargs: FFI.CData) -> FFI.CData: # An uncaught exception in the boby of this function would # result in a segfault. we wrap it in a try-except an report # exceptions as logs. rlib = openrlib.rlib try: rargs = rlib.CDR(rargs) cdata = rlib.CAR(rargs) if (_TYPEOF(cdata) != rlib.EXTPTRSXP): # TODO: also check tag # (rlib.R_ExternalPtrTag(sexp) == '.Python') logger.error('The fist item is not an R external pointer.') return rlib.R_NilValue handle = rlib.R_ExternalPtrAddr(cdata) func = ffi.from_handle(handle) pyargs = [] pykwargs = {} # R and Python function definitions differ. In R all arguments # are optionally named, that is like "positional or keyword" arguments # in Python. "positional or keyword" happens to be default for # arguments without a default *unless* the ellipsis `*args` is used, # in which case all arguments prior to it become positional-only. # We will want to skip naming positional-only arguments although R # might. # TODO: Extraction information by inspecting the signature of the # Python function will be done for each call. This is a performance burden. # Is there a way to do it at "rternalization" time and access the result at # call time? # TODO: An other optimization would be to have a streamlined path for # function that only have POSITIONAL_ONLY parameters (or may be # POSITIONAL_ONLY and/or KEYWORD_ONLY). pyfunc_params_iter = inspect.signature(func).parameters.items() py_posonly = [] py_has_ellipsis = False py_positionalorkw = [] for paramname, paramval in pyfunc_params_iter: if paramval.kind is inspect.Parameter.POSITIONAL_ONLY: py_posonly.append(paramname) elif (paramval.kind is inspect.Parameter.VAR_POSITIONAL or paramval.kind is inspect.Parameter.VAR_KEYWORD): py_has_ellipsis = True elif paramval.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: py_positionalorkw.append(paramname) else: # KEYWORD_ONLY from now on. break rarg_i = -1 rargs = rlib.CDR(rargs) while rargs != rlib.R_NilValue: rarg_i += 1 cdata = rlib.CAR(rargs) if rlib.Rf_isNull(rlib.TAG(rargs)): # Unnamed argument pyargs.append(conversion._cdata_to_rinterface(cdata)) else: # Named arguments rname = rlib.PRINTNAME(rlib.TAG(rargs)) name = conversion._cchar_to_str( rlib.R_CHAR(rname), conversion._R_ENC_PY[openrlib.rlib.Rf_getCharCE(rname)] ) if rarg_i < len(py_posonly): if py_posonly[rarg_i] == name: # This is an unnamed argument and names are matching. pyargs.append(conversion._cdata_to_rinterface(cdata)) else: # The R call is considering the parameter as a named one # and the position is not conserved. This is can lead # to unnoticed issues, and difficult to debug ones when # they are. It is better to report the issue here. raise RuntimeError( 'Parameter name mismatch. R call considering the argument ' f'"{py_posonly[rarg_i]}" as a position-independent ' 'keyword argument while it is positional-only ' 'in the rternalized Python function.' ) elif ( py_has_ellipsis and (rarg_i < (len(py_posonly) + len(py_positionalorkw))) ): if py_positionalorkw[rarg_i - len(py_posonly)] == name: # This is considered an unnamed argument and names are matching. pyargs.append(conversion._cdata_to_rinterface(cdata)) else: # The R call is considering the parameter as a named one # and the position is not conserved. This is can lead # to unnoticed issues, and difficult to debug ones when # they are. It is better to report the issue here. raise RuntimeError( 'Parameter name mismatch. R call considering the argument ' f'"{py_posonly[rarg_i]}" as a position-independent ' 'keyword argument while it is positional-or-keyword ' 'followed by an positional ellipsis `*args` in the ' 'rternalized Python function.' ) else: pykwargs[name] = conversion._cdata_to_rinterface(cdata) rargs = rlib.CDR(rargs) res = func(*pyargs, **pykwargs) # The object is whatever the "rternalized" function `func` # is returning and we need to cast that result into a SEXP # that R's C API can handle. At the same time we need to ensure # that the R is: # - protected from garbage collection long enough to let the R # code that called the rternalized function complete. # - eventually its memory is freed to prevent a leak. # To that end, we create a SEXP object to be returned that is # not managed by rpy2, leaving the object's lifespan under R's # sole control. if ( hasattr(res, '_sexpobject') and isinstance(res._sexpobject, SexpCapsule) ): return res._sexpobject._cdata else: return conversion._python_to_cdata(res) except Exception as e: logger.error('%s: rternalized %s' % (type(e).__name__, e)) return rlib.R_NilValue def _register_external_symbols() -> None: python_cchar = ffi.new('char []', b'.Python') ffi_proxy = openrlib.ffi_proxy if ( ffi_proxy.get_ffi_mode(openrlib._rinterface_cffi) == ffi_proxy.InterfaceType.ABI ): python_callback = ffi.cast('DL_FUNC', _evaluate_in_r) else: python_callback = ffi.cast('DL_FUNC', openrlib.rlib._evaluate_in_r) externalmethods = ffi.new( 'R_ExternalMethodDef[]', [[python_cchar, python_callback, -1], [ffi.NULL, ffi.NULL, 0]]) openrlib.rlib.R_registerRoutines( openrlib.rlib.R_getEmbeddingDllInfo(), ffi.NULL, ffi.NULL, ffi.NULL, externalmethods ) class PARSING_STATUS(enum.Enum): PARSE_NULL = openrlib.rlib.PARSE_NULL PARSE_OK = openrlib.rlib.PARSE_OK PARSE_INCOMPLETE = openrlib.rlib.PARSE_INCOMPLETE PARSE_ERROR = openrlib.rlib.PARSE_ERROR PARSE_EOF = openrlib.rlib.PARSE_EOF class RParsingError(Exception): def __init__(self, msg: str, status: typing.Optional[PARSING_STATUS] = None): full_msg = ( '{msg} - {status}' .format(msg=msg, status=status) ) super().__init__(full_msg) self.status = status @ffi_proxy.callback(ffi_proxy._parsevector_wrap_def, openrlib._rinterface_cffi) def _parsevector_wrap(data: FFI.CData): try: cdata, num, status = ffi.from_handle(data) res = openrlib.rlib.R_ParseVector( cdata, # text num, status, openrlib.rlib.R_NilValue) except Exception as e: res = openrlib.rlib.R_NilValue logger.error('_parsevector_wrap: %s', str(e)) return res # TODO: is this complete ? @ffi_proxy.callback(ffi_proxy._handler_def, openrlib._rinterface_cffi) def _handler_wrap(cond, hdata): return openrlib.rlib.R_NilValue if FFI_MODE is ffi_proxy.InterfaceType.ABI: _parsevector_wrap = _parsevector_wrap _handler_wrap = _handler_wrap elif FFI_MODE is ffi_proxy.InterfaceType.API: _parsevector_wrap = openrlib.rlib._parsevector_wrap _handler_wrap = openrlib.rlib._handler_wrap else: raise ImportError('cffi mode unknown: %s' % FFI_MODE) def _parse(cdata: FFI.CData, num, rmemory) -> FFI.CData: status = ffi.new('ParseStatus[1]', None) data = ffi.new_handle((cdata, num, status)) hdata = ffi.NULL res = rmemory.protect( openrlib.rlib.R_tryCatchError( _parsevector_wrap, data, _handler_wrap, hdata ) ) # TODO: design better handling of possible status: # PARSE_NULL, # PARSE_OK, # PARSE_INCOMPLETE, # PARSE_ERROR, # PARSE_EOF if status[0] != openrlib.rlib.PARSE_OK: raise RParsingError('Parsing status not OK', status=PARSING_STATUS(status[0])) return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/bufferprotocol.py0000644000175100001770000000426214543120705021364 0ustar00runnerdocker"""Python Buffer Protocol for R arrays.""" import typing from rpy2.rinterface_lib import openrlib def getrank(cdata) -> int: """Get the rank (number of dimensions) of an R array. The R NULL will return a rank of 1. :param cdata: C data from cffi :return: The rank""" dim_cdata = openrlib.rlib.Rf_getAttrib(cdata, openrlib.rlib.R_DimSymbol) if dim_cdata == openrlib.rlib.R_NilValue: # TODO: Why isn't this 0? return 1 else: return openrlib.rlib.Rf_length(dim_cdata) def getshape(cdata, rk: typing.Optional[int] = None) -> typing.Tuple[int, ...]: """Get the shape (size for each dimension) of an R array. The rank of the array can optionally by passed. Note that is potentially an unsafe operation if the value for the rank in incorrect. It may result in a segfault. :param cdata: C data from cffi :return: A Tuple with the sizes. The length of the tuple is the rank. """ if rk is None: rk = getrank(cdata) dim_cdata = openrlib.rlib.Rf_getAttrib(cdata, openrlib.rlib.R_DimSymbol) shape: typing.Tuple[int, ...] if dim_cdata == openrlib.rlib.R_NilValue: shape = (openrlib.rlib.Rf_length(cdata), ) else: _ = [] for i in range(rk): _.append(openrlib.INTEGER_ELT(dim_cdata, i)) shape = tuple(_) return shape def getstrides(cdata, shape: typing.Tuple[int, ...], itemsize: int) -> typing.Tuple[int, ...]: """Get the strides (offsets in memory when walking along dimension) for an R array. The shape (see method `getshape`) and itemsize must be specified. Incorrect values are potentially unsage and result in a segfault. :param cdata: C data from cffi. :param shape: The shape of the array. :param itemsize: The size of (C sizeof) each item in the array. :return: A tuple with the strides. The length of the tuple is rank-1.""" rk = len(shape) strides = [itemsize, ] for i in range(1, rk): strides.append(shape[i-1] * strides[i-1]) return tuple(strides) def getbuffer(cdata): raise NotImplementedError() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/callbacks.py0000644000175100001770000002266414543120705020256 0ustar00runnerdocker"""Callbacks available from R's C-API. The callbacks make available in R's C-API can be specified as Python functions, with the module providing the adapter code that makes it possible.""" from contextlib import contextmanager import logging import typing import os from rpy2.rinterface_lib import openrlib from rpy2.rinterface_lib import ffi_proxy from rpy2.rinterface_lib import conversion logger = logging.getLogger(__name__) _CCHAR_ENCODING = 'utf-8' # TODO: rename to "replace_in_module" @contextmanager def obj_in_module(module, name: str, obj: typing.Any): obj_orig = getattr(module, name) setattr(module, name, obj) try: yield finally: setattr(module, name, obj_orig) def consoleflush(): pass _FLUSHCONSOLE_EXCEPTION_LOG = 'R[flush console]: %s' @ffi_proxy.callback(ffi_proxy._consoleflush_def, openrlib._rinterface_cffi) def _consoleflush() -> None: try: consoleflush() except Exception as e: logger.error(_FLUSHCONSOLE_EXCEPTION_LOG, str(e)) def consoleread(prompt: str) -> str: """Read input for the R console. :param prompt: The message prompted. :return: A string with the input returned by the user. """ return input(prompt) _READCONSOLE_EXCEPTION_LOG = 'R[read into console]: %s' _READCONSOLE_INTERNAL_EXCEPTION_LOG = ('Internal rpy2 error with ' '_consoleread callback: %s') @ffi_proxy.callback(ffi_proxy._consoleread_def, openrlib._rinterface_cffi) def _consoleread(prompt, buf, n: int, addtohistory) -> int: success = None try: s = conversion._cchar_to_str(prompt, _CCHAR_ENCODING) reply = consoleread(s) except Exception as e: success = 0 logger.error(_READCONSOLE_EXCEPTION_LOG, str(e)) if success == 0: return success try: # TODO: Should the coding be dynamically extracted from # elsewhere ? reply_b = reply.encode('utf-8') reply_n = min(n, len(reply_b)) pybuf = bytearray(n) pybuf[:reply_n] = reply_b[:reply_n] pybuf[reply_n] = ord('\n') pybuf[reply_n+1] = 0 openrlib.ffi.memmove(buf, pybuf, n) if reply_n == 0: success = 0 else: success = 1 except Exception as e: success = 0 logger.error(_READCONSOLE_INTERNAL_EXCEPTION_LOG, str(e)) return success def consolereset() -> None: pass _RESETCONSOLE_EXCEPTION_LOG = 'R[reset console]: %s' @ffi_proxy.callback(ffi_proxy._consolereset_def, openrlib._rinterface_cffi) def _consolereset() -> None: try: consolereset() except Exception as e: logger.error(_RESETCONSOLE_EXCEPTION_LOG, str(e)) def consolewrite_print(s: str) -> None: """R writing to the console/terminal. :param s: the data to write to the console/terminal. """ # TODO: is the callback for flush working with Linux ? print(s, end='', flush=True) def consolewrite_warnerror(s: str) -> None: # TODO: use an rpy2/R-specific warning instead of UserWarning. logger.warning(_WRITECONSOLE_EXCEPTION_LOG, s) _WRITECONSOLE_EXCEPTION_LOG = 'R[write to console]: %s' @ffi_proxy.callback(ffi_proxy._consolewrite_ex_def, openrlib._rinterface_cffi) def _consolewrite_ex(buf, n: int, otype: int) -> None: s = conversion._cchar_to_str_with_maxlen(buf, n, _CCHAR_ENCODING) try: if otype == 0: consolewrite_print(s) else: consolewrite_warnerror(s) except Exception as e: logger.error(_WRITECONSOLE_EXCEPTION_LOG, str(e)) def showmessage(s: str) -> None: print('R wants to show a message') print(s) _SHOWMESSAGE_EXCEPTION_LOG = 'R[show message]: %s' @ffi_proxy.callback(ffi_proxy._showmessage_def, openrlib._rinterface_cffi) def _showmessage(buf): s = conversion._cchar_to_str(buf, _CCHAR_ENCODING) try: showmessage(s) except Exception as e: logger.error(_SHOWMESSAGE_EXCEPTION_LOG, str(e)) def choosefile(new): return input('Enter file name:') _CHOOSEFILE_EXCEPTION_LOG = 'R[choose file]: %s' @ffi_proxy.callback(ffi_proxy._choosefile_def, openrlib._rinterface_cffi) def _choosefile(new, buf, n: int) -> int: try: res = choosefile(new) except Exception as e: logger.error(_CHOOSEFILE_EXCEPTION_LOG, str(e)) res = None if res is None: return 0 res_cdata = conversion._str_to_cchar(res) openrlib.ffi.memmove(buf, res_cdata, len(res_cdata)) return len(res_cdata) def showfiles(filenames: typing.Tuple[str, ...], headers: typing.Tuple[str, ...], wtitle: typing.Optional[str], pager: typing.Optional[str]) -> None: """R showing files. :param filenames: A tuple of file names. :param headers: A tuple of strings (TODO: check what it is) :wtitle: Title of the "window" showing the files. :pager: Pager to use to show the list of files. """ for fn in filenames: print('File: %s' % fn) with open(fn) as fh: for row in fh: print(row) print('---') _SHOWFILE_EXCEPTION_LOG = 'R[show file]: %s' _SHOWFILE_INTERNAL_EXCEPTION_LOG = ('Internal rpy2 error while ' 'showing files for R: %s') @ffi_proxy.callback(ffi_proxy._showfiles_def, openrlib._rinterface_cffi) def _showfiles(nfiles: int, files, headers, wtitle, delete, pager) -> int: filenames = [] headers_str = [] wtitle_str = None pager_str = None try: wtitle_str = conversion._cchar_to_str(wtitle, _CCHAR_ENCODING) pager_str = conversion._cchar_to_str(pager, _CCHAR_ENCODING) for i in range(nfiles): filenames.append( conversion._cchar_to_str(files[i], _CCHAR_ENCODING) ) headers_str.append( conversion._cchar_to_str(headers[i], _CCHAR_ENCODING) ) except Exception as e: logger.error(_SHOWFILE_INTERNAL_EXCEPTION_LOG, str(e)) if len(filenames): res = 0 else: res = 1 try: showfiles(tuple(filenames), tuple(headers_str), wtitle_str, pager_str) except Exception as e: res = 1 logger.error(_SHOWFILE_EXCEPTION_LOG, str(e)) return res def cleanup(saveact, status, runlast): pass _CLEANUP_EXCEPTION_LOG = 'R[cleanup]: %s' @ffi_proxy.callback(ffi_proxy._cleanup_def, openrlib._rinterface_cffi) def _cleanup(saveact, status, runlast): try: cleanup(saveact, status, runlast) except Exception as e: logger.error(_CLEANUP_EXCEPTION_LOG, str(e)) def processevents() -> None: """Process R events. This function can be periodically called by R to handle events such as window resizing in an interactive graphical device.""" pass _PROCESSEVENTS_EXCEPTION_LOG = 'R[processevents]: %s' @ffi_proxy.callback(ffi_proxy._processevents_def, openrlib._rinterface_cffi) def _processevents() -> None: try: processevents() except KeyboardInterrupt: # This function is a Python callback. A keyboard interruption is # captured in Python, but R must be notified that an interruption # occured so it can handle it. rlib = openrlib.rlib if os.name == 'nt': # On Windows, the global C-level R variable `UserBreak` is set # to one to notifying R that a `SIGBREAK` has been sent # (see the R source - `src/gnuwin32/embeddedR.c:my_onintr()`). rlib.UserBreak = 1 else: # On UNIX-like, R can be notified that a SIGINT has been sent # through the C-level R variable `R_interrupts_pending` # (see the R source - `src/main/main.c:handleInterrupt()`) rlib.R_interrupts_pending = 1 except Exception as e: logger.error(_PROCESSEVENTS_EXCEPTION_LOG, str(e)) def busy(x: int) -> None: """R is busy. :param x: TODO this is an integer but I do not know what it does. """ pass _BUSY_EXCEPTION_LOG = 'R[busy]: %s' @ffi_proxy.callback(ffi_proxy._busy_def, openrlib._rinterface_cffi) def _busy(which: int) -> None: try: busy(which) except Exception as e: logger.error(_BUSY_EXCEPTION_LOG, str(e)) def callback() -> None: pass _CALLBACK_EXCEPTION_LOG = 'R[callback]: %s' @ffi_proxy.callback(ffi_proxy._callback_def, openrlib._rinterface_cffi) def _callback() -> None: try: callback() except Exception as e: logger.error(_CALLBACK_EXCEPTION_LOG, str(e)) def yesnocancel(question: str) -> int: """Asking a user to answer yes, no, or cancel. :param question: The question asked to the user :return: An integer with the answer. """ return int(input(question)) _YESNOCANCEL_EXCEPTION_LOG = 'R[yesnocancel]: %s' @ffi_proxy.callback(ffi_proxy._yesnocancel_def, openrlib._rinterface_cffi) def _yesnocancel(question): try: q = conversion._cchar_to_str(question, _CCHAR_ENCODING) res = yesnocancel(q) except Exception as e: logger.error(_YESNOCANCEL_EXCEPTION_LOG, str(e)) return res ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/conversion.py0000644000175100001770000001136314543120705020516 0ustar00runnerdocker"""Mapping between Python objects, C objects, and R objects.""" # TODO: rename the module with a prefix _ to indicate that this should # not be used outside of rpy2's own code from typing import Callable from typing import Dict from typing import Optional from typing import Type from typing import Union from rpy2.rinterface_lib import openrlib from rpy2.rinterface_lib import _rinterface_capi as _rinterface ffi = openrlib.ffi _R_RPY2_MAP = {} # type: Dict[int, Type] class DummyMissingRpy2Map(object): def __init__(self, *args, **kwargs): raise NotImplementedError('The default object mapper class is no set.') _R_RPY2_DEFAULT_MAP: Type[ Union[DummyMissingRpy2Map, '_rinterface.SupportsSEXP'] ] = DummyMissingRpy2Map # TODO: shouldn't the second type strictly inherit from an rpy2 # R object ? _PY_RPY2_MAP: Dict[Type, Callable] = {} def _cdata_to_rinterface(cdata): scaps = _rinterface.SexpCapsule(cdata) t = cdata.sxpinfo.type if t in _R_RPY2_MAP: cls = _R_RPY2_MAP[t] else: cls = _R_RPY2_DEFAULT_MAP return cls(scaps) def _cdata_res_to_rinterface(function): def _(*args, **kwargs): cdata = function(*args, **kwargs) # TODO: test cdata is of the expected CType return _cdata_to_rinterface(cdata) return _ def _sexpcapsule_to_rinterface(scaps: '_rinterface.SexpCapsule'): cls = _R_RPY2_MAP.get(scaps.typeof, _R_RPY2_DEFAULT_MAP) return cls(scaps) # TODO: The name of the function is misleading, I think. Consider changing it. def _python_to_cdata(obj): t = type(obj) if t in _PY_RPY2_MAP: cls = _PY_RPY2_MAP[t] else: raise ValueError(obj) # cls = _PY_RPY2_DEFAULT_MAP cdata = cls(obj) return cdata # TODO: can scalars in R's C API be used ? def _int_to_sexp(val: int): rlib = openrlib.rlib # TODO: _rinterface._MAX_INT is determined empirically. if val > _rinterface._MAX_INT: raise ValueError( f'The Python integer {val} is larger than {_rinterface._MAX_INT} (' 'R\'s largest possible integer).' ) s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.INTSXP, 1)) openrlib.SET_INTEGER_ELT(s, 0, val) rlib.Rf_unprotect(1) return s def _bool_to_sexp(val: bool): # TODO: test value is not too large for R's ints rlib = openrlib.rlib s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.LGLSXP, 1)) openrlib.SET_LOGICAL_ELT(s, 0, int(val)) rlib.Rf_unprotect(1) return s def _float_to_sexp(val: float): rlib = openrlib.rlib s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.REALSXP, 1)) openrlib.SET_REAL_ELT(s, 0, val) rlib.Rf_unprotect(1) return s def _complex_to_sexp(val: complex): rlib = openrlib.rlib s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.CPLXSXP, 1)) openrlib.SET_COMPLEX_ELT( s, 0, val ) rlib.Rf_unprotect(1) return s # Default encoding for converting R string back to Python # As defined in R_API.h, possible values are # CE_NATIVE = 0, # CE_UTF8 = 1, # CE_LATIN1 = 2, # CE_BYTES = 3, # CE_SYMBOL = 5, # CE_ANY = 99 # Default encoding for converting R strings to Python _R_ENC_PY = {None: 'ascii'} def _str_to_cchar(s: str, encoding: str = 'utf-8'): # TODO: use isString and installTrChar b = s.encode(encoding) return ffi.new('char[]', b) def _cchar_to_str(c, encoding: str) -> str: # TODO: use isString and installTrChar s = ffi.string(c).decode(encoding) return s def _cchar_to_str_with_maxlen(c, maxlen: int, encoding: str) -> str: # TODO: use isString and installTrChar s = ffi.string(c, maxlen).decode(encoding) return s def _str_to_charsxp(val: Optional[str]): rlib = openrlib.rlib if val is None: s = rlib.R_NaString else: cchar = _str_to_cchar(val, encoding='utf-8') s = rlib.Rf_mkCharCE(cchar, openrlib.rlib.CE_UTF8) return s def _str_to_sexp(val: str): rlib = openrlib.rlib s = rlib.Rf_protect(rlib.Rf_allocVector(rlib.STRSXP, 1)) charval = _str_to_charsxp(val) rlib.SET_STRING_ELT(s, 0, charval) rlib.Rf_unprotect(1) return s def _str_to_symsxp(val: str): rlib = openrlib.rlib cchar = _str_to_cchar(val) s = rlib.Rf_install(cchar) return s _PY_R_MAP = {} # type: Dict[Type, Union[Callable, None, bool]] # TODO: Do special values such as NAs need to be mapped into a SEXP when # a scalar ? def _get_cdata(obj): cls = _PY_R_MAP.get(type(obj)) if cls is False: cdata = obj elif cls is None: try: cdata = obj.__sexp__._cdata except AttributeError: raise ValueError('Not an rpy2 R object and unable ' 'to map it to one: %s' % repr(obj)) else: cdata = cls(obj) return cdata ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/embedded.py0000644000175100001770000002667314543120705020074 0ustar00runnerdockerimport enum import logging import os import sys import typing import warnings from rpy2.rinterface_lib import openrlib from rpy2.rinterface_lib import callbacks logger = logging.getLogger(__name__) if sys.version_info[:2] < (3, 8): from typing_extensions import Protocol else: from typing import Protocol ffi = openrlib.ffi _options = ('rpy2', '--quiet', '--no-save') # type: typing.Tuple[str, ...] logger.info('Default options to initialize R: {}'.format(', '.join(_options))) _DEFAULT_C_STACK_LIMIT: int = -1 _DEFAULT_R_INTERACTIVE: bool = True rpy2_embeddedR_isinitialized = 0x00 class Is_RStart(Protocol): @property def rhome(self): ... @rhome.setter def rhome(self, value) -> None: ... @property def home(self): ... @home.setter def home(self, value) -> None: ... @property def CharacterMode(self): ... @CharacterMode.setter def CharacterMode(self, value) -> None: ... @property def ReadConsole(self): ... @ReadConsole.setter def ReadConsole(self, value) -> None: ... @property def WriteConsoleEx(self): ... @WriteConsoleEx.setter def WriteConsoleEx(self, value) -> None: ... @property def CallBack(self): ... @CallBack.setter def CallBack(self, value) -> None: ... @property def ShowMessage(self): ... @ShowMessage.setter def ShowMessage(self, value) -> None: ... @property def YesNoCancel(self): ... @YesNoCancel.setter def YesNoCancel(self, value) -> None: ... @property def Busy(self): ... @Busy.setter def Busy(self, value) -> None: ... @property def R_Quiet(self): ... @R_Quiet.setter def R_Quiet(self, value) -> None: ... @property def R_Interactive(self): ... @R_Interactive.setter def R_Interactive(self, value) -> None: ... @property def RestoreAction(self): ... @RestoreAction.setter def RestoreAction(self, value) -> None: ... @property def SaveAction(self): ... @SaveAction.setter def SaveAction(self, value) -> None: ... @property def vsize(self): ... @vsize.setter def vsize(self, value) -> None: ... @property def nsize(self): ... @nsize.setter def nsize(self, value) -> None: ... @property def max_vsize(self): ... @max_vsize.setter def max_vsize(self, value) -> None: ... @property def max_nsize(self): ... @max_nsize.setter def max_nsize(self, value) -> None: ... @property def ppsize(self): ... @ppsize.setter def ppsize(self, value) -> None: ... rstart: Is_RStart = None # type: ignore # TODO: move initialization-related code to _rinterface ? class RPY_R_Status(enum.Enum): """Possible status for the embedded R.""" INITIALIZED = 0x01 BUSY = 0x02 ENDED = 0x04 def set_initoptions(options: typing.Tuple[str]) -> None: """Set initialization options for the embedded R. :param:`options` A tuple of string with the options (e.g., '--verbose', '--quiet'). """ if rpy2_embeddedR_isinitialized: raise RuntimeError('Options can no longer be set once ' 'R is initialized.') global _options for x in options: assert isinstance(x, str) with openrlib.rlock: logger.info('Setting options to initialize R: {}' .format(', '.join(options))) _options = tuple(options) def get_initoptions() -> typing.Tuple[str, ...]: """Get the initialization options for the embedded R.""" return _options def isinitialized() -> bool: """Is the embedded R initialized.""" return bool(rpy2_embeddedR_isinitialized & RPY_R_Status.INITIALIZED.value) def _setinitialized() -> None: """Set the embedded R as initialized. This may result in a later segfault if used with the embedded R has not been initialized. You should not have to use it.""" global rpy2_embeddedR_isinitialized rpy2_embeddedR_isinitialized = RPY_R_Status.INITIALIZED.value def isready() -> bool: """Is the embedded R ready for use.""" INITIALIZED = RPY_R_Status.INITIALIZED return bool( rpy2_embeddedR_isinitialized == INITIALIZED.value ) def assert_isready() -> None: """Assert whether R is ready (initialized). Raises an RNotReadyError if it is not.""" if not isready(): raise RNotReadyError( 'The embedded R is not ready to use.') class RNotReadyError(Exception): """Embedded R is not ready to use.""" pass class RRuntimeError(Exception): """Error generated by R.""" pass def _setcallback(rlib, rlib_symbol: str, callbacks, callback_symbol: typing.Optional[str]) -> None: """Set R callbacks.""" if callback_symbol is None: new_callback = ffi.NULL else: new_callback = getattr(callbacks, callback_symbol) setattr(rlib, rlib_symbol, new_callback) CALLBACK_INIT_PAIRS = (('ptr_R_WriteConsoleEx', '_consolewrite_ex'), ('ptr_R_WriteConsole', None), ('ptr_R_ShowMessage', '_showmessage'), ('ptr_R_ReadConsole', '_consoleread'), ('ptr_R_FlushConsole', '_consoleflush'), ('ptr_R_ResetConsole', '_consolereset'), ('ptr_R_ChooseFile', '_choosefile'), ('ptr_R_ShowFiles', '_showfiles'), ('ptr_R_CleanUp', '_cleanup'), ('ptr_R_ProcessEvents', '_processevents'), ('ptr_R_Busy', '_busy')) # TODO: can init_once() be used here ? def _initr( interactive: typing.Optional[bool] = None, _want_setcallbacks: bool = True, _c_stack_limit: typing.Optional[int] = None ) -> typing.Optional[int]: """Initialize the embedded R. :param interactive: Should R run in interactive or non-interactive mode? if `None` the value in `_DEFAULT_R_INTERACTIVE` will be used. :param _want_setcallbacks: Should custom rpy2 callbacks for R frontends be set?. :param _c_stack_limit: Limit for the C Stack. if `None` the value in `_DEFAULT_C_STACK_LIMIT` will be used. """ if interactive is None: interactive = _DEFAULT_R_INTERACTIVE if _c_stack_limit is None: _c_stack_limit = _DEFAULT_C_STACK_LIMIT rlib = openrlib.rlib ffi_proxy = openrlib.ffi_proxy if ( ffi_proxy.get_ffi_mode(openrlib._rinterface_cffi) == ffi_proxy.InterfaceType.ABI ): callback_funcs = callbacks else: callback_funcs = rlib with openrlib.rlock: if isinitialized(): logger.info('R is already initialized. No need to initialize.') return None elif openrlib.R_HOME is None: raise ValueError('openrlib.R_HOME cannot be None.') elif openrlib.rlib.R_NilValue != ffi.NULL: msg = ('R was initialized outside of rpy2 (R_NilValue != NULL). ' 'Trying to use it nevertheless.') warnings.warn(msg) logger.warn(msg) _setinitialized() return None os.environ['R_HOME'] = openrlib.R_HOME # TODO: Setting LD_LIBRARY_PATH after the process has started # is too late. Because of this, the line below does not help # address issues where calling R from the command line is working # (as it is a shell script setting environment variables before # start the binary in a child process). Calling C's dlopen with # the path of the shared library could address this but for the # API mode this would require writing a C wrapper to manually # load each each symbol in the C library. os.environ['LD_LIBRARY_PATH'] = ( ':'.join( (openrlib.LD_LIBRARY_PATH, os.environ.get('LD_LIBRARY_PATH', '')) ) ) options_c = [ffi.new('char[]', o.encode('ASCII')) for o in _options] n_options = len(options_c) n_options_c = ffi.cast('int', n_options) # TODO: Conditional in C code rlib.R_SignalHandlers = 0 # Instead of calling Rf_initEmbeddedR which breaks threaded context # perform the initialization manually to set R_CStackLimit before # calling setup_Rmainloop(), see: # https://github.com/rpy2/rpy2/issues/729 rlib.Rf_initialize_R(n_options_c, options_c) if _c_stack_limit: rlib.R_CStackLimit = ffi.cast('uintptr_t', _c_stack_limit) rlib.R_Interactive = True logger.debug('Calling R setup_Rmainloop.') rlib.setup_Rmainloop() _setinitialized() rlib.R_Interactive = interactive # TODO: Conditional definition in C code # (Aqua, TERM, and TERM not "dumb") rlib.R_Outputfile = ffi.NULL rlib.R_Consolefile = ffi.NULL if _want_setcallbacks: logger.debug('Setting functions for R callbacks.') for rlib_symbol, callback_symbol in CALLBACK_INIT_PAIRS: _setcallback(rlib, rlib_symbol, callback_funcs, callback_symbol) return 1 def endr(fatal: int) -> None: logger.debug('Ending embedded R process.') global rpy2_embeddedR_isinitialized rlib = openrlib.rlib with openrlib.rlock: if rpy2_embeddedR_isinitialized & RPY_R_Status.ENDED.value: logger.info('Embedded R already ended.') return logger.debug('R_do_Last()') rlib.R_dot_Last() logger.debug('R_RunExitFinalizers()') rlib.R_RunExitFinalizers() logger.debug('Rf_KillAllDevices()') rlib.Rf_KillAllDevices() logger.debug('R_CleanTempDir()') rlib.R_CleanTempDir() logger.debug('R_gc') rlib.R_gc() logger.debug('Rf_endEmbeddedR(fatal)') rlib.Rf_endEmbeddedR(fatal) rpy2_embeddedR_isinitialized ^= RPY_R_Status.ENDED.value logger.info('Embedded R ended.') _REFERENCE_TO_R_SESSIONS = 'https://github.com/rstudio/reticulate/issues/98' _R_SESSION_INITIALIZED = 'R_SESSION_INITIALIZED' _PYTHON_SESSION_INITIALIZED = 'PYTHON_SESSION_INITIALIZED' def get_r_session_status(r_session_init=None) -> dict: """Return information about the R session, if available. Information about the R session being already initialized can be communicated by an environment variable exported by the process that initialized it. See discussion at: %s """ % _REFERENCE_TO_R_SESSIONS res = {'current_pid': os.getpid()} if r_session_init is None: r_session_init = os.environ.get(_R_SESSION_INITIALIZED) if r_session_init: for item in r_session_init.split(':'): try: key, value = item.split('=', 1) except ValueError: warnings.warn( 'The item %s in %s should be of the form key=value.' % (item, _R_SESSION_INITIALIZED) ) res[key] = value return res def is_r_externally_initialized() -> bool: r_status = get_r_session_status() return str(r_status['current_pid']) == str(r_status.get('PID')) def set_python_process_info() -> None: """Set information about the Python process in an environment variable. See discussion at: %s """ % _REFERENCE_TO_R_SESSIONS info = (('current_pid', os.getpid()), ('sys.executable', sys.executable)) info_string = ':'.join('%s=%s' % x for x in info) os.environ[_PYTHON_SESSION_INITIALIZED] = info_string ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/embedded_mswin.py0000644000175100001770000000563014543120705021277 0ustar00runnerdockerimport sys import typing from rpy2.rinterface_lib import embedded from rpy2.rinterface_lib import callbacks from rpy2.rinterface_lib import openrlib ffi = openrlib.ffi # These constants are default values from R sources _DEFAULT_VSIZE: int = 67108864 # vector heap size _DEFAULT_NSIZE: int = 350000 # language heap size _DEFAULT_MAX_VSIZE: int = sys.maxsize # max vector heap size _DEFAULT_MAX_NSIZE: int = 50000000 # max language heap size _DEFAULT_PPSIZE: int = 50000 # stack size _DEFAULT_C_STACK_LIMIT: int = -1 _DEFAULT_R_INTERACTIVE: bool = True def _initr_win32( interactive: typing.Optional[bool] = None, _want_setcallbacks: bool = True, _c_stack_limit: typing.Optional[int] = _DEFAULT_C_STACK_LIMIT ) -> typing.Optional[int]: """Initialize the embedded R. :param interactive: Should R run in interactive or non-interactive mode? if `None` the value in `_DEFAULT_R_INTERACTIVE` will be used. :param _want_setcallbacks: Should custom rpy2 callbacks for R frontends be set?. :param _c_stack_limit: Limit for the C Stack. if `None` the value in `_DEFAULT_C_STACK_LIMIT` will be used. """ if interactive is None: interactive = _DEFAULT_R_INTERACTIVE if _c_stack_limit is None: _c_stack_limit = _DEFAULT_C_STACK_LIMIT with openrlib.rlock: if embedded.isinitialized(): return None options_c = [ffi.new('char[]', o.encode('ASCII')) for o in embedded._options] n_options = len(options_c) n_options_c = ffi.cast('int', n_options) status = openrlib.rlib.Rf_initEmbeddedR(n_options_c, options_c) embedded._setinitialized() embedded.rstart = ffi.new('Rstart') rstart = embedded.rstart rhome = openrlib.rlib.get_R_HOME() rstart.rhome = rhome rstart.home = openrlib.rlib.getRUser() rstart.CharacterMode = openrlib.rlib.LinkDLL if _want_setcallbacks: rstart.ReadConsole = callbacks._consoleread rstart.WriteConsoleEx = callbacks._consolewrite_ex rstart.CallBack = callbacks._callback rstart.ShowMessage = callbacks._showmessage rstart.YesNoCancel = callbacks._yesnocancel rstart.Busy = callbacks._busy rstart.R_Quiet = True rstart.R_Interactive = interactive rstart.RestoreAction = openrlib.rlib.SA_RESTORE rstart.SaveAction = openrlib.rlib.SA_NOSAVE rstart.vsize = ffi.cast('size_t', _DEFAULT_VSIZE) rstart.nsize = ffi.cast('size_t', _DEFAULT_NSIZE) rstart.max_vsize = ffi.cast('size_t', _DEFAULT_MAX_VSIZE) rstart.max_nsize = ffi.cast('size_t', _DEFAULT_MAX_NSIZE) rstart.ppsize = ffi.cast('size_t', _DEFAULT_PPSIZE) openrlib.rlib.R_SetParams(rstart) # TODO: still needed ? openrlib.rlib.R_CStackLimit = ffi.cast('uintptr_t', _c_stack_limit) return status ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/ffi_proxy.py0000644000175100001770000000760114543120705020336 0ustar00runnerdockerimport enum import logging import os import types import typing logger: logging.Logger = logging.getLogger(__name__) class InterfaceType(enum.Enum): ABI = 1 API = 2 class SignatureDefinition(object): def __init__(self, name: str, rtype: str, arguments: typing.Tuple[str, ...]): self.name = name self.rtype = rtype self.arguments = arguments @property def callback_def(self) -> str: return '{} ({})'.format(self.rtype, ' ,'.join(self.arguments)) @property def extern_def(self) -> str: return '{} {}({})'.format( self.rtype, self.name, ' ,'.join(self.arguments) ) @property def extern_python_def(self) -> str: return 'extern "Python" {} {}({});'.format( self.rtype, self.name, ' ,'.join(self.arguments) ) FFI_MODE: typing.Optional[InterfaceType] = None def get_ffi_mode(_rinterface_cffi: types.ModuleType) -> InterfaceType: global FFI_MODE if FFI_MODE is None: if hasattr(_rinterface_cffi, 'lib'): res = InterfaceType.API else: res = InterfaceType.ABI logger.debug(f'cffi mode is {res}') FFI_MODE = res return FFI_MODE def callback( definition, _rinterface_cffi ) -> typing.Callable[[typing.Callable], object]: def decorator(func: typing.Callable) -> object: if get_ffi_mode(_rinterface_cffi) == InterfaceType.ABI: res = _rinterface_cffi.ffi.callback(definition.callback_def)(func) elif get_ffi_mode(_rinterface_cffi) == InterfaceType.API: res = _rinterface_cffi.ffi.def_extern()(func) else: raise RuntimeError('The cffi mode is neither ABI or API.') return res return decorator # Callbacks _capsule_finalizer_def: SignatureDefinition = SignatureDefinition( '_capsule_finalizer', 'void', ('SEXP',)) _evaluate_in_r_def: SignatureDefinition = SignatureDefinition( '_evaluate_in_r', 'SEXP', ('SEXP args',)) _consoleflush_def: SignatureDefinition = SignatureDefinition( '_consoleflush', 'void', ('void', )) _consoleread_def: SignatureDefinition = SignatureDefinition( '_consoleread', 'int', ('char *', 'char *' if os.name == 'nt' else 'unsigned char *', 'int', 'int')) _consolereset_def: SignatureDefinition = SignatureDefinition( '_consolereset', 'void', ('void', )) _consolewrite_def: SignatureDefinition = SignatureDefinition( '_consolewrite', 'void', ('char *', 'int')) _consolewrite_ex_def: SignatureDefinition = SignatureDefinition( '_consolewrite_ex', 'void', ('char *', 'int', 'int')) _showmessage_def: SignatureDefinition = SignatureDefinition( '_showmessage', 'void', ('char *', )) _choosefile_def: SignatureDefinition = SignatureDefinition( '_choosefile', 'int', ('int', 'char *', 'int')) _cleanup_def: SignatureDefinition = SignatureDefinition( '_cleanup', 'void', ('SA_TYPE', 'int', 'int')) _showfiles_def: SignatureDefinition = SignatureDefinition( '_showfiles', 'int', ('int', 'const char **', 'const char **', 'const char *', 'Rboolean', 'const char *')) _processevents_def: SignatureDefinition = SignatureDefinition( '_processevents', 'void', ('void', )) _busy_def: SignatureDefinition = SignatureDefinition( '_busy', 'void', ('int', )) _callback_def: SignatureDefinition = SignatureDefinition( '_callback', 'void', ('void', )) # TODO: should be const char * _yesnocancel_def: SignatureDefinition = SignatureDefinition( '_yesnocancel', 'int', ('char *', )) _parsevector_wrap_def: SignatureDefinition = SignatureDefinition( '_parsevector_wrap', 'SEXP', ('void *data', )) _handler_def: SignatureDefinition = SignatureDefinition( '_handler_wrap', 'SEXP', ('SEXP cond', 'void *hdata')) _exec_findvar_in_frame_def: SignatureDefinition = SignatureDefinition( '_exec_findvar_in_frame', 'void', ('void *data', )) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/memorymanagement.py0000644000175100001770000000323214543120705021672 0ustar00runnerdocker"""Interface to and utilities for R's memory management.""" import contextlib import typing from . import openrlib from _cffi_backend import FFI # type: ignore # TODO: make it extend ContextManager and delete the function # rmemory ? class ProtectionTracker(object): """Convenience class to keep track of R's C-level protection stack. Mixing this with direct calls to Rf_protect() and Rf_unprotect() in the C API from Python, or even using Rf_protect() and Rf_unprotect(), is strongly discouraged.""" def __init__(self) -> None: self._counter = 0 @property def count(self) -> int: """Return the count for the protection stack.""" return self._counter def protect(self, cdata: FFI.CData): """Pass-through function that adds the R object to the short-term stack of objects protected from garbase collection.""" cdata = openrlib.rlib.Rf_protect(cdata) self._counter += 1 return cdata def unprotect(self, n: int) -> None: """Release the n objects last added to the protection stack.""" if n > self._counter: raise ValueError('n > count') self._counter -= n openrlib.rlib.Rf_unprotect(n) def unprotect_all(self) -> None: """Release the total count of objects this instance knows to be protected from the protection stack.""" openrlib.rlib.Rf_unprotect(self._counter) self._counter = 0 @contextlib.contextmanager def rmemory() -> typing.Iterator[ProtectionTracker]: pt = ProtectionTracker() with openrlib.rlock: try: yield pt finally: pt.unprotect_all() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/na_values.py0000644000175100001770000000102314543120705020276 0ustar00runnerdocker"""NA (Non-Available) values in R.""" # TODO: Indicate the Python version(s) supported that require # `import __future__ import annotations`. from __future__ import annotations import typing if typing.TYPE_CHECKING: from rpy2.rinterface_lib import sexp NA_Character: typing.Optional[sexp.NACharacterType] = None NA_Integer: typing.Optional[sexp.NAIntegerType] = None NA_Logical: typing.Optional[sexp.NALogicalType] = None NA_Real: typing.Optional[sexp.NARealType] = None NA_Complex: typing.Optional[sexp.NAComplexType] = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/openrlib.py0000644000175100001770000001160014543120705020135 0ustar00runnerdockerimport logging import os import platform import threading import typing import rpy2.situation from rpy2.rinterface_lib import ffi_proxy logger = logging.getLogger(__name__) cffi_mode = rpy2.situation.get_cffi_mode() if cffi_mode == rpy2.situation.CFFI_MODE.API: import _rinterface_cffi_api as _rinterface_cffi # type: ignore elif cffi_mode == rpy2.situation.CFFI_MODE.ABI: import _rinterface_cffi_abi as _rinterface_cffi # type: ignore else: try: import _rinterface_cffi_api as _rinterface_cffi # type: ignore except ImportError as ie_api: try: import _rinterface_cffi_abi as _rinterface_cffi # type: ignore except ImportError as ie_abi: logger.error(f'Failed to import the API mode with "{ie_api}" ' 'and unable to import the ABI mode.') raise ie_abi ffi = _rinterface_cffi.ffi # TODO: Separate the functions in the module from the side-effect of # finding R_HOME and opening the shared library. R_HOME = rpy2.situation.get_r_home() if os.name != 'nt': # not relevant for Windows? (https://stackoverflow.com/questions/72575015) LD_LIBRARY_PATH = ( rpy2.situation.r_ld_library_path_from_subprocess(R_HOME) if R_HOME is not None else '') rlock = threading.RLock() def _dlopen_rlib(r_home: typing.Optional[str]): """Open R's shared C library. This is only relevant in ABI mode.""" if r_home is None: raise ValueError('r_home is None. ' 'Try python -m rpy2.situation') lib_path = rpy2.situation.get_rlib_path(r_home, platform.system()) if lib_path is None: raise ValueError('The library path cannot be None.') else: rlib = ffi.dlopen(lib_path) return rlib if ffi_proxy.get_ffi_mode(_rinterface_cffi) == ffi_proxy.InterfaceType.API: rlib = _rinterface_cffi.lib else: rlib = _dlopen_rlib(R_HOME) # R macros and functions def _get_symbol_or_fallback(symbol: str, fallback: typing.Any): """Get a cffi object from rlib, or the fallback if missing.""" try: res = getattr(rlib, symbol) except (ffi.error, AttributeError): res = fallback return res def _get_dataptr_fallback(vec): # DATAPTR seems to be the following macro in R < 3.5 # but I cannot get it to work (seems to be pointing # to incorrect memory region). # (((SEXPREC_ALIGN *)(x)) + 1) raise NotImplementedError() DATAPTR = _get_symbol_or_fallback('DATAPTR', _get_dataptr_fallback) def _LOGICAL(x): return ffi.cast('int *', DATAPTR(x)) LOGICAL = rlib.LOGICAL def _INTEGER(x): return ffi.cast('int *', DATAPTR(x)) INTEGER = rlib.INTEGER def _RAW(x): return ffi.cast('Rbyte *', DATAPTR(x)) RAW = rlib.RAW def _REAL(robj): return ffi.cast('double *', DATAPTR(robj)) REAL = rlib.REAL def _COMPLEX(robj): return ffi.cast('Rcomplex *', DATAPTR(robj)) COMPLEX = rlib.COMPLEX def _get_raw_elt_fallback(vec, i: int): return RAW(vec)[i] RAW_ELT = _get_symbol_or_fallback('RAW_ELT', _get_raw_elt_fallback) def _get_integer_elt_fallback(vec, i: int): return INTEGER(vec)[i] INTEGER_ELT = _get_symbol_or_fallback('INTEGER_ELT', _get_integer_elt_fallback) def _set_integer_elt_fallback(vec, i: int, value: int): INTEGER(vec)[i] = value SET_INTEGER_ELT = _get_symbol_or_fallback('SET_INTEGER_ELT', _set_integer_elt_fallback) def _get_logical_elt_fallback(vec, i: int): return LOGICAL(vec)[i] LOGICAL_ELT = _get_symbol_or_fallback('LOGICAL_ELT', _get_logical_elt_fallback) def _set_logical_elt_fallback(vec, i: int, value): LOGICAL(vec)[i] = value SET_LOGICAL_ELT = _get_symbol_or_fallback('SET_LOGICAL_ELT', _set_logical_elt_fallback) def _get_real_elt_fallback(vec, i: int): return REAL(vec)[i] REAL_ELT = _get_symbol_or_fallback('REAL_ELT', _get_real_elt_fallback) def _set_real_elt_fallback(vec, i: int, value: float): REAL(vec)[i] = value SET_REAL_ELT = _get_symbol_or_fallback('SET_REAL_ELT', _set_real_elt_fallback) def _get_complex_elt_fallback(vec, i: int): return COMPLEX(vec)[i] COMPLEX_ELT = _get_symbol_or_fallback('COMPLEX_ELT', _get_complex_elt_fallback) def SET_COMPLEX_ELT(vec, i: int, value: complex): COMPLEX(vec)[i].r = value.real COMPLEX(vec)[i].i = value.imag # TODO: still useful or is it in the C API ? def _VECTOR_ELT(robj, i): return ffi.cast('SEXP *', DATAPTR(robj))[i] def _STRING_PTR(robj): return ffi.cast('SEXP *', DATAPTR(robj)) def _VECTOR_PTR(robj): return ffi.cast('SEXP *', DATAPTR(robj)) def _STRING_VALUE(robj): return rlib.R_CHAR(rlib.Rf_asChar(robj)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rinterface_lib/sexp.py0000644000175100001770000010732214543120705017311 0ustar00runnerdocker"""Base definitions for R objects.""" import abc import collections.abc from collections import OrderedDict import enum import itertools import typing from rpy2.rinterface_lib import embedded from rpy2.rinterface_lib import memorymanagement from rpy2.rinterface_lib import openrlib import rpy2.rinterface_lib._rinterface_capi as _rinterface from rpy2.rinterface_lib._rinterface_capi import _evaluated_promise from rpy2.rinterface_lib._rinterface_capi import SupportsSEXP from rpy2.rinterface_lib import conversion from rpy2.rinterface_lib.conversion import _cdata_res_to_rinterface from rpy2.rinterface_lib import na_values class Singleton(type): _instances: typing.Dict[typing.Type['Singleton'], 'Singleton'] = {} def __call__(cls, *args, **kwargs): instances = cls._instances if cls not in instances: instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return instances[cls] class SingletonABC(Singleton, abc.ABCMeta): pass class RTYPES(enum.IntEnum): """Native R types as defined in R's C API.""" NILSXP = openrlib.rlib.NILSXP SYMSXP = openrlib.rlib.SYMSXP LISTSXP = openrlib.rlib.LISTSXP CLOSXP = openrlib.rlib.CLOSXP ENVSXP = openrlib.rlib.ENVSXP PROMSXP = openrlib.rlib.PROMSXP LANGSXP = openrlib.rlib.LANGSXP SPECIALSXP = openrlib.rlib.SPECIALSXP BUILTINSXP = openrlib.rlib.BUILTINSXP CHARSXP = openrlib.rlib.CHARSXP LGLSXP = openrlib.rlib.LGLSXP INTSXP = openrlib.rlib.INTSXP REALSXP = openrlib.rlib.REALSXP CPLXSXP = openrlib.rlib.CPLXSXP STRSXP = openrlib.rlib.STRSXP DOTSXP = openrlib.rlib.DOTSXP ANYSXP = openrlib.rlib.ANYSXP VECSXP = openrlib.rlib.VECSXP EXPRSXP = openrlib.rlib.EXPRSXP BCODESXP = openrlib.rlib.BCODESXP EXTPTRSXP = openrlib.rlib.EXTPTRSXP WEAKREFSXP = openrlib.rlib.WEAKREFSXP RAWSXP = openrlib.rlib.RAWSXP S4SXP = openrlib.rlib.S4SXP NEWSXP = openrlib.rlib.NEWSXP FREESXP = openrlib.rlib.FREESXP FUNSXP = openrlib.rlib.FUNSXP # The following constants can be use to create Python proxies # for R objects while R has not been initialized yet. UNINIT_CAPSULE_CHAR = _rinterface.UninitializedRCapsule(RTYPES.CHARSXP.value) UNINIT_CAPSULE_INTEGER = _rinterface.UninitializedRCapsule(RTYPES.INTSXP.value) UNINIT_CAPSULE_LOGICAL = _rinterface.UninitializedRCapsule(RTYPES.LGLSXP.value) UNINIT_CAPSULE_REAL = _rinterface.UninitializedRCapsule(RTYPES.REALSXP.value) UNINIT_CAPSULE_CPLX = _rinterface.UninitializedRCapsule(RTYPES.CPLXSXP.value) UNINIT_CAPSULE_ENV = _rinterface.UninitializedRCapsule(RTYPES.ENVSXP.value) class Sexp(SupportsSEXP): """Base class for R objects. The name of a class corresponds to the name SEXP used in R's C API.""" __slots__ = ('_sexpobject', ) def __init__(self, sexp: typing.Union[SupportsSEXP, '_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']): if isinstance(sexp, SupportsSEXP): self._sexpobject = sexp.__sexp__ elif isinstance(sexp, _rinterface.CapsuleBase): self._sexpobject = sexp else: raise ValueError( 'The constructor must be called ' 'with an instance of rpy2.rinterface.Sexp ' 'or an instance of ' 'rpy2.rinterface._rinterface.SexpCapsule') def __repr__(self) -> str: return super().__repr__() + (' [%s]' % self.typeof) @property def __sexp__(self) -> typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']: """Access to the underlying C pointer to the R object. When assigning a new SexpCapsule to this attribute, the R C-level type of the new capsule must be equal to the type of the old capsule. A ValueError is raised otherwise.""" return self._sexpobject @__sexp__.setter def __sexp__(self, value: typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']) -> None: assert isinstance(value, _rinterface.SexpCapsule) if value.typeof != self.__sexp__.typeof: raise ValueError('New capsule type mismatch: %s' % RTYPES(value.typeof)) self._sexpobject = value @property def __sexp_refcount__(self) -> int: """Count the number of independent Python references to the underlying R object.""" return _rinterface._R_PRESERVED[ _rinterface.get_rid(self.__sexp__._cdata) ] def __getstate__(self) -> bytes: with memorymanagement.rmemory() as rmemory: ser = rmemory.protect( _rinterface.serialize( self.__sexp__._cdata, globalenv.__sexp__._cdata) ) n = openrlib.rlib.Rf_xlength(ser) res = bytes(_rinterface.ffi.buffer(openrlib.rlib.RAW(ser), n)) return res def __setstate__(self, state: bytes) -> None: self._sexpobject = unserialize(state) @property def rclass(self) -> 'StrSexpVector': """Get or set the R "class" attribute for the object.""" return rclass_get(self.__sexp__) @rclass.setter def rclass(self, value: 'typing.Union[StrSexpVector, str]'): rclass_set(self.__sexp__, value) @property def rid(self) -> int: """ID of the underlying R object (memory address).""" return _rinterface.get_rid(self.__sexp__._cdata) @property def typeof(self) -> RTYPES: return RTYPES(_rinterface._TYPEOF(self.__sexp__._cdata)) @property def named(self) -> int: return _rinterface._NAMED(self.__sexp__._cdata) @conversion._cdata_res_to_rinterface def list_attrs(self) -> 'typing.Union[StrSexpVector, str]': return _rinterface._list_attrs(self.__sexp__._cdata) @conversion._cdata_res_to_rinterface def do_slot(self, name: str) -> None: _rinterface._assert_valid_slotname(name) cchar = conversion._str_to_cchar(name) with memorymanagement.rmemory() as rmemory: name_r = rmemory.protect(openrlib.rlib.Rf_install(cchar)) if not _rinterface._has_slot(self.__sexp__._cdata, name_r): raise LookupError(name) res = openrlib.rlib.R_do_slot(self.__sexp__._cdata, name_r) return res def do_slot_assign(self, name: str, value) -> None: _rinterface._assert_valid_slotname(name) cchar = conversion._str_to_cchar(name) with memorymanagement.rmemory() as rmemory: name_r = rmemory.protect(openrlib.rlib.Rf_install(cchar)) cdata = rmemory.protect(conversion._get_cdata(value)) openrlib.rlib.R_do_slot_assign(self.__sexp__._cdata, name_r, cdata) @conversion._cdata_res_to_rinterface def get_attrib(self, name: str) -> 'Sexp': res = openrlib.rlib.Rf_getAttrib(self.__sexp__._cdata, conversion._str_to_charsxp(name)) return res # TODO: deprecate this (and implement __eq__) ? def rsame(self, sexp) -> bool: if isinstance(sexp, Sexp): return self.__sexp__._cdata == sexp.__sexp__._cdata elif isinstance(sexp, _rinterface.SexpCapsule): return sexp._cdata == sexp._cdata else: raise ValueError('Not an R object.') @property def names(self) -> 'Sexp': return baseenv['names'](self) @names.setter def names(self, value) -> None: if not isinstance(value, StrSexpVector): raise ValueError('The new names should be a StrSexpVector.') openrlib.rlib.Rf_namesgets( self.__sexp__._cdata, value.__sexp__._cdata) @property @conversion._cdata_res_to_rinterface def names_from_c_attribute(self) -> 'Sexp': return openrlib.rlib.Rf_getAttrib( self.__sexp__._cdata, openrlib.rlib.R_NameSymbol) class NULLType(Sexp, metaclass=SingletonABC): """A singleton class for R's NULL.""" def __init__(self): if embedded.isready(): tmp = Sexp( _rinterface.UnmanagedSexpCapsule( openrlib.rlib.R_NilValue ) ) else: tmp = Sexp(_rinterface.UninitializedRCapsule(RTYPES.NILSXP.value)) super().__init__(tmp) def __bool__(self) -> bool: """This is always False.""" return False @property def __sexp__(self) -> typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']: return self._sexpobject @__sexp__.setter def __sexp__(self, value: typing.Union['_rinterface.SexpCapsule', '_rinterface.UninitializedRCapsule']) -> None: raise TypeError('The capsule for the R object cannot be modified.') @property def rid(self) -> int: return self._sexpobject.rid class CETYPE(enum.Enum): """Character encodings for R string.""" CE_NATIVE = openrlib.rlib.CE_NATIVE CE_UTF8 = openrlib.rlib.CE_UTF8 CE_LATIN1 = openrlib.rlib.CE_LATIN1 CE_BYTES = openrlib.rlib.CE_BYTES CE_SYMBOL = openrlib.rlib.CE_SYMBOL CE_ANY = openrlib.rlib.CE_ANY class NCHAR_TYPE(enum.Enum): """Type of string scalar in R.""" Bytes = 0 Chars = 1 Width = 2 class CharSexp(Sexp): """R's internal (C API-level) scalar for strings.""" _R_TYPE = openrlib.rlib.CHARSXP _NCHAR_MSG = openrlib.ffi.new('char []', b'rpy2.rinterface.CharSexp.nchar') @property def encoding(self) -> CETYPE: return CETYPE( openrlib.rlib.Rf_getCharCE(self.__sexp__._cdata) ) def nchar(self, what: NCHAR_TYPE = NCHAR_TYPE.Bytes) -> int: # TODO: nchar_type is not parsed properly by cffi ? return openrlib.rlib.R_nchar(self.__sexp__._cdata, what.value, openrlib.rlib.FALSE, openrlib.rlib.FALSE, self._NCHAR_MSG) class SexpEnvironment(Sexp): """Proxy for an R "environment" object. An R "environment" object can be thought of as a mix of a mapping (like a `dict`) and a scope. To make it more "Pythonic", both aspects are kept separate and the method `__getitem__` will get an item as it would for a Python `dict` while the method `find` will get an item as if it was a scope. As soon as R is initialized the following main environments become available to the user: - `globalenv`: The "workspace" for the current R process. This can be thought of as when `__name__ == '__main__'` in Python. - `baseenv`: The namespace of R's "base" package. """ @_cdata_res_to_rinterface @_evaluated_promise def find(self, key: str, wantfun: bool = False) -> Sexp: """Find an item, starting with this R environment. Raises a `KeyError` if the key cannot be found. This method is called `find` because it is somewhat different from the method :meth:`get` in Python mappings such :class:`dict`. This is looking for a key across enclosing environments, returning the first key found.""" if not isinstance(key, str): raise TypeError('The key must be a non-empty string.') elif not len(key): raise ValueError('The key must be a non-empty string.') with memorymanagement.rmemory() as rmemory: key_cchar = conversion._str_to_cchar(key, 'utf-8') symbol = rmemory.protect( openrlib.rlib.Rf_install(key_cchar) ) if wantfun: # One would expect this to be like # res = _rinterface._findfun(symbol, self.__sexp__._cdata) # but R's findfun will segfault if the symbol is not in # the environment. :/ rho = self while rho.rid != emptyenv.rid: res = rmemory.protect( _rinterface.findvar_in_frame_wrap( rho.__sexp__._cdata, symbol ) ) if _rinterface._TYPEOF(res) in (openrlib.rlib.CLOSXP, openrlib.rlib.BUILTINSXP): break # TODO: move check of R_UnboundValue to _rinterface ? res = openrlib.rlib.R_UnboundValue rho = rho.enclos else: res = _rinterface._findvar(symbol, self.__sexp__._cdata) # TODO: move check of R_UnboundValue to _rinterface ? if res == openrlib.rlib.R_UnboundValue: raise KeyError("'%s' not found" % key) return res @_cdata_res_to_rinterface @_evaluated_promise def __getitem__(self, key: str) -> typing.Any: if not isinstance(key, str): raise TypeError('The key must be a non-empty string.') elif not len(key): raise ValueError('The key must be a non-empty string.') embedded.assert_isready() with memorymanagement.rmemory() as rmemory: key_cchar = conversion._str_to_cchar(key) symbol = rmemory.protect( openrlib.rlib.Rf_install(key_cchar) ) res = rmemory.protect( _rinterface.findvar_in_frame_wrap( self.__sexp__._cdata, symbol ) ) # TODO: move check of R_UnboundValue to _rinterface if res == openrlib.rlib.R_UnboundValue: raise KeyError("'%s' not found" % key) return res def __setitem__(self, key: str, value) -> None: # TODO: move body to _rinterface-level function if not isinstance(key, str): raise TypeError('The key must be a non-empty string.') elif not len(key): raise ValueError('The key must be a non-empty string.') if (self.__sexp__._cdata == openrlib.rlib.R_BaseEnv) or \ (self.__sexp__._cdata == openrlib.rlib.R_EmptyEnv): raise ValueError('Cannot remove variables from the base or ' 'empty environments.') # TODO: call to Rf_duplicate needed ? with memorymanagement.rmemory() as rmemory: key_cchar = conversion._str_to_cchar(key) symbol = rmemory.protect( openrlib.rlib.Rf_install(key_cchar) ) cdata = rmemory.protect(conversion._get_cdata(value)) cdata_copy = rmemory.protect( openrlib.rlib.Rf_duplicate(cdata) ) openrlib.rlib.Rf_defineVar(symbol, cdata_copy, self.__sexp__._cdata) def __len__(self) -> int: with memorymanagement.rmemory() as rmemory: symbols = rmemory.protect( openrlib.rlib.R_lsInternal(self.__sexp__._cdata, openrlib.rlib.TRUE) ) n = openrlib.rlib.Rf_xlength(symbols) return n def __delitem__(self, key: str) -> None: # Testing that key is a non-empty string is implicitly # performed when checking that the key is in the environment. if key not in self: raise KeyError("'%s' not found" % key) if self.__sexp__ == baseenv.__sexp__: raise ValueError('Values from the R base environment ' 'cannot be removed.') # TODO: also check it is not R_EmpyEnv or R_BaseNamespace if self.is_locked(): ValueError('Cannot remove an item from a locked ' 'environment.') with memorymanagement.rmemory() as rmemory: key_cdata = rmemory.protect( openrlib.rlib.Rf_mkString(conversion._str_to_cchar(key)) ) _rinterface._remove(key_cdata, self.__sexp__._cdata, openrlib.rlib.Rf_ScalarLogical( openrlib.rlib.FALSE)) @_cdata_res_to_rinterface def frame(self) -> 'typing.Union[NULLType, SexpEnvironment]': """Get the parent frame of the environment.""" return openrlib.rlib.FRAME(self.__sexp__._cdata) @property @_cdata_res_to_rinterface def enclos(self) -> 'typing.Union[NULLType, SexpEnvironment]': """Get or set the enclosing environment.""" return openrlib.rlib.ENCLOS(self.__sexp__._cdata) @enclos.setter def enclos(self, value: 'SexpEnvironment') -> None: assert isinstance(value, SexpEnvironment) openrlib.rlib.SET_ENCLOS(self.__sexp__._cdata, value.__sexp__._cdata) def keys(self) -> typing.Generator[str, None, None]: """Generator over the keys (symbols) in the environment.""" with memorymanagement.rmemory() as rmemory: symbols = rmemory.protect( openrlib.rlib.R_lsInternal(self.__sexp__._cdata, openrlib.rlib.TRUE) ) n = openrlib.rlib.Rf_xlength(symbols) res = [] for i in range(n): _ = _rinterface._string_getitem(symbols, i) if _ is None: raise TypeError( 'R symbol string should not be able to be NA.' ) res.append(_) for e in res: yield e def __iter__(self) -> typing.Generator[str, None, None]: """See method `keys()`.""" return self.keys() def is_locked(self) -> bool: return openrlib.rlib.R_EnvironmentIsLocked( self.__sexp__._cdata) emptyenv = SexpEnvironment(UNINIT_CAPSULE_ENV) baseenv = SexpEnvironment(UNINIT_CAPSULE_ENV) globalenv = SexpEnvironment(UNINIT_CAPSULE_ENV) NULL = NULLType() VT = typing.TypeVar('VT', bound='SexpVector') # TODO: move to _rinterface-level function (as ABI / API compatibility # will have API-defined code compile for efficiency). def _populate_r_vector(iterable, r_vector, set_elt, cast_value) -> None: for i, v in enumerate(iterable): set_elt(r_vector, i, cast_value(v)) class SexpVectorAbstract(SupportsSEXP, typing.Generic[VT], metaclass=abc.ABCMeta): @property @abc.abstractmethod def _R_TYPE(self): pass @property @abc.abstractmethod def _R_SIZEOF_ELT(self): pass @staticmethod @abc.abstractmethod def _CAST_IN(o): pass @staticmethod @abc.abstractmethod def _R_SET_VECTOR_ELT(x, i, v): pass @staticmethod @abc.abstractmethod def _R_VECTOR_ELT(x, i): pass @staticmethod @abc.abstractmethod def _R_GET_PTR(o): pass @classmethod @_cdata_res_to_rinterface def from_iterable(cls, iterable, populate_func=None, set_elt=None, cast_value=None) -> VT: """Create an R vector/array from an iterable.""" if not embedded.isready(): raise embedded.RNotReadyError('Embedded R is not ready to use.') if populate_func is None: populate_func = _populate_r_vector if set_elt is None: set_elt = cls._R_SET_VECTOR_ELT if cast_value is None: cast_value = cls._CAST_IN n = len(iterable) with memorymanagement.rmemory() as rmemory: r_vector = rmemory.protect( openrlib.rlib.Rf_allocVector( cls._R_TYPE, n) ) populate_func(iterable, r_vector, set_elt, cast_value) return r_vector @classmethod def _raise_incompatible_C_size(cls, mview): msg = ( 'Incompatible C type sizes. ' 'The R array type is "{r_type}" with {r_size} byte{r_size_pl} ' 'per item ' 'while the Python array type is "{py_type}" with {py_size} ' 'byte{py_size_pl} per item.' .format(r_type=cls._R_TYPE, r_size=cls._R_SIZEOF_ELT, r_size_pl='s' if cls._R_SIZEOF_ELT > 1 else '', py_type=mview.format, py_size=mview.itemsize, py_size_pl='s' if mview.itemsize > 1 else '') ) raise ValueError(msg) @classmethod def _check_C_compatible(cls, mview): return mview.itemsize == cls._R_SIZEOF_ELT @classmethod @_cdata_res_to_rinterface def from_memoryview(cls, mview: memoryview) -> VT: """Create an R vector/array from a memoryview. The memoryview must be contiguous, and the C representation for the vector must be compatible between R and Python. If not the case, a :class:`ValueError` exception with will be raised.""" if not embedded.isready(): raise embedded.RNotReadyError('Embedded R is not ready to use.') if not mview.contiguous: raise ValueError('The memory view must be contiguous.') if not cls._check_C_compatible(mview): cls._raise_incompatible_C_size(mview) r_vector = None n = len(mview) with memorymanagement.rmemory() as rmemory: r_vector = rmemory.protect( openrlib.rlib.Rf_allocVector( cls._R_TYPE, n) ) dest_ptr = cls._R_GET_PTR(r_vector) src_ptr = _rinterface.ffi.from_buffer(mview) nbytes = n * mview.itemsize _rinterface.ffi.memmove(dest_ptr, src_ptr, nbytes) return r_vector @classmethod def from_object(cls, obj) -> VT: """Create an R vector/array from a Python object, if possible. An exception :class:`ValueError` will be raised if not possible.""" try: mv = memoryview(obj) res = cls.from_memoryview(mv) except (TypeError, ValueError): try: res = cls.from_iterable(obj) except ValueError: msg = ('The class methods from_memoryview() and ' 'from_iterable() both failed to make a {} ' 'from an object of class {}' .format(cls, type(obj))) raise ValueError(msg) return res def __getitem__( self, i: typing.Union[int, slice]) -> typing.Union[Sexp, VT, typing.Any]: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) res = conversion._cdata_to_rinterface( self._R_VECTOR_ELT(cdata, i_c)) elif isinstance(i, slice): res = self.from_iterable( [ self._R_VECTOR_ELT( cdata, i_c, ) for i_c in range(*i.indices(len(self))) ], cast_value=lambda x: x ) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) return res def __setitem__(self, i: typing.Union[int, slice], value) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) if isinstance(value, Sexp): val_cdata = value.__sexp__._cdata else: val_cdata = conversion._python_to_cdata(value) self._R_SET_VECTOR_ELT(cdata, i_c, val_cdata) elif isinstance(i, slice): for i_c, v in zip(range(*i.indices(len(self))), value): self._R_SET_VECTOR_ELT(cdata, i_c, v.__sexp__._cdata) else: raise TypeError( 'Indices must be integers or slices, not %s' % type(i)) def __len__(self) -> int: return openrlib.rlib.Rf_xlength(self.__sexp__._cdata) def __iter__(self) -> typing.Iterator[typing.Union[Sexp, VT, typing.Any]]: for i in range(len(self)): yield self[i] def index(self, item: typing.Any) -> int: for i, e in enumerate(self): if e == item: return i raise ValueError("'%s' is not in R vector" % item) class SexpVector(Sexp, SexpVectorAbstract): """Base abstract class for R vector objects. R vector objects are, at the C level, essentially C arrays wrapped in the general structure for R objects.""" def __init__(self, obj: typing.Union[SupportsSEXP, _rinterface.SexpCapsule, collections.abc.Sized]): if ( isinstance(obj, SupportsSEXP) or isinstance(obj, _rinterface.SexpCapsule) ): super().__init__(obj) elif isinstance(obj, collections.abc.Sized): robj: Sexp = type(self).from_object(obj) super().__init__(robj) else: raise TypeError( 'The constructor must be called with an instance of ' 'rpy2.rinterface.Sexp, ' 'a Python sized object that can be iterated on, ' 'or less commonly an rpy2.rinterface._rinterface.SexpCapsule.' ) def _as_charsxp_cdata(x: typing.Union[CharSexp, str]): if isinstance(x, CharSexp): return x.__sexp__._cdata else: return conversion._str_to_charsxp(x) class StrSexpVector(SexpVector): """R vector of strings.""" _R_TYPE = openrlib.rlib.STRSXP _R_GET_PTR = openrlib._STRING_PTR _R_SIZEOF_ELT = None _R_VECTOR_ELT = openrlib.rlib.STRING_ELT _R_SET_VECTOR_ELT = openrlib.rlib.SET_STRING_ELT _CAST_IN = _as_charsxp_cdata def __getitem__( self, i: typing.Union[int, slice] ) -> typing.Union['StrSexpVector', str, 'NACharacterType']: cdata = self.__sexp__._cdata res: typing.Union['StrSexpVector', str, 'NACharacterType'] if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) _ = _rinterface._string_getitem(cdata, i_c) if _ is None: res = na_values.NA_Character # type: ignore else: res = _ elif isinstance(i, slice): res = self.from_iterable( [_rinterface._string_getitem(cdata, i_c) for i_c in range(*i.indices(len(self)))] ) else: raise TypeError('Indices must be integers or slices,' ' not %s' % type(i)) return res def __setitem__( self, i: typing.Union[int, slice], value: typing.Union[str, typing.Sequence[typing.Optional[str]], 'StrSexpVector', 'NACharacterType'] ) -> None: cdata = self.__sexp__._cdata if isinstance(i, int): i_c = _rinterface._python_index_to_c(cdata, i) if isinstance(value, Sexp): val_cdata = value.__sexp__._cdata else: if not isinstance(value, str): value = str(value) val_cdata = _as_charsxp_cdata(value) self._R_SET_VECTOR_ELT( cdata, i_c, val_cdata ) elif isinstance(i, slice): value_slice: typing.Iterable if ( isinstance(value, NACharacterType) or isinstance(value, str) ): value_slice = itertools.cycle((value, )) elif len(value) == 1: value_slice = itertools.cycle(value) else: value_slice = value for i_c, _ in zip(range(*i.indices(len(self))), value_slice): if _ is None: v_cdata = openrlib.rlib.R_NaString else: if isinstance(_, str): v = _ else: v = str(_) v_cdata = _as_charsxp_cdata(v) self._R_SET_VECTOR_ELT( cdata, i_c, v_cdata ) else: raise TypeError('Indices must be integers or slices, ' 'not %s' % type(i)) def get_charsxp(self, i: int) -> CharSexp: """Get the R CharSexp objects for the index i.""" i_c = _rinterface._python_index_to_c(self.__sexp__._cdata, i) return CharSexp( _rinterface.SexpCapsule( openrlib.rlib.STRING_ELT(self.__sexp__._cdata, i_c) ) ) class RVersion(metaclass=Singleton): _version = None def __init__(self): assert embedded.isinitialized() robj = StrSexpVector(['R.version']) with memorymanagement.rmemory() as rmemory: parsed = _rinterface._parse(robj.__sexp__._cdata, 1, rmemory) res = baseenv['eval'](parsed) self._version = OrderedDict((k, v[0]) for k, v in zip(res.names, res)) def __getitem__(self, k): return self._version[k] def keys(self): return self._version.keys() _TYPE2STR = { RTYPES.NILSXP: 'NULL', RTYPES.SYMSXP: 'symbol', # alias: name RTYPES.LISTSXP: 'pairlist', RTYPES.CLOSXP: 'closure', RTYPES.ENVSXP: 'environment', RTYPES.PROMSXP: 'promise', RTYPES.LANGSXP: 'language', RTYPES.SPECIALSXP: 'special', RTYPES.BUILTINSXP: 'builtin', RTYPES.CHARSXP: 'char', RTYPES.LGLSXP: 'logical', RTYPES.INTSXP: 'integer', RTYPES.REALSXP: 'double', # alias: numeric RTYPES.CPLXSXP: 'complex', RTYPES.STRSXP: 'character', RTYPES.DOTSXP: '...', RTYPES.ANYSXP: 'any', RTYPES.EXPRSXP: 'expression', RTYPES.VECSXP: 'list', RTYPES.EXTPTRSXP: 'externalptr', RTYPES.BCODESXP: 'bytecode', RTYPES.WEAKREFSXP: 'weakref', RTYPES.RAWSXP: 'raw', RTYPES.S4SXP: 'S4' } def rclass_get(scaps: _rinterface.CapsuleBase) -> StrSexpVector: """ Get the R class name. If no specific attribute "class" is defined from the objects, this will perform the equivalent of R_data_class() (src/main/attrib.c in the R source code). """ rlib = openrlib.rlib with memorymanagement.rmemory() as rmemory: classes = rmemory.protect( rlib.Rf_getAttrib(scaps._cdata, rlib.R_ClassSymbol)) if rlib.Rf_length(classes) == 0: classname: typing.Tuple[str, ...] dim = rmemory.protect( rlib.Rf_getAttrib(scaps._cdata, rlib.R_DimSymbol)) ndim = rlib.Rf_length(dim) if ndim > 0: if ndim == 2: if int(RVersion()['major']) >= 4: classname = ('matrix', 'array') else: classname = ('matrix', ) else: classname = ('array', ) else: typeof = RTYPES(scaps.typeof) if typeof in (RTYPES.CLOSXP, RTYPES.SPECIALSXP, RTYPES.BUILTINSXP): classname = ('function', ) elif typeof == RTYPES.REALSXP: classname = ('numeric', ) elif typeof == RTYPES.SYMSXP: classname = ('name', ) elif typeof == RTYPES.LANGSXP: symb = rlib.CAR(scaps._cdata) if openrlib.rlib.Rf_isSymbol(symb): symb_rstr = openrlib.rlib.PRINTNAME(symb) symb_str = conversion._cchar_to_str( openrlib.rlib.R_CHAR(symb_rstr), conversion._R_ENC_PY[openrlib.rlib .Rf_getCharCE(symb_rstr)] ) if symb_str in ('if', 'while', 'for', '=', '<-', '(', '{'): classname = (symb_str, ) else: classname = ('call', ) else: classname = ('call', ) else: classname = (_TYPE2STR.get(typeof, str(typeof)), ) classes = StrSexpVector.from_iterable(classname) else: classes = conversion._cdata_to_rinterface(classes) return classes def rclass_set( scaps: _rinterface.CapsuleBase, value: 'typing.Union[StrSexpVector, str]' ) -> None: """ Set the R class. :param:`scaps` A capsule with a pointer to an R object. :param:`value` An R vector of strings.""" if isinstance(value, StrSexpVector): value_r = value elif isinstance(value, str): value_r = StrSexpVector.from_iterable( [value]) else: raise TypeError('Value should a str or ' 'a rpy2.rinterface.sexp.StrSexpVector.') openrlib.rlib.Rf_setAttrib(scaps._cdata, openrlib.rlib.R_ClassSymbol, value_r.__sexp__._cdata) def unserialize(state): n = len(state) with memorymanagement.rmemory() as rmemory: cdata = rmemory.protect( openrlib.rlib.Rf_allocVector(openrlib.rlib.RAWSXP, n)) _rinterface.ffi.memmove( openrlib.rlib.RAW(cdata), state, n) ser = rmemory.protect( _rinterface.unserialize(cdata, globalenv.__sexp__._cdata) ) res = _rinterface.SexpCapsule(ser) return res class NAIntegerType(int, metaclass=Singleton): def __new__(cls, *args, **kwargs): embedded.assert_isready() return super().__new__(cls, openrlib.rlib.R_NaInt) def __repr__(self) -> str: return 'NA_integer_' def __str__(self) -> str: return 'NA_integer_' def __bool__(self): raise ValueError('R value for missing integer value') class NACharacterType(CharSexp, metaclass=SingletonABC): def __init__(self): embedded.assert_isready() super().__init__( CharSexp( _rinterface.SexpCapsule(openrlib.rlib.R_NaString) ) ) def __repr__(self) -> str: return 'NA_character_' def __str__(self) -> str: return 'NA_character_' def __bool__(self): raise ValueError('R value for missing character value') class NALogicalType(int, metaclass=Singleton): def __new__(cls, *args, **kwargs): embedded.assert_isready() return super().__new__(cls, openrlib.rlib.R_NaInt) def __repr__(self) -> str: return 'NA' def __str__(self) -> str: return 'NA' def __bool__(self) -> bool: raise ValueError('R value for missing boolean value') class NARealType(float, metaclass=Singleton): def __new__(cls, *args, **kwargs): embedded.assert_isready() return super().__new__(cls, openrlib.rlib.R_NaReal) def __repr__(self) -> str: return 'NA_real_' def __str__(self) -> str: return 'NA_real_' def __bool__(self) -> bool: raise ValueError('R value for missing float value') class NAComplexType(complex, metaclass=Singleton): def __new__(cls, *args, **kwargs): embedded.assert_isready() return super().__new__(cls, openrlib.rlib.R_NaReal, openrlib.rlib.R_NaReal) def __repr__(self) -> str: return 'NA_complex_' def __str__(self) -> str: return 'NA_complex_' def __bool__(self): raise ValueError('R value for missing complex value') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8579466 rpy2-3.5.15/rpy2/rlike/0000755000175100001770000000000014543120757014120 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rlike/__init__.py0000644000175100001770000000000014543120705016210 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rlike/container.py0000644000175100001770000002207414543120705016452 0ustar00runnerdockerimport rpy2.rlike.indexing as rli from typing import Any from typing import Iterable from typing import List from typing import Optional from typing import Tuple class OrdDict(dict): """ Implements the Ordered Dict API defined in PEP 372. When `odict` becomes part of collections, this class should inherit from it rather than from `dict`. This class differs a little from the Ordered Dict proposed in PEP 372 by the fact that: not all elements have to be named. None as a key value means an absence of name for the element. """ __l: List[Tuple[Optional[str], Any]] def __init__(self, c: Iterable[Tuple[Optional[str], Any]]=[]): if isinstance(c, TaggedList) or isinstance(c, OrdDict): c = c.items() elif isinstance(c, dict): # FIXME: allow instance from OrdDict ? raise TypeError('A regular dictionnary does not ' + 'conserve the order of its keys.') super(OrdDict, self).__init__() self.__l = [] for k, v in c: self[k] = v def __copy__(self): cp = OrdDict(c=tuple(self.items())) return cp def __reduce__(self): # We need to override the special-cased dict unpickling process in order # to retain the attributes the `__l` attribute. return ( self.__class__, # callable (), # arguments to constructor {'_OrdDict__l': self.__l}, # state None, # list items iter(self.items()), # dict items ) def __cmp__(self, o): return NotImplemented def __eq__(self, o): return NotImplemented def __getitem__(self, key: str): if key is None: raise ValueError("Unnamed items cannot be retrieved by key.") i = super(OrdDict, self).__getitem__(key) return self.__l[i][1] def __iter__(self): seq = self.__l for e in seq: k = e[0] if k is None: continue else: yield k def __len__(self): return len(self.__l) def __ne__(self, o): return NotImplemented def __repr__(self) -> str: s = ['o{', ] for k, v in self.items(): s.append("'%s': %s, " % (str(k), str(v))) s.append('}') return ''.join(s) def __reversed__(self): raise NotImplementedError("Not yet implemented.") def __setitem__(self, key: Optional[str], value: Any): """ Replace the element if the key is known, and conserve its rank in the list, or append it if unknown. """ if key is None: self.__l.append((key, value)) return if key in self: i = self.index(key) self.__l[i] = (key, value) else: self.__l.append((key, value)) super(OrdDict, self).__setitem__(key, len(self.__l)-1) def byindex(self, i: int) -> Any: """ Fetch a value by index (rank), rather than by key.""" return self.__l[i] def index(self, k: str) -> int: """ Return the index (rank) for the key 'k' """ return super(OrdDict, self).__getitem__(k) def get(self, k: str, d: Any = None): """ OD.get(k[,d]) -> OD[k] if k in OD, else d. d defaults to None """ try: res = self[k] except KeyError: res = d return res def items(self): """ OD.items() -> an iterator over the (key, value) items of D """ return iter(self.__l) def keys(self): """ """ return tuple([x[0] for x in self.__l]) def reverse(self): """ Reverse the order of the elements in-place (no copy).""" seq = self.__l n = len(self.__l) for i in range(n//2): tmp = seq[i] seq[i] = seq[n-i-1] kv = seq[i] if kv is not None: super(OrdDict, self).__setitem__(kv[0], i) seq[n-i-1] = tmp kv = tmp if kv is not None: super(OrdDict, self).__setitem__(kv[0], n-i-1) def sort(self, cmp=None, key=None, reverse=False): raise NotImplementedError("Not yet implemented.") class TaggedList(list): """ A list for which each item has a 'tag'. :param l: list :param tag: optional sequence of tags """ __tags: List[Optional[str]] def __init__(self, seq, tags=None): super(TaggedList, self).__init__(seq) if tags is None: tags = [None, ] * len(seq) if len(tags) != len(seq): raise ValueError("There must be as many tags as seq") self.__tags = list(tags) def __add__(self, tl): try: tags = tl.tags except AttributeError: raise ValueError('Can only concatenate TaggedLists.') res = TaggedList(list(self) + list(tl), tags=self.tags + tags) return res def __delitem__(self, y): super(TaggedList, self).__delitem__(y) self.__tags.__delitem__(y) def __delslice__(self, i, j): super(TaggedList, self).__delslice__(i, j) self.__tags.__delslice__(i, j) def __iadd__(self, y): super(TaggedList, self).__iadd__(y) if isinstance(y, TaggedList): self.__tags.__iadd__(y.tags) else: self.__tags.__iadd__([None, ] * len(y)) return self def __imul__(self, y): restags = self.__tags.__imul__(y) resitems = super(TaggedList, self).__imul__(y) return self def __mul__(self, y): restags = self.__tags__ * y.__tags__ resitems = super(TaggedList, self).__mul__(y) return type(self)(tuple(resitems), tags=restags) def __reduce__(self): return super(TaggedList, self).__reduce__() @staticmethod def from_items(tagval): res = TaggedList([]) for k, v in tagval.items(): res.append(v, tag=k) return res def __setslice__(self, i, j, y): super(TaggedList, self).__setslice__(i, j, y) # TODO: handle TaggedList ? # self.__tags.__setslice__(i, j, [None, ]) def append(self, obj, tag=None): """ Append an object to the list :param obj: object :param tag: object """ super(TaggedList, self).append(obj) self.__tags.append(tag) def extend(self, iterable): """ Extend the list with an iterable object. :param iterable: iterable object """ if isinstance(iterable, TaggedList): itertags = iterable.itertags() else: itertags = [None, ] * len(iterable) for tag, item in zip(itertags, iterable): self.append(item, tag=tag) def insert(self, index, obj, tag=None): """ Insert an object in the list :param index: integer :param obj: object :param tag: object """ super(TaggedList, self).insert(index, obj) self.__tags.insert(index, tag) def iterontag(self, tag): """ iterate on items marked with one given tag. :param tag: object """ i = 0 for onetag in self.__tags: if tag == onetag: yield self[i] i += 1 def items(self): """ OD.items() -> an iterator over the (key, value) items of D """ for tag, item in zip(self.__tags, self): yield (tag, item) def itertags(self): """ iterate on tags. :rtype: iterator """ for tag in self.__tags: yield tag def pop(self, index=None): """ Pop the item at a given index out of the list :param index: integer """ if index is None: index = len(self) - 1 res = super(TaggedList, self).pop(index) self.__tags.pop(index) return res def remove(self, value): """ Remove a given value from the list. :param value: object """ found = False for i in range(len(self)): if self[i] == value: found = True break if found: self.pop(i) def reverse(self): """ Reverse the order of the elements in the list. """ super(TaggedList, self).reverse() self.__tags.reverse() def sort(self, reverse=False): """ Sort in place """ o = rli.order(self, reverse=reverse) super(TaggedList, self).sort(reverse=reverse) self.__tags = [self.__tags[i] for i in o] def __get_tags(self): return tuple(self.__tags) def __set_tags(self, tags): if len(tags) == len(self.__tags): self.__tags = tuple(tags) else: raise ValueError('The new list of tags should have the ' 'same length as the old one.') tags = property(__get_tags, __set_tags) def settag(self, i, t): """ Set tag 't' for item 'i'. :param i: integer (index) :param t: object (tag) """ self.__tags[i] = t ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rlike/functional.py0000644000175100001770000000173114543120705016627 0ustar00runnerdockerdef tapply(seq, tag, fun): """ Apply the function `fun` to the items in `seq`, grouped by the tags defined in `tag`. :param seq: sequence :param tag: any sequence of tags :param fun: function :rtype: list """ if len(seq) != len(tag): raise ValueError("seq and tag should have the same length.") tag_grp = {} for i, t in enumerate(tag): try: tag_grp[t].append(i) except LookupError: tag_grp[t] = [i, ] res = [(tag, fun([seq[i] for i in ti])) for tag, ti in tag_grp.items()] return res def listify(fun): """ Decorator to make a function apply to each item in a sequence, and return a list. """ def f(seq): res = [fun(x) for x in seq] return res return f def iterify(fun): """ Decorator to make a function apply to each item in a sequence, and return an iterator. """ def f(seq): for x in seq: yield fun(x) return f ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/rlike/indexing.py0000644000175100001770000000055314543120705016273 0ustar00runnerdockerdef default_key(x): """ Default comparison function.""" return x def order(seq, key=default_key, reverse=False): """ Return the order in which to take the items to obtained a sorted sequence.""" o = list(range(len(seq))) def wrap_key(x): x = seq[x] return key(x) o.sort(key=wrap_key, reverse=reverse) return o ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8619466 rpy2-3.5.15/rpy2/robjects/0000755000175100001770000000000014543120757014625 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/__init__.py0000644000175100001770000003715214543120705016737 0ustar00runnerdocker""" R objects as Python objects. The module is structured around the singleton r of class R, that represents an embedded R. License: GPLv2+ """ import array import contextlib import os import types import typing import rpy2.rinterface as rinterface import rpy2.rinterface_lib.embedded import rpy2.rinterface_lib.openrlib import rpy2.rlike.container as rlc from rpy2.robjects.robject import RObjectMixin, RObject import rpy2.robjects.functions from rpy2.robjects.environments import (Environment, local_context) from rpy2.robjects.methods import methods_env from rpy2.robjects.methods import RS4 from . import conversion from . import vectors from . import language from rpy2.rinterface import (Sexp, SexpVector, SexpClosure, SexpEnvironment, SexpS4, StrSexpVector, SexpExtPtr) from rpy2.robjects.functions import Function from rpy2.robjects.functions import SignatureTranslatedFunction _globalenv = rinterface.globalenv _reval = rinterface.baseenv['eval'] BoolVector = vectors.BoolVector IntVector = vectors.IntVector FloatVector = vectors.FloatVector ComplexVector = vectors.ComplexVector StrVector = vectors.StrVector FactorVector = vectors.FactorVector Vector = vectors.Vector PairlistVector = vectors.PairlistVector ListVector = vectors.ListVector DateVector = vectors.DateVector POSIXct = vectors.POSIXct POSIXlt = vectors.POSIXlt Array = vectors.Array Matrix = vectors.Matrix DataFrame = vectors.DataFrame # Missing values. NA_Real = rinterface.NA_Real NA_Integer = rinterface.NA_Integer NA_Logical = rinterface.NA_Logical NA_Character = rinterface.NA_Character NA_Complex = rinterface.NA_Complex NULL = rinterface.NULL # TODO: Something like this could be part of the rpy2 API. def _print_deferred_warnings() -> None: """Print R warning messages. rpy2's default pattern add a prefix per warning lines. This should be revised. In the meantime, we clean it at least for the R magic. """ with rpy2.rinterface_lib.openrlib.rlock: rinterface.evalr('.Internal(printDeferredWarnings())') def reval(string, envir=_globalenv): """ Evaluate a string as R code - string: a string - envir: an environment in which the environment should take place (default: R's global environment) """ p = rinterface.parse(string) res = _reval(p, envir=envir) return res default_converter = conversion.Converter('base empty converter') @default_converter.rpy2py.register(RObject) def _rpy2py_robject(obj): return obj VT = typing.TypeVar('VT') MT = typing.TypeVar('MT') AT = typing.TypeVar('AT') def _vector_matrix_array( obj, vector_cls: typing.Type[VT], matrix_cls: typing.Type[MT], array_cls: typing.Type[AT]) -> typing.Union[ typing.Type[VT], typing.Type[MT], typing.Type[AT]]: # Should it be promoted to array or matrix ? try: dim = obj.do_slot("dim") if len(dim) == 2: return matrix_cls else: return array_cls except Exception: return vector_cls @default_converter.rpy2py.register(rinterface.IntSexpVector) def _convert_rpy2py_intvector(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.IntSexpVector]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(rinterface.FloatSexpVector) def _convert_rpy2py_floatvector(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.FloatSexpVector]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(rinterface.ComplexSexpVector) def _convert_rpy2py_complexvector(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.ComplexSexpVector]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(rinterface.BoolSexpVector) def _convert_rpy2py_boolvector(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.BoolSexpVector]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(rinterface.StrSexpVector) def _convert_rpy2py_strvector(obj): cls = _vector_matrix_array(obj, vectors.StrVector, vectors.StrMatrix, vectors.StrArray) return cls(obj) @default_converter.rpy2py.register(rinterface.ByteSexpVector) def _convert_rpy2py_bytevector(obj): cls = _vector_matrix_array(obj, vectors.ByteVector, vectors.ByteMatrix, vectors.ByteArray) return cls(obj) default_converter.rpy2py.register(rinterface.PairlistSexpVector, PairlistVector) @default_converter.rpy2py.register(rinterface.LangSexpVector) def _convert_rpy2py_langvector(obj): if 'formula' in obj.rclass: cls = Formula else: cls = language.LangVector return cls(obj) TYPEORDER = {bool: (0, BoolVector), int: (1, IntVector), float: (2, FloatVector), complex: (3, ComplexVector), str: (4, StrVector)} def sequence_to_vector(lst): curr_typeorder = -1 i = None for i, elt in enumerate(lst): cls = type(elt) if cls in TYPEORDER: if TYPEORDER[cls][0] > curr_typeorder: curr_typeorder, curr_type = TYPEORDER[cls] else: raise ValueError('The element %i in the list has a type ' 'that cannot be handled.' % i) if i is None: raise ValueError('The parameter "lst" is an empty sequence. ' 'The type of the corresponding R vector cannot ' 'be determined.') res = curr_type(lst) return res @default_converter.py2rpy.register(rinterface._MissingArgType) def _py2rpy_missingargtype(obj): return obj @default_converter.py2rpy.register(bool) def _py2rpy_bool(obj): return obj @default_converter.py2rpy.register(int) def _py2rpy_int(obj): return obj @default_converter.py2rpy.register(float) def _py2rpy_float(obj): return obj @default_converter.py2rpy.register(bytes) def _py2rpy_bytes(obj): return obj @default_converter.py2rpy.register(str) def _py2rpy_str(obj): return obj @default_converter.rpy2py.register(SexpClosure) def _rpy2py_sexpclosure(obj): return SignatureTranslatedFunction(obj) @default_converter.rpy2py.register(SexpEnvironment) def _rpy2py_sexpenvironment(obj): return Environment(obj) @default_converter.rpy2py.register(rinterface.ListSexpVector) def _rpy2py_listsexp(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.ListSexpVector]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(SexpS4) def _rpy2py_sexps4(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[SexpS4]) cls = clsmap.find(methods_env['extends'](obj.rclass)) return cls(obj) @default_converter.rpy2py.register(SexpExtPtr) def _rpy2py_sexpextptr(obj): clsmap = (conversion.converter_ctx.get() .rpy2py_nc_name[rinterface.SexpExtPtr]) cls = clsmap.find(obj.rclass) return cls(obj) @default_converter.rpy2py.register(object) def _rpy2py_object(obj): return RObject(obj) @default_converter.rpy2py.register(type(NULL)) def _rpy2py_null(obj): return obj # TODO: delete ? def default_py2ri(o): """ Convert an arbitrary Python object to a :class:`rpy2.rinterface.Sexp` object. Creates an R object with the content of the Python object, wich means data copying. :param o: object :rtype: :class:`rpy2.rinterface.Sexp` (and subclasses) """ pass @default_converter.py2rpy.register(RObject) def _py2rpy_robject(obj): return rinterface.Sexp(obj) @default_converter.py2rpy.register(Sexp) def _py2rpy_sexp(obj): return obj @default_converter.py2rpy.register(array.array) def _py2rpy_array(obj): if obj.typecode in ('h', 'H', 'i', 'I'): res = IntVector(obj) elif obj.typecode in ('f', 'd'): res = FloatVector(obj) else: raise( ValueError('Nothing can be done for this array ' 'type at the moment.') ) return res default_converter.py2rpy.register(int, lambda x: x) @default_converter.py2rpy.register(list) def _py2rpy_list(obj): cv = conversion.get_conversion() return vectors.ListVector( rinterface.ListSexpVector( [cv.py2rpy(x) for x in obj] ) ) @default_converter.py2rpy.register(rlc.TaggedList) def _py2rpy_taggedlist(obj): cv = conversion.get_conversion() res = vectors.ListVector( rinterface.ListSexpVector([cv.py2rpy(x) for x in obj]) ) res.do_slot_assign('names', rinterface.StrSexpVector(obj.tags)) return res @default_converter.py2rpy.register(complex) def _py2rpy_complex(obj): return obj @default_converter.py2rpy.register(types.FunctionType) def _function_to_rpy(func): def wrap(*args): res = func(*args) res = conversion.py2ro(res) return res rfunc = rinterface.rternalize(wrap) return conversion.get_conversion().rpy2py(rfunc) @default_converter.rpy2py.register(object) def _(obj): return obj class ExternalPointer(RObjectMixin, rinterface.SexpExtPtr): pass default_converter._rpy2py_nc_map.update( { rinterface.SexpExtPtr: conversion.NameClassMap(ExternalPointer), rinterface.SexpS4: conversion.NameClassMap(RS4), rinterface.IntSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array( obj, vectors.IntVector, vectors.IntMatrix, vectors.IntArray)(obj), {'factor': FactorVector}), rinterface.FloatSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array( obj, vectors.FloatVector, vectors.FloatMatrix, vectors.FloatArray)(obj), {'Date': DateVector, 'POSIXct': POSIXct}), rinterface.BoolSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array( obj, vectors.BoolVector, vectors.BoolMatrix, vectors.BoolArray)(obj) ), rinterface.ByteSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array( obj, vectors.ByteVector, vectors.ByteMatrix, vectors.ByteArray)(obj) ), rinterface.StrSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array( obj, vectors.StrVector, vectors.StrMatrix, vectors.StrArray)(obj) ), rinterface.ComplexSexpVector: conversion.NameClassMap( lambda obj: _vector_matrix_array(obj, vectors.ComplexVector, vectors.ComplexMatrix, vectors.ComplexArray)(obj) ), rinterface.ListSexpVector: conversion.NameClassMap( ListVector, {'data.frame': DataFrame}), rinterface.SexpEnvironment: conversion.NameClassMap(Environment) } ) class Formula(RObjectMixin, rinterface.Sexp): def __init__(self, formula, environment=_globalenv): if isinstance(formula, str): inpackage = rinterface.baseenv["::"] asformula = inpackage(rinterface.StrSexpVector(['stats', ]), rinterface.StrSexpVector(['as.formula', ])) formula = rinterface.StrSexpVector([formula, ]) robj = asformula(formula, env=environment) else: robj = formula super(Formula, self).__init__(robj) def getenvironment(self): """ Get the environment in which the formula is finding its symbols.""" res = self.do_slot(".Environment") res = conversion.get_conversion().rpy2py(res) return res def setenvironment(self, val): """ Set the environment in which a formula will find its symbols.""" if not isinstance(val, rinterface.SexpEnvironment): raise TypeError('The environment must be an instance of' ' rpy2.rinterface.Sexp.environment') self.do_slot_assign('.Environment', val) environment = property(getenvironment, setenvironment, None, 'R environment in which the formula will look for ' 'its variables.') class R(object): """ Singleton representing the embedded R running. """ _instance = None # Default for the evaluation _print_r_warnings: bool = True _invisible: bool = True def __new__(cls): if cls._instance is None: rinterface.initr_simple() cls._instance = object.__new__(cls) return cls._instance def __getattribute__(self, attr: str) -> object: try: return super(R, self).__getattribute__(attr) except AttributeError as ae: orig_ae = str(ae) try: return self.__getitem__(attr) except LookupError: raise AttributeError(orig_ae) def __getitem__(self, item: str) -> object: res = _globalenv.find(item) res = conversion.get_conversion().rpy2py(res) if hasattr(res, '__rname__'): res.__rname__ = item return res # TODO: check that this is properly working def __cleanup__(self) -> None: rinterface.embedded.endr(0) del(self) def __str__(self) -> str: s = [super(R, self).__str__()] version = self['version'] version_k: typing.Tuple[str, ...] = tuple(version.names) # type: ignore version_v: typing.Tuple[str, ...] = tuple( x[0] for x in version # type: ignore ) for key, val in zip(version_k, version_v): s.extend('%s: %s' % (key, val)) return os.linesep.join(s) def __call__(self, string: str, invisible: typing.Optional[bool] = None, print_r_warnings: typing.Optional[bool] = None) -> object: """Evaluate a string as R code. :param string: A string with R code :param invisible: evaluate the R expression handling R's invisibility flag. When `True` expressions meant to return an "invisible" result (for example, `x <- 1`) will return None. The default is `None`, in which case the attribute _invisible is used. :param print_r_warning: When `True` the R deferred warnings are printed using the R callback function. The default is `None`, in which case the attribute _print_r_warning is used. :return: The value returned by R after rpy2 conversion.""" r_expr = rinterface.parse(string) if invisible is None: invisible = self._invisible if invisible: res, visible = rinterface.evalr_expr_with_visible( # type: ignore r_expr ) if not visible[0]: # type: ignore res = None else: res = rinterface.evalr_expr(r_expr) if print_r_warnings is None: print_r_warnings = self._print_r_warnings if print_r_warnings: _print_deferred_warnings() return (None if res is None else conversion.get_conversion().rpy2py(res)) r = R() rl = language.LangVector.from_string conversion.set_conversion(default_converter) globalenv = conversion.converter_ctx.get().rpy2py(_globalenv) baseenv = conversion.converter_ctx.get().rpy2py(rinterface.baseenv) emptyenv = conversion.converter_ctx.get().rpy2py(rinterface.emptyenv) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/constants.py0000644000175100001770000000040014543120705017176 0ustar00runnerdocker""" R objects with fixed values. """ import rpy2.rinterface as rinterface _reval = rinterface.baseenv['eval'] # NULL NULL = _reval(rinterface.parse("NULL")) # TRUE/FALSE TRUE = _reval(rinterface.parse("TRUE")) FALSE = _reval(rinterface.parse("FALSE")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/conversion.py0000644000175100001770000003153314543120705017362 0ustar00runnerdocker""" The module contains the conversions to be used by rpy2.robjects functions and methods. Conversions are initially empty place-holders, raising a NotImplementedError exception. """ import contextvars from functools import singledispatch import os from typing import Any from typing import Optional import typing import warnings from rpy2.rinterface_lib import _rinterface_capi import rpy2.rinterface_lib.sexp import rpy2.rinterface_lib.conversion import rpy2.rinterface deprecated_names = {'converter', 'py2rpy', 'rpy2py'} def __getattr__(name): if name in deprecated_names: _deprecated_name = f'_deprecated_{name}' warnings.warn( f'The use of {name} in module {__name__} is deprecated. ' f'Use {__name__}.get_conversion() instead of ' f'{__name__}.converter.', DeprecationWarning ) if name == 'converter': return globals()[_deprecated_name]() else: return globals()[_deprecated_name] raise AttributeError(f'module {__name__} has no attribute {name}') class NameClassMap(object): """Map a name (R class name) to a Python class. R class names, as returned for example by the R function `class()`, are arrays of strings representing the class lineage. This class helps mapping the class of an R object (a sequence of names) to a Python class. For example, R data frames are of class "data.frame", but are R lists (VECSEXP) at the C level. The NameClassMap for that such R VECSEXP objects would be: NameClassMap(robjects.vectors.ListVector, {'data.frame': robjects.vectors.DataFrame}) This means that the default class on the Python side will be `ListVector`, but if the R object is a "data.frame" it will be a `DataFrame`. """ _default: typing.Union[ typing.Any, typing.Callable[[typing.Any], typing.Any] ] _map: typing.Dict[ str, typing.Union[ typing.Any, typing.Callable[[typing.Any], typing.Any] ] ] default = property(lambda self: self._default) def __init__(self, defaultcls: typing.Union[ typing.Type, typing.Callable[[typing.Any], typing.Any]] = object, namemap: typing.Optional[dict] = None): if namemap is None: namemap = {} self._default = defaultcls self._map = namemap.copy() def __contains__(self, key: str) -> bool: return key in self._map def __delitem__(self, key: str) -> None: del self._map[key] def __getitem__( self, key: str ) -> typing.Union[typing.Type, typing.Callable[[typing.Any], typing.Any]]: return self._map[key] def __setitem__(self, key: str, value: typing.Union[ typing.Type[typing.Any], typing.Callable[[typing.Any], typing.Any] ]): self._map[key] = value def copy(self) -> 'NameClassMap': return NameClassMap(defaultcls=self._default, namemap=self._map.copy()) def update(self, mapping: typing.Dict[ str, typing.Union[ typing.Any, typing.Callable[[typing.Any], typing.Any] ] ], default: typing.Optional[typing.Type] = None): self._map.update(mapping) if default: self._default = default def find_key(self, keys: typing.Iterable[str]) -> typing.Optional[str]: """ Find the first mapping key in a sequence of names (keys). Args: keys (iterable): The keys are the R classes (the last being the most distant ancestor class) Returns: None if no mapping key. """ for k in keys: if k in self._map: return k return None def find( self, keys: typing.Iterable[str] ) -> typing.Union[typing.Type, typing.Callable[[typing.Any], typing.Any]]: """Find the first mapping in a sequence of names (keys). Returns the default class (specified when creating the instance if no mapping key.""" k = self.find_key(keys) if k: cls = self._map[k] else: cls = self._default return cls class NameClassMapContext(object): """Context manager to add/override in-place name->class maps.""" def __init__(self, nameclassmap: NameClassMap, d: dict): self._nameclassmap = nameclassmap self._d = d self._keep: typing.List[typing.Tuple[str, bool, Optional[str]]] = [] def __enter__(self): nameclassmap = self._nameclassmap for k, v in self._d.items(): if k in nameclassmap: restore = True orig_v = nameclassmap[k] else: restore = False orig_v = None self._keep.append((k, restore, orig_v)) nameclassmap[k] = v def __exit__(self, exc_type, exc_val, exc_tb): nameclassmap = self._nameclassmap for k, restore, orig_v in self._keep: if restore: nameclassmap[k] = orig_v else: del nameclassmap[k] return False def noconversion(obj): """ Bypass robject-level conversion. Bypass robject-level conversion, casting the object down to rinterface-level rpy2 objects. :param obj: Any object :return: Either an rinterface-level object or a Python object. """ if isinstance(obj, rpy2.rinterface_lib.sexp.Sexp): res = (rpy2.rinterface_lib.conversion ._sexpcapsule_to_rinterface(obj.__sexp__)) else: res = obj return res def overlay_converter(src: 'Converter', target: 'Converter') -> None: """Overlay a converter onto an other. :param src: source of additional conversion rules :type src: :class:`Converter` :param target: target. The conversion rules in the src will be added to this object. :type target: :class:`Converter` """ for k, v in src.py2rpy.registry.items(): # skip the root dispatch if k is object and v is _py2rpy: continue target._py2rpy.register(k, v) for k, v in src.rpy2py.registry.items(): # skip the root dispatch if k is object and v is _rpy2py: continue target._rpy2py.register(k, v) for k, v in src.rpy2py_nc_map.items(): if k in target.rpy2py_nc_map: target.rpy2py_nc_map[k].update( v._map.copy(), default=v._default ) else: target.rpy2py_nc_map[k] = NameClassMap( v._default, namemap=v._map.copy(), ) def _py2rpy(obj): """ Dummy function for py2rpy. This function will convert Python objects into rpy2.rinterface objects. """ if isinstance(obj, _rinterface_capi.SupportsSEXP): return obj raise NotImplementedError( "Conversion 'py2rpy' not defined for objects of type '%s'" % str(type(obj)) ) def _rpy2py(obj): """ Dummy function for rpy2py. This function will convert Python objects into Python (presumably non-rpy2) objects. """ raise NotImplementedError( "Conversion 'rpy2py' not defined for objects of type '%s'" % str(type(obj)) ) class Converter(object): """ Conversion between rpy2's low-level and high-level proxy objects for R objects, and Python (no R) objects. Converter objects can be added, the result being a Converter objects combining the translation rules from the different converters. """ _name: str _rpy2py_nc_map: typing.Dict[ typing.Type[rpy2.rinterface_lib.sexp.Sexp], NameClassMap ] _lineage: typing.Tuple[str, ...] name = property(lambda self: self._name) py2rpy = property(lambda self: self._py2rpy) rpy2py = property(lambda self: self._rpy2py) rpy2py_nc_map = property(lambda self: self._rpy2py_nc_map) # TODO: rpy2py_nc_name should be deprecated. rpy2py_nc_name = rpy2py_nc_map lineage = property(lambda self: self._lineage) def __init__(self, name: str, template: typing.Optional['Converter'] = None): (py2rpy, rpy2py) = Converter.make_dispatch_functions() self._name = name self._py2rpy = py2rpy self._rpy2py = rpy2py self._rpy2py_nc_map = {} lineage: typing.Tuple[str, ...] if template is None: lineage = tuple() else: _ = list(template.lineage) _.append(name) lineage = tuple(_) overlay_converter(template, self) self._lineage = lineage def __add__(self, converter: 'Converter') -> 'Converter': assert isinstance(converter, Converter) new_name = '%s + %s' % (self.name, converter.name) # create a copy of `self` as the result converter result_converter = Converter(new_name, template=self) overlay_converter(converter, result_converter) return result_converter def context(self) -> 'ConversionContext': """ Create a Conversion context to use in a `with` statement. >>> with conversion_rules.context() as cv: ... # Do something while using those conversion_rules. >>> # Do something else whith the earlier conversion rules restored. The conversion context is a *copy* of the converter object. :return: A :class:`ConversionContext` """ return ConversionContext(self) def __enter__(self): raise Exception( "Use the converter's method context instead." ) def __exit__(self, exc_type, exc_val, exc_tb): return False def __str__(self): res = [str(type(self))] for subcv in ('py2rpy', 'rpy2py'): res.append(subcv) for cls in getattr(self, subcv).registry.keys(): res.append(f'- {cls.__module__}.{cls.__name__}') if subcv == 'rpy2py': ncmap = self._rpy2py_nc_map.get(cls) if ncmap: for k, v in ncmap._map.items(): res.append(f' - {k} (in {v.__module__})') res.append('---') return os.linesep.join(res) @staticmethod def make_dispatch_functions(): py2rpy = singledispatch(_py2rpy) rpy2py = singledispatch(_rpy2py) return (py2rpy, rpy2py) def rclass_map_context(self, cls, d: typing.Dict[str, typing.Type]): return NameClassMapContext( self.rpy2py_nc_map[cls], d) class ConversionContext(object): """ Context manager for instances of class Converter. """ def __init__(self, ctx_converter): assert isinstance(ctx_converter, Converter) self._original_converter = converter_ctx.get() self.ctx_converter = Converter('Converter-%i-in-context' % id(self), template=ctx_converter) def __enter__(self): set_conversion(self.ctx_converter) return self.ctx_converter def __exit__(self, exc_type, exc_val, exc_tb): set_conversion(self._original_converter) return False localconverter = ConversionContext def _raise_missingconverter(obj): _missingconverter_msg = """ Conversion rules for `rpy2.robjects` appear to be missing. Those rules are in a Python `contextvars.ContextVar`. This could be caused by multithreading code not passing context to the thread. Check rpy2's documentation about conversions. """ raise NotImplementedError(_missingconverter_msg) missingconverter = Converter('missing') missingconverter.rpy2py.register( object, _raise_missingconverter ) missingconverter.py2rpy.register( object, _raise_missingconverter ) converter_ctx = contextvars.ContextVar('converter', default=missingconverter) def _deprecated_converter(): return converter_ctx.get('converter') def _deprecated_py2rpy( obj: Any ) -> _rinterface_capi.SupportsSEXP: return converter_ctx.get('converter').py2rpy(obj) # type: ignore def _deprecated_rpy2py( obj: Any ) -> _rinterface_capi.SupportsSEXP: return converter_ctx.get('converter').rpy2py(obj) # type: ignore def get_conversion(): """ Get the conversion rules active in the current context. """ return converter_ctx.get() def set_conversion(this_converter): """ Set conversion rules in the conversion module. :param this_converter: The conversion rules :type this_converter: :class:`Converter` """ converter_ctx.set(this_converter) set_conversion(Converter('base converter')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/environments.py0000644000175100001770000001270114543120705017720 0ustar00runnerdockerimport contextlib import os import typing import rpy2.rinterface as rinterface import rpy2.rinterface_lib.sexp as sexp from rpy2.robjects.robject import RObjectMixin from rpy2.robjects import conversion _new_env = rinterface.baseenv["new.env"] class Environment(RObjectMixin, sexp.SexpEnvironment): """ An R environement, implementing Python's mapping interface. """ def __init__(self, o: typing.Optional[sexp.SexpEnvironment] = None): if o is None: o = _new_env(hash=rinterface.BoolSexpVector([True, ])) super(sexp.SexpEnvironment, self).__init__(o) def __getitem__(self, item: str): res = super(Environment, self).__getitem__(item) res = conversion.get_conversion().rpy2py(res) # objects in a R environment have an associated name / symbol try: res.__rname__ = item except AttributeError: # the 3rd-party conversion function can return objects # for which __rname__ cannot be set (because of fixed # __slots__ and no __rname__ in the original set # of attributes) pass return res def __setitem__(self, item: str, value: typing.Any) -> None: robj = conversion.get_conversion().py2rpy(value) super(Environment, self).__setitem__(item, robj) @property def enclos(self) -> typing.Union[sexp.SexpEnvironment, sexp.NULLType]: return conversion.get_conversion().rpy2py(super().enclos) @enclos.setter def enclos(self, value: sexp.SexpEnvironment) -> None: # TODO: I can't figure out why mypy is throwing an error # here. There scope of this assignment is rather limited # (the setter in the parent class SexpEnvironment has the same # signature). super().enclos = value # type: ignore @property def frame(self) -> sexp.SexpEnvironment: return conversion.get_conversion().rpy2py(super().frame) def find(self, item: str, wantfun: bool = False): """Find an item, starting with this R environment. Raises a `KeyError` if the key cannot be found. This method is called `find` because it is somewhat different from the method :meth:`get` in Python mappings such :class:`dict`. This is looking for a key across enclosing environments, returning the first key found. :param item: string (name/symbol) :rtype: object (as returned by :func:`conversion.converter.rpy2py`) """ res = super(Environment, self).find(item, wantfun=wantfun) res = conversion.get_conversion().rpy2py(res) # TODO: There is a design issue here. The attribute __rname__ is # intended to store the symbol name of the R object but this is # meaningless for non-rpy2 objects. try: res.__rname__ = item except AttributeError: pass return res def keys(self) -> typing.Generator[str, None, None]: """ Return an iterator over keys in the environment.""" return super().keys() def items(self) -> typing.Generator[ typing.Tuple[str, sexp.Sexp], None, None]: """ Iterate through the symbols and associated objects in this R environment.""" for k, v in zip(self.keys(), self.values()): yield (k, v) def values(self) -> typing.Generator[sexp.Sexp, None, None]: """ Iterate through the objects in this R environment.""" for k in self: yield self[k] def pop(self, k: str, *args) -> sexp.Sexp: """ E.pop(k[, d]) -> v, remove the specified key and return the corresponding value. If the key is not found, d is returned if given, otherwise KeyError is raised.""" if k in self: v = self[k] del self[k] elif args: if len(args) > 1: raise ValueError('Invalid number of optional parameters.') v = args[0] else: raise KeyError(k) return v def popitem(self) -> typing.Tuple[str, sexp.Sexp]: """ E.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if E is empty. """ if len(self) == 0: raise KeyError() kv = next(self.items()) del self[kv[0]] return kv def clear(self) -> None: """ E.clear() -> None. Remove all items from D. """ # FIXME: is there a more efficient implementation (when large # number of keys) ? for k in self: del self[k] def __repr__(self): return os.linesep.join( (super(Environment, self).__repr__(), 'n items: {:d}'.format(len(self))) ) @contextlib.contextmanager def local_context( env: typing.Optional[sexp.SexpEnvironment] = None, use_rlock: bool = True ) -> typing.Iterator[Environment]: """Local context for the evaluation of R code. This is a wrapper around the rpy2.rinterface function with the same name. Args: - env: an environment to use as a context. If not specified (None, the default), a child environment to the current context is created. - use_rlock: whether to use a threading lock (see the documentation about "rlock". The default is True. Returns: Yield the environment (passed to env, or created). """ with rinterface.local_context(env=env, use_rlock=use_rlock) as lc: yield Environment(lc) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/functions.py0000644000175100001770000004035714543120705017211 0ustar00runnerdockerimport inspect import os import re import textwrap import typing from typing import Union import warnings from collections import OrderedDict from rpy2.robjects.robject import RObjectMixin import rpy2.rinterface as rinterface import rpy2.rinterface_lib.sexp from rpy2.robjects import help from rpy2.robjects import conversion from rpy2.robjects.vectors import Vector from rpy2.robjects.packages_utils import (default_symbol_r2python, default_symbol_resolve, _map_symbols, _fix_map_symbols) baseenv_ri = rinterface.baseenv # Needed to avoid circular imports. __formals = baseenv_ri.find('formals') __args = baseenv_ri.find('args') __is_null = baseenv_ri.find('is.null') def _formals_fixed(func): if func.typeof in (rpy2.rinterface_lib.sexp.RTYPES.SPECIALSXP, rpy2.rinterface_lib.sexp.RTYPES.BUILTINSXP): res = rpy2.rinterface_lib.sexp.NULL else: res = __formals(func) if res is rpy2.rinterface_lib.sexp.NULL: res = __formals(__args(func)) return res # docstring_property and DocstringProperty # from Bradley Froehle # https://gist.github.com/bfroehle/4041015 def docstring_property(class_doc): def wrapper(fget): return DocstringProperty(class_doc, fget) return wrapper class DocstringProperty(object): def __init__(self, class_doc, fget): self.fget = fget self.class_doc = class_doc def __get__(self, obj, objtype=None): if obj is None: return self.class_doc else: return self.fget(obj) def __set__(self, obj, value): raise AttributeError("Cannot set the attribute") def __delete__(self, obj): raise AttributeError("Cannot delete the attribute") def _repr_argval(obj): """ Helper functions to display an R object in the docstring. This a hack and will be hopefully replaced the extraction of information from the R help system.""" try: size = len(obj) if size == 1: if obj[0].rid == rinterface.MissingArg.rid: # no default value s = None elif obj[0].rid == rinterface.NULL.rid: s = 'rinterface.NULL' else: s = str(obj[0][0]) elif size > 1: s = '(%s, ...)' % str(obj[0][0]) else: s = str(obj) except Exception: s = str(obj) return s class Function(RObjectMixin, rinterface.SexpClosure): """ Python representation of an R function. """ __local = baseenv_ri.find('local') __call = baseenv_ri.find('call') __assymbol = baseenv_ri.find('as.symbol') __newenv = baseenv_ri.find('new.env') _local_env = None def __init__(self, *args, **kwargs): super(Function, self).__init__(*args, **kwargs) self._local_env = self.__newenv( hash=rinterface.BoolSexpVector((True, )) ) @docstring_property(__doc__) def __doc__(self) -> str: fm = _formals_fixed(self) doc = list(['Python representation of an R function.', 'R arguments:', '']) if fm is rinterface.NULL: doc.append('') else: for key, val in zip(fm.do_slot('names'), fm): if key == '...': val = 'R ellipsis (any number of parameters)' doc.append('%s: %s' % (key, _repr_argval(val))) return os.linesep.join(doc) def __call__(self, *args, **kwargs): cv = conversion.get_conversion() new_args = [cv.py2rpy(a) for a in args] new_kwargs = {} for k, v in kwargs.items(): # TODO: shouldn't this be handled by the conversion itself ? if isinstance(v, rinterface.Sexp): new_kwargs[k] = v else: new_kwargs[k] = cv.py2rpy(v) res = super(Function, self).__call__(*new_args, **new_kwargs) res = cv.rpy2py(res) return res def formals(self): """ Return the signature of the underlying R function (as the R function 'formals()' would). """ res = _formals_fixed(self) res = conversion.get_conversion().rpy2py(res) return res def rcall( self, keyvals, environment: typing.Optional[rinterface.SexpEnvironment] = None ) -> rinterface.sexp.Sexp: """ Wrapper around the parent method rpy2.rinterface.SexpClosure.rcall(). """ res = super(Function, self).rcall(keyvals, environment=environment) return res class SignatureTranslatedFunction(Function): """ Python representation of an R function, where the names in named argument are translated to valid argument names in Python. """ _prm_translate: Union[OrderedDict, dict] = {} def __init__(self, sexp: rinterface.SexpClosure, init_prm_translate=None, on_conflict='warn', symbol_r2python=default_symbol_r2python, symbol_resolve=default_symbol_resolve): super(SignatureTranslatedFunction, self).__init__(sexp) if init_prm_translate is None: self._prm_translate = OrderedDict() else: assert isinstance(init_prm_translate, dict) self._prm_translate = init_prm_translate formals = self.formals() if formals.__sexp__._cdata != rinterface.NULL.__sexp__._cdata: (symbol_mapping, conflicts, resolutions) = _map_symbols( formals.names, translation=self._prm_translate, symbol_r2python=symbol_r2python, symbol_resolve=symbol_resolve) msg_prefix = ('Conflict when converting R symbols' ' in the function\'s signature:\n- ') exception = ValueError _fix_map_symbols(symbol_mapping, conflicts, on_conflict, msg_prefix, exception) symbol_mapping.update(resolutions) # TODO: Why was this done? # reserved_pynames = set(dir(self)) self._prm_translate.update((k, v[0]) for k, v in symbol_mapping.items()) if hasattr(sexp, '__rname__'): # TODO: mypy does not use the line above and trips on # __rname__ being not always present. self.__rname__ = sexp.__rname__ # type: ignore def __call__(self, *args, **kwargs): prm_translate = self._prm_translate for k in tuple(kwargs.keys()): r_k = prm_translate.get(k, None) if r_k is not None: v = kwargs.pop(k) kwargs[r_k] = v return (super(SignatureTranslatedFunction, self) .__call__(*args, **kwargs)) pattern_link = re.compile(r'\\link\{(.+?)\}') pattern_code = re.compile(r'\\code\{(.+?)\}') pattern_samp = re.compile(r'\\samp\{(.+?)\}') class DocumentedSTFunction(SignatureTranslatedFunction): def __init__(self, sexp: rinterface.SexpClosure, init_prm_translate=None, packagename: typing.Optional[str] = None): super(DocumentedSTFunction, self).__init__(sexp, init_prm_translate=init_prm_translate) self.__rpackagename__ = packagename @docstring_property(__doc__) def __doc__(self): package = help.Package(self.__rpackagename__) page = package.fetch(self.__rname__) doc = ['Wrapper around an R function.', '', 'The docstring below is built from the R documentation.', ''] if r'\description' in page.sections: doc.append( page.to_docstring( section_names=[r'\description'] ) ) fm = _formals_fixed(self) if fm is rpy2.rinterface_lib.sexp.NULL: # If still NULL there is no argument. names = tuple() else: names = fm.do_slot('names') doc.append(self.__rname__+'(') for key, val in self._prm_translate.items(): if key == '___': description = ('(was "..."). R ellipsis ' '(any number of parameters)') else: description = _repr_argval(fm[names.index(val)]) if description is None: doc.append(' %s,' % key) else: doc.append(' %s = %s,' % (key, description)) doc.extend((')', '')) #### doc.append('Args:') for item in page.arguments(): description = ('%s ' % os.linesep).join(item.value) doc.append(' '.join((' ', item.name, ': ', description))) doc.append('') if r'\details' in page.sections: doc.append( page.to_docstring( section_names=[r'\details'] ) ) return os.linesep.join(doc) # TODO: shouldn't this be in a more central place / or more general interest ? _SCALAR_COMPAT_RTYPES = set( getattr(rinterface.RTYPES, name).value for name in ('STRSXP', 'INTSXP', 'REALSXP', 'LGLSXP', 'CPLXSXP') ) def _map_default_value(value: rinterface.Sexp): """ Map default in the R signature. Because of R's lazy evaluation some default might be unevaluated expressions. Args: value: """ if value.__sexp__.typeof in _SCALAR_COMPAT_RTYPES: # TODO: The dynamic check of typeof (to ensure that that # the underlying R object is of a compatible type makes # mypy trip. There is no way to check type outside of runtime. # Code refactoring would be needed. if len(value) == 1: # type: ignore res = value[0] # type: ignore else: res = value else: res = value return res def map_signature( r_func: SignatureTranslatedFunction, is_method: bool = False, map_default: typing.Optional[ typing.Callable[[rinterface.Sexp], typing.Any] ] = _map_default_value ) -> typing.Tuple[inspect.Signature, typing.Optional[int]]: """ Map the signature of an function to the signature of a Python function. While mapping the signature, it will report the eventual presence of an R ellipsis. Args: r_func (SignatureTranslatedFunction): an R function is_method (bool): Whether the function should be treated as a method (adds a `self` param to the signature if so). map_default (function): Function to map default values in the Python signature. No mapping to default values is done if None. Returns: A tuple (inspect.Signature, int or None). """ params = [] r_ellipsis = None if is_method: params.append(inspect.Parameter('self', inspect.Parameter.POSITIONAL_ONLY)) r_params = r_func.formals() rev_prm_transl = {v: k for k, v in r_func._prm_translate.items()} if r_params.names is not rinterface.NULL: for i, (name, default_orig) in enumerate( zip(r_params.names, r_params) ): if default_orig == '...': r_ellipsis = i warnings.warn('The R ellispsis is not yet well supported.') transl_name = rev_prm_transl.get(name) if isinstance(default_orig, Vector): default_orig = default_orig[0] if map_default and not rinterface.MissingArg.rsame(default_orig): default_mapped = map_default(default_orig) else: default_mapped = inspect.Parameter.empty else: default_mapped = default_orig prm = inspect.Parameter( transl_name if transl_name else name, inspect.Parameter.POSITIONAL_OR_KEYWORD, default=default_mapped ) params.append(prm) return (inspect.Signature(params), r_ellipsis) def wrap_docstring_default( r_func: SignatureTranslatedFunction, is_method: bool, signature: inspect.Signature, r_ellipsis: typing.Optional[int], *, full_repr: bool = False ) -> str: """ Create a docstring that for a wrapped function. Args: r_func (SignatureTranslatedFunction): an R function is_method (bool): Whether the function should be treated as a method (a `self` parameter is added to the signature if so). signature (inspect.Signature): A mapped signature for `r_func` r_ellipsis (bool): Index of the parameter containing the R ellipsis (`...`). None if the R ellipsis is not in the function signature. full_repr (bool): Whether to have the full body of the R function in the docstring dynamically generated. Returns: A string. """ docstring = [] docstring.append('This {} wraps the following R function.' .format('method' if is_method else 'function')) if r_ellipsis: docstring.extend( ('', textwrap.dedent( """The R ellipsis "..." present in the function's parameters is mapped to a python iterable of (name, value) pairs (such as it is returned by the `dict` method `items()` for example.""" ), '') ) if full_repr: docstring.append('\n{}'.format(r_func.r_repr())) else: r_repr = r_func.r_repr() i = r_repr.find('\n{') if i == -1: docstring.append('\n{}'.format(r_func.r_repr())) else: docstring.append('\n{}\n{{\n ...\n}}'.format(r_repr[:i])) return '\n'.join(docstring) def wrap_r_function( r_func: SignatureTranslatedFunction, name: str, *, is_method: bool = False, full_repr: bool = False, map_default: typing.Optional[ typing.Callable[[rinterface.Sexp], typing.Any] ] = _map_default_value, wrap_docstring: typing.Optional[ typing.Callable[[SignatureTranslatedFunction, bool, inspect.Signature, typing.Optional[int]], str] ] = wrap_docstring_default ) -> typing.Callable: """ Wrap an rpy2 function handle with a Python function of matching signature. Args: r_func (rpy2.robjects.functions.SignatureTranslatedFunction): The function to be wrapped. name (str): The name of the function. is_method (bool): Whether the function should be treated as a method (adds a `self` param to the signature if so). map_default (function): Function to map default values in the Python signature. No mapping to default values is done if None. Returns: A function wrapping an underlying R function. """ name = name.replace('.', '_') signature, r_ellipsis = map_signature(r_func, is_method=is_method, map_default=map_default) if r_ellipsis: def wrapped_func(*args, **kwargs): new_args = ( list((None, x) for x in rinterface.args[:r_ellipsis]) + list(args[r_ellipsis]) + list((None, x) for x in args[min(r_ellipsis+1, len(args)-1):]) + list(kwargs.items())) value = r_func.rcall(new_args) return value else: def wrapped_func(*args, **kwargs): value = r_func(*args, **kwargs) return value if wrap_docstring: docstring = wrap_docstring(r_func, is_method, signature, r_ellipsis) else: docstring = 'This is a dynamically created wrapper for an R function.' wrapped_func.__name__ = name wrapped_func.__qualname__ = name # TODO: Open issue in mypy about __signature. # https://github.com/python/mypy/issues/5958 # Ignore the type check for now. wrapped_func.__signature__ = signature # type: ignore wrapped_func.__doc__ = docstring return wrapped_func ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/help.py0000644000175100001770000003422714543120705016130 0ustar00runnerdocker""" R help system. """ import os from collections import namedtuple import re import sqlite3 import typing import warnings import rpy2.rinterface as rinterface from rpy2.rinterface import StrSexpVector from rpy2.robjects.packages_utils import (get_packagepath, _libpaths, _packages) from collections import OrderedDict tmp = rinterface.baseenv['R.Version']() tmp_major = int(tmp[tmp.do_slot('names').index('major')][0]) tmp_minor = float(tmp[tmp.do_slot('names').index('minor')][0]) readRDS = rinterface.baseenv['readRDS'] del tmp del tmp_major del tmp_minor _eval = rinterface.baseenv['eval'] NON_UNIQUE_TAGS = set((r'\alias', r'\keyword', r'\section')) def quiet_require(name: str, lib_loc: typing.Optional[str] = None) -> bool: """ Load an R package /quietly/ (suppressing messages to the console). """ if lib_loc is None: lib_loc = "NULL" expr_txt = ('suppressPackageStartupMessages(base::require(%s, lib.loc=%s))' % (name, lib_loc)) expr = rinterface.parse(expr_txt) ok = _eval(expr) return ok quiet_require('tools') _get_namespace = rinterface.baseenv['getNamespace'] _lazyload_dbfetch = rinterface.baseenv['lazyLoadDBfetch'] tools_ns = _get_namespace(StrSexpVector(('tools',))) _Rd_db = tools_ns['Rd_db'] _Rd_deparse = tools_ns['.Rd_deparse'] __rd_meta = os.path.join('Meta', 'Rd.rds') __package_meta = os.path.join('Meta', 'package.rds') p_newarg = re.compile(r'^\s*([a-zA-Z\._][a-zA-Z0-9\._]*?)\s*:\s*(.+?)\s*$') p_desc = re.compile(r'^\s+([^\s]+.*?)\s*$') def _Rd2txt(section_doc): tempfilename = rinterface.baseenv['tempfile']()[0] filecon = rinterface.baseenv['file'](tempfilename, open='w') try: tools_ns['Rd2txt']( section_doc, out=filecon, fragment=True )[0].split('\n') rinterface.baseenv['flush'](filecon) rinterface.baseenv['close'](filecon) with open(tempfilename) as fh: section_rows = fh.readlines() finally: os.unlink(tempfilename) return section_rows def create_metaRd_db(dbcon) -> None: """ Create an database to store R help pages. dbcon: database connection (assumed to be SQLite - may or may not work with other databases) """ dbcon.execute(''' CREATE TABLE package ( name TEXT UNIQUE, title TEXT, version TEXT, description TEXT ); ''') dbcon.execute(''' CREATE TABLE rd_meta ( id INTEGER, file TEXT UNIQUE, name TEXT, type TEXT, title TEXT, encoding TEXT, package_rowid INTEGER ); ''') dbcon.execute(''' CREATE INDEX type_idx ON rd_meta (type); ''') dbcon.execute(''' CREATE TABLE rd_alias_meta ( rd_meta_rowid INTEGER, alias TEXT ); ''') dbcon.execute(''' CREATE INDEX alias_idx ON rd_alias_meta (alias); ''') dbcon.commit() def populate_metaRd_db(package_name: str, dbcon, package_path: typing.Optional[str] = None) -> None: """ Populate a database with the meta-information associated with an R package: version, description, title, and aliases (those are what the R help system is organised around). - package_name: a string - dbcon: a database connection - package_path: path the R package installation (default: None) """ if package_path is None: package_path = get_packagepath(package_name) rpath = StrSexpVector((os.path.join(package_path, __package_meta),)) rds = readRDS(rpath) desc = rds[rds.do_slot('names').index('DESCRIPTION')] db_res = dbcon.execute('insert into package values (?,?,?,?)', (desc[desc.do_slot('names').index('Package')], desc[desc.do_slot('names').index('Title')], desc[desc.do_slot('names').index('Version')], desc[desc.do_slot('names').index('Description')], )) package_rowid = db_res.lastrowid rpath = StrSexpVector((os.path.join(package_path, __rd_meta),)) rds = readRDS(rpath) FILE_I = rds.do_slot("names").index('File') NAME_I = rds.do_slot("names").index('Name') TYPE_I = rds.do_slot("names").index('Type') TITLE_I = rds.do_slot("names").index('Title') ENCODING_I = rds.do_slot("names").index('Encoding') ALIAS_I = rds.do_slot("names").index('Aliases') for row_i in range(len(rds[0])): db_res = dbcon.execute('insert into rd_meta values (?,?,?,?,?,?,?)', (row_i, rds[FILE_I][row_i], rds[NAME_I][row_i], rds[TYPE_I][row_i], rds[TITLE_I][row_i], rds[ENCODING_I][row_i], package_rowid)) rd_rowid = db_res.lastrowid for alias in rds[ALIAS_I][row_i]: dbcon.execute('insert into rd_alias_meta values (?,?)', (rd_rowid, alias)) Item = namedtuple('Item', 'name value') class Page(object): """ An R documentation page. The original R structure is a nested sequence of components, corresponding to the latex-like .Rd file An help page is divided into sections, the names for the sections are the keys for the dict attribute 'sections', and a given section can be extracted with the square-bracket operator. In R, the S3 class 'Rd' is the closest entity to this class. """ def __init__(self, struct_rdb: rinterface.ListSexpVector, _type: str = ''): sections = OrderedDict() for elt_i in range(len(struct_rdb)): elt = rinterface.baseenv['['](struct_rdb, elt_i+1) rd_tag = elt[0].do_slot("Rd_tag")[0] if rd_tag in sections and rd_tag not in NON_UNIQUE_TAGS: warnings.warn('Section of the R doc duplicated: %s' % rd_tag) sections[rd_tag] = elt self._sections = sections self._type = _type def _section_get(self): return self._sections sections = property(_section_get, None, None, 'Sections in the in help page, as a dict.') def __getitem__(self, item): """ Get a section """ return self.sections[item] def arguments(self) -> typing.List[Item]: """ Get the arguments and descriptions as a list of Item objects. """ section_doc = self._sections.get(r'\arguments') res: typing.List[Item] = list() if section_doc is None: return res else: arg_name = None arg_desc = None section_rows = _Rd2txt(section_doc) if len(section_rows) < 3: return res for row in section_rows[2:]: if arg_name is None: m = p_newarg.match(row) if m: arg_name = m.groups()[0] arg_desc = [m.groups()[1]] else: if p_desc.match(row): arg_desc.append(row.strip()) else: res.append( Item(arg_name, arg_desc) ) arg_name = None arg_desc = None if arg_name is not None: res.append( Item(arg_name, arg_desc) ) return res def _get_section(self, section: str): section_doc = self._sections.get(section, None) if section_doc is None: res = '' else: res = _Rd2txt(section_doc) return res def description(self) -> str: """ Get the description of the entry """ return self._get_section(r'\description') def details(self) -> str: """ Get the section Details for the documentation entry.""" return self._get_section(r'\details') def title(self) -> str: """ Get the title """ return self._get_section(r'\title') def value(self) -> str: """ Get the value returned """ return self._get_section(r'\value') def seealso(self) -> str: """ Get the other documentation entries recommended """ return self._get_section(r'\seealso') def usage(self) -> str: """ Get the usage for the object """ return self._get_section(r'\usage') def items(self): """ iterator through the sections names and content in the documentation Page. """ return self.sections.items() def iteritems(self): """ iterator through the sections names and content in the documentation Page. (deprecated, use items()) """ warnings.warn('Use the method items().', DeprecationWarning) return self.sections.items() def to_docstring( self, section_names: typing.Optional[typing.Tuple[str, ...]] = None ) -> str: """ section_names: list of section names to consider. If None all sections are used. Returns a string that can be used as a Python docstring. """ s = [] if section_names is None: section_names = self.sections.keys() def walk(tree): if not isinstance(tree, str): for elt in tree: walk(elt) else: s.append(tree) s.append(' ') for name in section_names: name_str = name[1:] if name.startswith('\\') else name s.append(name_str) s.append(os.linesep) s.append('-' * len(name_str)) s.append(os.linesep) s.append(os.linesep) walk(self.sections[name]) s.append(os.linesep) s.append(os.linesep) return ''.join(s) class Package(object): """ The R documentation page (aka help) for a package. """ __package_path = None __package_name = None __aliases_info = 'aliases.rds' __hsearch_meta = os.path.join('Meta', 'hsearch.rds') __paths_info = 'paths.rds' __anindex_info = 'AnIndex' def __package_name_get(self): return self.__package_name name = property(__package_name_get, None, None, 'Name of the package as known by R') def __init__(self, package_name: str, package_path: typing.Optional[str] = None): self.__package_name = package_name if package_path is None: package_path = get_packagepath(package_name) self.__package_path = package_path rd_meta_dbcon = sqlite3.connect(':memory:') create_metaRd_db(rd_meta_dbcon) populate_metaRd_db(package_name, rd_meta_dbcon, package_path=package_path) self._dbcon = rd_meta_dbcon path = os.path.join(package_path, 'help', package_name + '.rdx') self._rdx = readRDS(StrSexpVector((path, ))) def fetch(self, alias: str) -> Page: """ Fetch the documentation page associated with a given alias. For S4 classes, the class name is *often* suffixed with '-class'. For example, the alias to the documentation for the class AnnotatedDataFrame in the package Biobase is 'AnnotatedDataFrame-class'. """ c = self._dbcon.execute( 'SELECT rd_meta_rowid, alias FROM rd_alias_meta WHERE alias=?', (alias, ) ) res_alias = c.fetchall() if len(res_alias) == 0: raise HelpNotFoundError( 'No help could be fetched', topic=alias, package=self.__package_name ) c = self._dbcon.execute( 'SELECT file, name, type FROM rd_meta WHERE rowid=?', (res_alias[0][0], ) ) # since the selection is on a verified rowid we are sure to # exactly get one row res_all = c.fetchall() rkey = StrSexpVector((res_all[0][0][:-3], )) _type = res_all[0][2] rpath = StrSexpVector( (os.path.join(self.package_path, 'help', f'{self.__package_name}.rdb'),) ) rdx_variables = ( self._rdx[self._rdx.do_slot('names').index('variables')] ) _eval = rinterface.baseenv['eval'] devnull_func = rinterface.parse('function(x) {}') devnull_func = _eval(devnull_func) res = _lazyload_dbfetch( rdx_variables[rdx_variables.do_slot('names').index(rkey[0])], rpath, self._rdx[self._rdx.do_slot('names').index("compressed")], devnull_func ) p_res = Page(res, _type=_type) return p_res package_path = property(lambda self: str(self.__package_path), None, None, 'Path to the installed R package') def __repr__(self): r = 'R package %s %s' % (self.__package_name, super(Package, self).__repr__()) return r class HelpNotFoundError(KeyError): """ Exception raised when an help topic cannot be found. """ def __init__(self, msg, topic=None, package=None): super(HelpNotFoundError, self).__init__(msg) self.topic = topic self.package = package def pages(topic): """ Get help pages corresponding to a given topic. """ res = list() for path in _libpaths(): for name in _packages(**{'all.available': True, 'lib.loc': StrSexpVector((path,))}): # TODO: what if the same package is installed # at different locations ? pack = Package(name) try: page = pack.fetch(topic) res.append(page) except HelpNotFoundError: pass return tuple(res) def docstring( package: Package, alias: str, sections: typing.Tuple[str, ...] = (r'\usage', r'\arguments')) -> str: """Fetch the R documentation for an alias in a package.""" if not isinstance(package, Package): package = Package(package) page = package.fetch(alias) return page.to_docstring(sections) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/language.py0000644000175100001770000000552114543120705016756 0ustar00runnerdocker""" Utilities for manipulating or evaluating the R language. """ # TODO: uuid is used to create a temporary symbol. This is a ugly # hack to work around an issue with with calling R functions # (language objects seem to be evaluated when doing so from rpy2 # but not when doing it from R). import typing import uuid from rpy2.robjects import conversion from rpy2.robjects.robject import RObject import rpy2.rinterface as ri _reval = ri.baseenv['eval'] _parse = ri.parse def eval( x: typing.Union[str, ri.ExprSexpVector], envir: typing.Union[ None, ri.SexpEnvironment, ri.NULLType, ri.ListSexpVector, ri.PairlistSexpVector, int, ri._MissingArgType] = None, enclos: typing.Union[ None, ri.ListSexpVector, ri.PairlistSexpVector, ri.NULLType, ri._MissingArgType] = None ) -> RObject: """ Evaluate R code. If the input object is an R expression it evaluates it directly, if it is a string it parses it before evaluating it. By default the evaluation is performed in R's global environment but a specific environment can be specified. This function is a wrapper around rpy2.rinterface.evalr and rpy2.rinterface.evalr_expr. Args: - x (str or ExprSexpVector): a string to be parsed as R code and evaluated, or an R expression to be evaluated. - envir: An optional R environment where the R code will be evaluated. - enclos: An optional enclosure. Returns: The R objects resulting from the evaluation.""" if envir is None: envir = ri.get_evaluation_context() if isinstance(x, str): res = ri.evalr(x, envir=envir, enclos=enclos) else: res = ri.evalr_expr(x, envir=envir, enclos=enclos) return conversion.rpy2py(res) LangVector_VT = typing.TypeVar('LangVector_VT', bound='LangVector') class LangVector(RObject, ri.LangSexpVector): """R language object. R language objects are unevaluated constructs using the R language. They can be found in the default values for named arguments, for example: ```r r_function(x, n = ncol(x)) ``` The default value for `n` is then the result of calling the R function `ncol()` on the object `x` passed at the first argument. """ def __repr__(self): # TODO: hack to work around an issue with calling R functions # with rpy2 (language objects seem to be evaluated). Without # the issue, the representation should simply be # ri.baseenv['deparse'](self)[0] tmp_r_var = str(uuid.uuid4()) representation = None try: ri.globalenv[tmp_r_var] = self representation = ri.evalr('deparse(`{}`)'.format(tmp_r_var))[0] finally: del ri.globalenv[tmp_r_var] return 'Rlang( {} )'.format(representation) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8619466 rpy2-3.5.15/rpy2/robjects/lib/0000755000175100001770000000000014543120757015373 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/__init__.py0000644000175100001770000000000014543120705017463 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/dbplyr.py0000644000175100001770000000163014543120705017232 0ustar00runnerdockerfrom rpy2.robjects.packages import (importr, WeakPackage) import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") dbplyr = importr('dbplyr', on_conflict="warn") dbplyr = WeakPackage(dbplyr._env, dbplyr.__rname__, translation=dbplyr._translation, exported_names=dbplyr._exported_names, on_conflict="warn", version=dbplyr.__version__, symbol_r2python=dbplyr._symbol_r2python, symbol_resolve=dbplyr._symbol_resolve) TARGET_VERSION = '2.2.' if not dbplyr.__version__.startswith(TARGET_VERSION): warnings.warn( 'This was designed against dbplyr versions starting with %s' ' but you have %s' % (TARGET_VERSION, dbplyr.__version__)) sql = dbplyr.sql ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/dplyr.py0000644000175100001770000003422714543120705017100 0ustar00runnerdockerfrom rpy2 import robjects from rpy2.robjects.packages import (importr, WeakPackage) import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") dplyr_ = importr('dplyr', on_conflict="warn") lazyeval = importr('lazyeval', on_conflict="warn") rlang = importr('rlang', on_conflict='warn', robject_translations={'.env': '__env'}) dplyr = WeakPackage(dplyr_._env, dplyr_.__rname__, translation=dplyr_._translation, exported_names=dplyr_._exported_names, on_conflict="warn", version=dplyr_.__version__, symbol_r2python=dplyr_._symbol_r2python, symbol_resolve=dplyr_._symbol_resolve) TARGET_VERSION = '1.0' if ( (dplyr.__version__ is None) or (not dplyr.__version__.startswith(TARGET_VERSION)) ): warnings.warn( 'This was designed against dplyr versions starting with %s' ' but you have %s' % (TARGET_VERSION, dplyr.__version__)) def _wrap(rfunc, cls): def func(dataf, *args, **kwargs): res = rfunc(dataf, *args, **kwargs) if cls is None: return type(dataf)(res) else: return cls(res) return func def _wrap2(rfunc, cls, env=robjects.globalenv): def func(dataf_a, dataf_b, *args, **kwargs): res = rfunc(dataf_a, dataf_b, *args, **kwargs) if cls is None: return type(dataf_a)(res) else: return cls(res) return func def _make_pipe(rfunc, cls, env=robjects.globalenv): """ :param rfunc: An R function. :param cls: The class to use wrap the result of `rfunc`. :param env: A R environment. :rtype: A function.""" def inner(obj, *args, **kwargs): res = rfunc(obj, *args, **kwargs) return cls(res) return inner def _make_pipe2(rfunc, cls, env=robjects.globalenv): """ :param rfunc: An R function. :param cls: The class to use wrap the result of `rfunc`. :param env: A R environment. :rtype: A function.""" def inner(obj_a, obj_b, *args, **kwargs): res = rfunc(obj_a, obj_b, *args, **kwargs) return cls(res) return inner def _fix_call(func): def inner(self, *args, **kwargs): if len(args) > 0: if len(kwargs) > 0: res = func(self, *args, **kwargs) else: res = func(self, *args) else: if len(kwargs) > 0: res = func(self, **kwargs) else: res = func(self) return res return inner def result_as(func, constructor=None): """Wrap the result using the constructor. This decorator is intended for methods. The first arguments passed to func must be 'self'. Args: constructor: a constructor to call using the result of func(). If None, the type of self will be used.""" def inner(self, *args, **kwargs): if constructor is None: wrap = type(self) else: wrap = constructor res = func(self, *args, **kwargs) return wrap(res) return inner class DataFrame(robjects.DataFrame): """DataFrame object extending the object of the same name in `rpy2.robjects.vectors` with functionalities defined in the R package `dplyr`.""" @property def is_grouped_df(self) -> bool: """Is the DataFrame in a grouped state""" return dplyr.is_grouped_df(self)[0] def __rshift__(self, other): return other(self) @result_as def anti_join(self, *args, **kwargs): """Call the R function `dplyr::anti_join()`.""" res = dplyr.anti_join(self, *args, **kwargs) return res @result_as def arrange(self, *args, _by_group=False): """Call the R function `dplyr::arrange()`.""" res = dplyr.arrange(self, *args, **{'.by_group': _by_group}) return res def copy_to(self, destination, name, **kwargs): """ - destination: database - name: table name in the destination database """ res = dplyr.copy_to(destination, self, name=name) return guess_wrap_type(res)(res) @result_as def collapse(self, *args, **kwargs): """ Call the function `collapse` in the R package `dplyr`. """ return dplyr.collapse(self, *args, **kwargs) @result_as def collect(self, *args, **kwargs): """Call the function `collect` in the R package `dplyr`.""" return dplyr.collect(self, *args, **kwargs) @result_as def count(self, *args, **kwargs): """Call the function `count` in the R package `dplyr`.""" return dplyr.count(self, *args, **kwargs) @result_as def distinct(self, *args, _keep_all=False): """Call the R function `dplyr::distinct()`.""" res = dplyr.distinct(self, *args, **{'.keep_all': _keep_all}) return res @result_as def filter(self, *args, _preserve=False): """Call the R function `dplyr::filter()`.""" res = dplyr.filter(self, *args, **{'.preserve': _preserve}) return res def group_by(self, *args, _add=False, _drop=robjects.rl('group_by_drop_default(.data)')): """Call the R function `dplyr::group_by()`.""" res = dplyr.group_by(self, *args, **{'.add': _add, '.drop': _drop}) return GroupedDataFrame(res) @result_as def inner_join(self, *args, **kwargs): """Call the R function `dplyr::inner_join()`.""" res = dplyr.inner_join(self, *args, **kwargs) return res @result_as def left_join(self, *args, **kwargs): """Call the R function `dplyr::left_join()`.""" res = dplyr.left_join(self, *args, **kwargs) return res @result_as def full_join(self, *args, **kwargs): """Call the R function `dplyr::full_join()`.""" res = dplyr.full_join(self, *args, **kwargs) return res @result_as def mutate(self, **kwargs): """Call the R function `dplyr::mutate()`.""" res = dplyr.mutate(self, **kwargs) return res @result_as def mutate_all(self, *args, **kwargs): """Call the R function `dplyr::mutate_all()`.""" res = dplyr.mutate_all(self, *args, **kwargs) return res @result_as def mutate_at(self, *args, **kwargs): """Call the R function `dplyr::mutate_at()`.""" res = dplyr.mutate_at(self, *args, **kwargs) return res @result_as def mutate_if(self, *args, **kwargs): """Call the R function `dplyr::mutate_if()`.""" res = dplyr.mutate_if(self, *args, **kwargs) return res @result_as def right_join(self, *args, **kwargs): """Call the R function `dplyr::right_join()`.""" res = dplyr.right_join(self, *args, **kwargs) return res @result_as def sample_frac(self, *args): """Call the R function `dplyr::sample_frac()`.""" res = dplyr.sample_frac(self, *args) return res @result_as def sample_n(self, *args): """Call the R function `dplyr::sample_n()`.""" res = dplyr.sample_n(self, *args) return res @result_as def select(self, *args): """Call the R function `dplyr::select()`.""" res = dplyr.select(self, *args) return res @result_as def semi_join(self, *args, **kwargs): """Call the R function `dplyr::semi_join()`.""" res = dplyr.semi_join(self, *args, **kwargs) return res @result_as def slice(self, *args, **kwargs): """Call the R function `dplyr::slice()`.""" res = dplyr.slice(self, *args, **kwargs) return res @result_as def slice_head(self, *args, **kwargs): """Call the R function `dplyr::slice_head()`.""" res = dplyr.slice_head(self, *args, **kwargs) return res @result_as def slice_min(self, *args, **kwargs): """Call the R function `dplyr::slice_min()`.""" res = dplyr.slice_min(self, *args, **kwargs) return res @result_as def slice_max(self, *args, **kwargs): """Call the R function `dplyr::slice_max()`.""" res = dplyr.slice_max(self, *args, **kwargs) return res @result_as def slice_sample(self, *args, **kwargs): """Call the R function `dplyr::slice_sample()`.""" res = dplyr.slice_sample(self, *args, **kwargs) return res @result_as def slice_tail(self, *args, **kwargs): """Call the R function `dplyr::slice_tail()`.""" res = dplyr.slice_tail(self, *args, **kwargs) return res def summarize(self, *args, **kwargs): """Call the R function `dplyr::summarize()`. This can return a GroupedDataFrame or a DataFrame. """ res = dplyr.summarize(self, *args, **kwargs) return guess_wrap_type(res)(res) summarise = summarize def summarize_all(self, *args, **kwargs): """Call the R function `dplyr::summarize_all()`. This can return a GroupedDataFrame or a DataFrame. """ res = dplyr.summarize_all(self, *args, **kwargs) return guess_wrap_type(res)(res) summarise_all = summarize_all def summarize_at(self, *args, **kwargs): """Call the R function `dplyr::summarize_at()`. This can return a GroupedDataFrame or a DataFrame. """ res = dplyr.summarize_at(self, *args, **kwargs) return guess_wrap_type(res)(res) summarise_at = summarize_at def summarize_if(self, *args, **kwargs): """Call the R function `dplyr::summarize_if()`. This can return a GroupedDataFrame or a DataFrame. """ res = dplyr.summarize_if(self, *args, **kwargs) return guess_wrap_type(res)(res) summarise_if = summarize_if @result_as def tally(self, *args, **kwargs): """Call the R function `dplyr::transmute()`.""" res = dplyr.tally(self, *args, **kwargs) return res @result_as def transmute(self, *args, **kwargs): """Call the R function `dplyr::transmute()`.""" res = dplyr.transmute(self, *args, **kwargs) return res @result_as def transmute_all(self, *args, **kwargs): """Call the R function `dplyr::transmute_all()`.""" res = dplyr.transmute_all(self, *args, **kwargs) return res @result_as def transmute_at(self, *args, **kwargs): """Call the R function `dplyr::transmute_at()`.""" res = dplyr.transmute_at(self, *args, **kwargs) return res @result_as def transmute_if(self, *args, **kwargs): """Call the R function `dplyr::transmute_if()`.""" res = dplyr.transmute_if(self, *args, **kwargs) return res union = _wrap2(dplyr.union_data_frame, None) intersect = _wrap2(dplyr.intersect_data_frame, None) setdiff = _wrap2(dplyr.setdiff_data_frame, None) def guess_wrap_type(x): if dplyr.is_grouped_df(x)[0]: return GroupedDataFrame else: return DataFrame class GroupedDataFrame(DataFrame): """DataFrame grouped by one of several factors.""" def ungroup(self, *args): res = dplyr.ungroup(self, *args) return guess_wrap_type(res)(res) arrange = _make_pipe(dplyr.arrange, DataFrame) count = _make_pipe(dplyr.count, DataFrame) mutate = _make_pipe(dplyr.mutate, DataFrame) transmute = _make_pipe(dplyr.transmute, DataFrame) filter = result_as(dplyr.filter) select = _make_pipe(dplyr.select, DataFrame) group_by = _make_pipe(dplyr.group_by, GroupedDataFrame) summarize = _make_pipe(dplyr.summarize, guess_wrap_type) summarise = summarize distinct = _make_pipe(dplyr.distinct, DataFrame) inner_join = _make_pipe2(dplyr.inner_join, DataFrame) left_join = _make_pipe2(dplyr.left_join, DataFrame) right_join = _make_pipe2(dplyr.right_join, DataFrame) full_join = _make_pipe2(dplyr.full_join, DataFrame) semi_join = _make_pipe2(dplyr.semi_join, DataFrame) anti_join = _make_pipe2(dplyr.anti_join, DataFrame) union = _make_pipe2(dplyr.union_data_frame, DataFrame) intersect = _make_pipe2(dplyr.intersect_data_frame, DataFrame) setdiff = _make_pipe2(dplyr.setdiff_data_frame, DataFrame) sample_n = _make_pipe(dplyr.sample_n, DataFrame) sample_frac = _make_pipe(dplyr.sample_frac, DataFrame) slice = _make_pipe(dplyr.slice_, DataFrame) tally = _make_pipe(dplyr.tally, DataFrame) mutate_if = _make_pipe(dplyr.mutate_if, DataFrame) mutate_at = _make_pipe(dplyr.mutate_at, DataFrame) mutate_all = _make_pipe(dplyr.mutate_all, DataFrame) summarize_all = _make_pipe(dplyr.summarize_all, DataFrame) summarise_all = _make_pipe(dplyr.summarise_all, DataFrame) summarize_at = _make_pipe(dplyr.summarize_at, DataFrame) summarise_at = _make_pipe(dplyr.summarize_at, DataFrame) summarize_if = _make_pipe(dplyr.summarize_if, DataFrame) summarise_if = _make_pipe(dplyr.summarize_if, DataFrame) transmute_all = _make_pipe(dplyr.transmute_all, DataFrame) transmute_if = _make_pipe(dplyr.transmute_if, DataFrame) transmute_at = _make_pipe(dplyr.transmute_at, DataFrame) # Functions for databases class DataSource(robjects.ListVector): """ Source of data tables (e.g., in a schema in a relational database). """ @property def tablenames(self): """ Call the R function dplyr::src_tbls() and return a vector of table names.""" return tuple(dplyr.src_tbls(self)) def get_table(self, name): """ "Get" table from a source (R dplyr's function `tbl`). """ return DataFrame(tbl(self, name)) src = dplyr.src src_tbls = dplyr.src_tbls src_local = dplyr.src_local src_df = dplyr.src_df def src_mysql(*args, **kwargs): res = dplyr.src_mysql(*args, **kwargs) return DataSource(res) def src_postgres(*args, **kwargs): res = dplyr.src_postgres(*args, **kwargs) return DataSource(res) def src_sqlite(*args, **kwargs): res = dplyr.src_sqlite(*args, **kwargs) return DataSource(res) def copy_to(*args, **kwargs): res = dplyr.copy_to(*args, **kwargs) return DataFrame(res) # Generic to create a data table tbl = dplyr.tbl # TODO: wrapper classes for the output of the following two function. explain = dplyr.explain show_query = dplyr.show_query ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/ggplot2.py0000644000175100001770000006724714543120705017334 0ustar00runnerdocker""" Wrapper for the popular R library ggplot2. With rpy2, the most convenient general way to import packages is to use `importr()`, for example with ggplot2:: from rpy2.robjects.packages import importr ggplot2 = importr('ggplot2') This module is an supplementary layer in which an attempt at modelling the package as it was really developed as Python package is made. Behind the scene, `importr()` is used and can be accessed with: from robjects.robjects.lib import ggplot2 ggplot2.ggplot2 GGplot2 is designed using a prototype-based approach to Object-Oriented Programming, and this module is trying to define class-hierachies so the nature of a given instance can be identified more easily. The main families of classes are: - GGplot - Aes and AesString - Layer - Stat A downside of the approach is that the code in the module is 'hand-made'. In hindsight, this can be tedious to maintain and document but this is a good showcase of "manual" mapping of R code into Python classes. The codebase in R for ggplot2 has evolved since this was initially written, and many functions have signature-defined parameters (used to be ellipsis about everywhere). Metaprogramming will hopefully be added to shorten the Python code in the module, and provide a more dynamic mapping. """ import rpy2.robjects as robjects import rpy2.robjects.constants import rpy2.robjects.conversion as conversion from rpy2.robjects.packages import importr, WeakPackage from rpy2.robjects import rl import warnings NULL = robjects.NULL rlang = importr('rlang', on_conflict='warn', robject_translations={'.env': '__env'}) lazyeval = importr('lazyeval', on_conflict='warn') base = importr('base', on_conflict='warn') ggplot2 = importr('ggplot2', on_conflict='warn') ggplot2 = WeakPackage(ggplot2._env, ggplot2.__rname__, translation=ggplot2._translation, exported_names=ggplot2._exported_names, on_conflict="warn", version=ggplot2.__version__, symbol_r2python=ggplot2._symbol_r2python, symbol_resolve=ggplot2._symbol_resolve) TARGET_VERSION = '3.3.' if not ggplot2.__version__.startswith(TARGET_VERSION): warnings.warn( 'This was designed against ggplot2 versions starting with %s but you ' 'have %s' % (TARGET_VERSION, ggplot2.__version__)) ggplot2_env = robjects.baseenv['as.environment']('package:ggplot2') StrVector = robjects.StrVector def as_symbol(x): return rlang.sym(x) _AES_RLANG = rl('ggplot2::aes()') class GGPlot(robjects.vectors.ListVector): """ A Grammar of Graphics Plot. GGPlot instances can be added to one an other in order to construct the final plot (the method `__add__()` is implemented). """ _constructor = ggplot2._env['ggplot'] _rprint = ggplot2._env['print.ggplot'] _add = ggplot2._env['%+%'] @classmethod def new(cls, data, mapping=_AES_RLANG, **kwargs): """ Constructor for the class GGplot. """ data = conversion.get_conversion().py2rpy(data) res = cls(cls._constructor(data, mapping=mapping, **kwargs)) return res def plot(self, vp=rpy2.robjects.constants.NULL): self._rprint(self, vp=vp) def __add__(self, obj): res = self._add(self, obj) if 'gg' not in res.rclass: raise ValueError( "Added object did not give a ggplot result " "(get class '%s')." % res.rclass[0]) return self.__class__(res) def save(self, filename, **kwargs): """ Save the plot ( calls R's `ggplot2::ggsave()` ) """ ggplot2.ggsave(filename=filename, plot=self, **kwargs) ggplot = GGPlot.new class Aes(robjects.ListVector): """ Aesthetics mapping, using expressions rather than string (this is the most common form when using the package in R - it might be easier to use AesString when working in Python using rpy2 - see class AesString in this Python module). """ _constructor = ggplot2_env['aes'] @classmethod def new(cls, *args, **kwargs): res = cls(cls._constructor(*args, **kwargs)) return res aes = Aes.new class Vars(robjects.ListVector): """ Aesthetics mapping, using expressions rather than string (this is the most common form when using the package in R - it might be easier to use AesString when working in Python using rpy2 - see class AesString in this Python module). """ _constructor = ggplot2_env['vars'] @classmethod def new(cls, *args): """Constructor for the class Vars.""" new_args = list() for a in args: new_args.append( rlang.parse_quo( a, env=robjects.baseenv['sys.frame']() ) ) res = cls(cls._constructor(*new_args)) return res vars = Vars.new class AesString(robjects.ListVector): """ Aesthetics mapping, using strings rather than expressions (the later being most common form when using the package in R - see class Aes in this Python module). This associates dimensions in the data sets (columns in the DataFrame), possibly with a transformation applied on-the-fly (e.g., "log(value)", or "cost / benefits") to graphical "dimensions" in a chosen graphical representation (e.g., x-axis, color of points, size, etc...). Not all graphical representations have all dimensions. Refer to the documentation of ggplot2, online tutorials, or Hadley's book for more details. """ _constructor = ggplot2_env['aes_string'] @classmethod def new(cls, **kwargs): """Constructor for the class AesString.""" res = cls(cls._constructor(**kwargs)) return res aes_string = AesString.new class Layer(robjects.RObject): """ At this level, aesthetics mapping can (should ?) be specified (see Aes and AesString). """ _constructor = ggplot2_env['layer'] @classmethod def new(cls, *args, **kwargs): """ Constructor for the class Layer. """ for i, elt in enumerate(args): args[i] = conversion.py2rpy(elt) for k in kwargs: kwargs[k] = conversion.py2rpy(kwargs[k]) res = cls(cls.contructor)(*args, **kwargs) return res layer = Layer.new class GBaseObject(robjects.Environment): @classmethod def new(cls, *args, **kwargs): args_list = list(args) res = cls(cls._constructor(*args_list, **kwargs)) return res class Stat(GBaseObject): """ A "statistical" processing of the data in order to make a plot, or a plot element. This is an abstract class; material classes are called Stat* (e.g., StatAbline, StatBin, etc...). """ pass class StatBin(Stat): """ Bin data. """ _constructor = ggplot2_env['stat_bin'] stat_bin = StatBin.new class StatBin2D(Stat): """ 2D binning of data into squares/rectangles. """ _constructor = ggplot2_env['stat_bin_2d'] stat_bin2d = StatBin2D.new stat_bin_2d = StatBin2D.new class StatBinhex(Stat): """ 2D binning of data into hexagons. """ _constructor = ggplot2_env['stat_bin_hex'] stat_binhex = StatBinhex.new stat_bin_hex = StatBinhex.new class StatBoxplot(Stat): """ Components of box and whisker plot. """ _constructor = ggplot2_env['stat_boxplot'] stat_boxplot = StatBoxplot.new class StatContour(Stat): """ Contours of 3D data. """ _constructor = ggplot2_env['stat_contour'] stat_contour = StatContour.new class StatDensity(Stat): """ 1D density estimate """ _constructor = ggplot2_env['stat_density'] stat_density = StatDensity.new class StatDensity2D(Stat): """ 2D density estimate """ _constructor = ggplot2_env['stat_density_2d'] stat_density2d = StatDensity2D.new stat_density_2d = StatDensity2D.new class StatFunction(Stat): """ Superimpose a function """ _constructor = ggplot2_env['stat_function'] stat_function = StatFunction.new class StatIdentity(Stat): """ Identity function """ _constructor = ggplot2_env['stat_identity'] stat_identity = StatIdentity.new class StatQQ(Stat): """ Calculation for quantile-quantile plot. """ _constructor = ggplot2_env['stat_qq'] stat_qq = StatQQ.new class StatQuantile(Stat): """ Continuous quantiles """ _constructor = ggplot2_env['stat_quantile'] stat_quantile = StatQuantile.new class StatSmooth(Stat): """ Smoothing function """ _constructor = ggplot2_env['stat_smooth'] stat_smooth = StatSmooth.new class StatSpoke(Stat): """ Convert angle and radius to xend and yend """ _constructor = ggplot2_env['stat_spoke'] stat_spoke = StatSpoke.new class StatSum(Stat): """ Sum unique values. Useful when overplotting. """ _constructor = ggplot2_env['stat_sum'] stat_sum = StatSum.new class StatSummary(Stat): """ Summarize values for y at every unique value for x""" _constructor = ggplot2_env['stat_summary'] stat_summary = StatSummary.new class StatSummary2D(Stat): """ Summarize values for y at every unique value for x""" _constructor = ggplot2_env['stat_summary_2d'] stat_summary2d = StatSummary2D.new stat_summary_2d = StatSummary2D.new class StatUnique(Stat): """ Remove duplicates. """ _constructor = ggplot2_env['stat_unique'] stat_unique = StatUnique.new class Coord(GBaseObject): """ Coordinates """ pass class CoordFixed(Coord): """ Cartesian coordinates with fixed relationship (that is fixed ratio between units in axes). CoordEquel seems to be identical to this class.""" _constructor = ggplot2_env['coord_fixed'] coord_fixed = CoordFixed.new class CoordCartesian(Coord): """ Cartesian coordinates. """ _constructor = ggplot2_env['coord_cartesian'] coord_cartesian = CoordCartesian.new class CoordEqual(Coord): """ This class seems to be identical to CoordFixed. """ _constructor = ggplot2_env['coord_equal'] coord_equal = CoordEqual.new class CoordFlip(Coord): """ Flip horizontal and vertical coordinates. """ _constructor = ggplot2_env['coord_flip'] coord_flip = CoordFlip.new class CoordMap(Coord): """ Map projections. """ _constructor = ggplot2_env['coord_map'] coord_map = CoordMap.new class CoordPolar(Coord): """ Polar coordinates. """ _constructor = ggplot2_env['coord_polar'] coord_polar = CoordPolar.new class CoordTrans(Coord): """ Apply transformations (functions) to a cartesian coordinate system. """ _constructor = ggplot2_env['coord_trans'] coord_trans = CoordTrans.new class Facet(GBaseObject): """ Panels """ pass class FacetGrid(Facet): """ Panels in a grid. """ _constructor = ggplot2_env['facet_grid'] facet_grid = FacetGrid.new class FacetWrap(Facet): """ Sequence of panels in a 2D layout """ _constructor = ggplot2_env['facet_wrap'] facet_wrap = FacetWrap.new class Geom(GBaseObject): pass class GeomAbline(Geom): _constructor = ggplot2_env['geom_abline'] geom_abline = GeomAbline.new class GeomArea(Geom): _constructor = ggplot2_env['geom_area'] geom_area = GeomArea.new class GeomBar(Geom): _constructor = ggplot2_env['geom_bar'] geom_bar = GeomBar.new class GeomCol(Geom): _constructor = ggplot2_env['geom_col'] geom_col = GeomCol.new class GeomBin2D(Geom): _constructor = ggplot2_env['geom_bin2d'] geom_bin2d = GeomBin2D.new class GeomBlank(Geom): _constructor = ggplot2_env['geom_blank'] geom_blank = GeomBlank.new class GeomBoxplot(Geom): _constructor = ggplot2_env['geom_boxplot'] geom_boxplot = GeomBoxplot.new class GeomContour(Geom): _constructor = ggplot2_env['geom_contour'] geom_contour = GeomContour.new class GeomCrossBar(Geom): _constructor = ggplot2_env['geom_crossbar'] geom_crossbar = GeomCrossBar.new class GeomDensity(Geom): _constructor = ggplot2_env['geom_density'] geom_density = GeomDensity.new class GeomDensity2D(Geom): _constructor = ggplot2_env['geom_density_2d'] geom_density2d = GeomDensity2D.new geom_density_2d = GeomDensity2D.new class GeomDotplot(Geom): _constructor = ggplot2_env['geom_dotplot'] geom_dotplot = GeomDotplot.new class GeomErrorBar(Geom): _constructor = ggplot2_env['geom_errorbar'] geom_errorbar = GeomErrorBar.new class GeomErrorBarH(Geom): _constructor = ggplot2_env['geom_errorbarh'] geom_errorbarh = GeomErrorBarH.new class GeomFreqPoly(Geom): _constructor = ggplot2_env['geom_freqpoly'] geom_freqpoly = GeomFreqPoly.new class GeomHex(Geom): _constructor = ggplot2_env['geom_hex'] geom_hex = GeomHex.new class GeomHistogram(Geom): _constructor = ggplot2_env['geom_histogram'] geom_histogram = GeomHistogram.new class GeomHLine(Geom): _constructor = ggplot2_env['geom_hline'] geom_hline = GeomHLine.new class GeomJitter(Geom): _constructor = ggplot2_env['geom_jitter'] geom_jitter = GeomJitter.new class GeomLine(Geom): _constructor = ggplot2_env['geom_line'] geom_line = GeomLine.new class GeomLineRange(Geom): _constructor = ggplot2_env['geom_linerange'] geom_linerange = GeomLineRange.new class GeomPath(Geom): _constructor = ggplot2_env['geom_path'] geom_path = GeomPath.new class GeomPoint(Geom): _constructor = ggplot2_env['geom_point'] geom_point = GeomPoint.new class GeomPointRange(Geom): _constructor = ggplot2_env['geom_pointrange'] geom_pointrange = GeomPointRange.new class GeomPolygon(Geom): _constructor = ggplot2_env['geom_polygon'] geom_polygon = GeomPolygon.new class GeomQuantile(Geom): _constructor = ggplot2_env['geom_quantile'] geom_quantile = GeomQuantile.new class GeomRaster(Geom): _constructor = ggplot2_env['geom_raster'] geom_raster = GeomRaster.new class GeomRect(Geom): _constructor = ggplot2_env['geom_rect'] geom_rect = GeomRect.new class GeomRibbon(Geom): _constructor = ggplot2_env['geom_ribbon'] geom_ribbon = GeomRibbon.new class GeomRug(Geom): _constructor = ggplot2_env['geom_rug'] geom_rug = GeomRug.new class GeomSegment(Geom): _constructor = ggplot2_env['geom_segment'] geom_segment = GeomSegment.new class GeomSmooth(Geom): _constructor = ggplot2_env['geom_smooth'] geom_smooth = GeomSmooth.new class GeomSpoke(Geom): """ Convert angle and radius to xend and yend """ _constructor = ggplot2_env['geom_spoke'] geom_spoke = GeomSpoke.new class GeomStep(Geom): _constructor = ggplot2_env['geom_step'] geom_step = GeomStep.new class GeomText(Geom): _constructor = ggplot2_env['geom_text'] geom_text = GeomText.new class GeomTile(Geom): _constructor = ggplot2_env['geom_tile'] geom_tile = GeomTile.new class GeomVLine(Geom): _constructor = ggplot2_env['geom_vline'] geom_vline = GeomVLine.new class Position(GBaseObject): pass class PositionDodge(Position): _constructor = ggplot2_env['position_dodge'] position_dodge = PositionDodge.new class PositionFill(Position): _constructor = ggplot2_env['position_fill'] position_fill = PositionFill.new class PositionJitter(Position): _constructor = ggplot2_env['position_jitter'] position_jitter = PositionJitter.new class PositionStack(Position): _constructor = ggplot2_env['position_stack'] position_stack = PositionStack.new class Scale(GBaseObject): pass class ScaleAlpha(Scale): _constructor = ggplot2_env['scale_alpha'] scale_alpha = ScaleAlpha.new class ScaleColour(Scale): pass class ScaleDiscrete(Scale): pass class ScaleLinetype(Scale): _constructor = ggplot2_env['scale_linetype'] scale_linetype = ScaleLinetype.new class ScaleShape(Scale): _constructor = ggplot2_env['scale_shape'] scale_shape = ScaleShape.new class ScaleRadius(Scale): _constructor = ggplot2_env['scale_radius'] scale_radius = ScaleRadius.new class ScaleSize(Scale): _constructor = ggplot2_env['scale_size'] scale_size = ScaleSize.new class ScaleSizeArea(Scale): _constructor = ggplot2_env['scale_size_area'] scale_size_area = ScaleSizeArea.new class ScaleShapeDiscrete(Scale): _constructor = ggplot2_env['scale_shape_discrete'] scale_shape_discrete = ScaleShapeDiscrete.new class ScaleFill(Scale): pass class ScaleX(Scale): pass class ScaleY(Scale): pass # class Limits(Scale): # _constructor = ggplot2_env['limits'] # limits = Limits.new class XLim(Scale): _constructor = ggplot2_env['xlim'] xlim = XLim.new class YLim(Scale): _constructor = ggplot2_env['ylim'] ylim = YLim.new class ScaleXContinuous(ScaleX): _constructor = ggplot2_env['scale_x_continuous'] scale_x_continuous = ScaleXContinuous.new class ScaleYContinuous(ScaleY): _constructor = ggplot2_env['scale_y_continuous'] scale_y_continuous = ScaleYContinuous.new class ScaleXDiscrete(ScaleX): _constructor = ggplot2_env['scale_x_discrete'] scale_x_discrete = ScaleXDiscrete.new class ScaleYDiscrete(ScaleY): _constructor = ggplot2_env['scale_y_discrete'] scale_y_discrete = ScaleYDiscrete.new class ScaleXDate(ScaleX): _constructor = ggplot2_env['scale_x_date'] scale_x_date = ScaleXDate.new class ScaleYDate(ScaleY): _constructor = ggplot2_env['scale_y_date'] scale_y_date = ScaleYDate.new class ScaleXDatetime(ScaleX): _constructor = ggplot2_env['scale_x_datetime'] scale_x_datetime = ScaleXDatetime.new class ScaleYDatetime(ScaleY): _constructor = ggplot2_env['scale_y_datetime'] scale_y_datetime = ScaleYDatetime.new class ScaleXLog10(ScaleX): _constructor = ggplot2_env['scale_x_log10'] scale_x_log10 = ScaleXLog10.new class ScaleYLog10(ScaleY): _constructor = ggplot2_env['scale_y_log10'] scale_y_log10 = ScaleYLog10.new class ScaleXReverse(ScaleX): _constructor = ggplot2_env['scale_x_reverse'] scale_x_reverse = ScaleXReverse.new class ScaleYReverse(ScaleY): _constructor = ggplot2_env['scale_y_reverse'] scale_y_reverse = ScaleYReverse.new class ScaleXSqrt(ScaleX): _constructor = ggplot2_env['scale_x_sqrt'] scale_x_sqrt = ScaleXSqrt.new class ScaleYSqrt(ScaleY): _constructor = ggplot2_env['scale_y_sqrt'] scale_y_sqrt = ScaleYSqrt.new class ScaleColourBrewer(ScaleColour): _constructor = ggplot2_env['scale_colour_brewer'] scale_colour_brewer = ScaleColourBrewer.new scale_color_brewer = scale_colour_brewer class ScaleColourContinuous(ScaleColour): _constructor = ggplot2_env['scale_colour_continuous'] scale_colour_continuous = ScaleColourContinuous.new scale_color_continuous = scale_colour_continuous class ScaleColourDiscrete(ScaleColour): _constructor = ggplot2_env['scale_colour_discrete'] scale_colour_discrete = ScaleColourDiscrete.new scale_color_discrete = scale_colour_discrete class ScaleColourGradient(ScaleColour): _constructor = ggplot2_env['scale_colour_gradient'] scale_colour_gradient = ScaleColourGradient.new scale_color_gradient = scale_colour_gradient class ScaleColourGradient2(ScaleColour): _constructor = ggplot2_env['scale_colour_gradient2'] scale_colour_gradient2 = ScaleColourGradient2.new scale_color_gradient2 = scale_colour_gradient2 class ScaleColourGradientN(ScaleColour): _constructor = ggplot2_env['scale_colour_gradientn'] scale_colour_gradientn = ScaleColourGradientN.new scale_color_gradientn = scale_colour_gradientn class ScaleColourGrey(ScaleColour): _constructor = ggplot2_env['scale_colour_grey'] scale_colour_grey = ScaleColourGrey.new scale_color_grey = scale_colour_grey class ScaleColourHue(ScaleColour): _constructor = ggplot2_env['scale_colour_hue'] scale_colour_hue = ScaleColourHue.new scale_color_hue = scale_colour_hue class ScaleColourIdentity(ScaleColour): _constructor = ggplot2_env['scale_colour_identity'] scale_colour_identity = ScaleColourIdentity.new scale_color_identity = scale_colour_identity class ScaleColourManual(ScaleColour): _constructor = ggplot2_env['scale_colour_manual'] scale_colour_manual = ScaleColourManual.new scale_color_manual = scale_colour_manual class ScaleFillBrewer(ScaleFill): _constructor = ggplot2_env['scale_fill_brewer'] scale_fill_brewer = ScaleFillBrewer.new class ScaleFillContinuous(ScaleFill): _constructor = ggplot2_env['scale_fill_continuous'] scale_fill_continuous = ScaleFillContinuous.new class ScaleFillDiscrete(ScaleFill): _constructor = ggplot2_env['scale_fill_discrete'] scale_fill_discrete = ScaleFillDiscrete.new class ScaleFillGradient(ScaleFill): _constructor = ggplot2_env['scale_fill_gradient'] scale_fill_gradient = ScaleFillGradient.new class ScaleFillGradient2(ScaleFill): _constructor = ggplot2_env['scale_fill_gradient2'] scale_fill_gradient2 = ScaleFillGradient2.new class ScaleFillGradientN(ScaleFill): _constructor = ggplot2_env['scale_fill_gradientn'] scale_fill_gradientn = ScaleFillGradientN.new class ScaleFillGrey(ScaleFill): _constructor = ggplot2_env['scale_fill_grey'] scale_fill_grey = ScaleFillGrey.new class ScaleFillHue(ScaleFill): _constructor = ggplot2_env['scale_fill_hue'] scale_fill_hue = ScaleFillHue.new class ScaleFillIdentity(ScaleFill): _constructor = ggplot2_env['scale_fill_identity'] scale_fill_identity = ScaleFillIdentity.new class ScaleFillManual(ScaleFill): _constructor = ggplot2_env['scale_fill_manual'] scale_fill_manual = ScaleFillManual.new class ScaleLinetypeContinuous(ScaleLinetype): _constructor = ggplot2_env['scale_linetype_continuous'] scale_linetype_continuous = ScaleLinetypeContinuous.new class ScaleLinetypeDiscrete(ScaleLinetype): _constructor = ggplot2_env['scale_linetype_discrete'] scale_linetype_discrete = ScaleLinetypeDiscrete.new class ScaleLinetypeManual(ScaleLinetype): _constructor = ggplot2_env['scale_linetype_manual'] scale_linetype_manual = ScaleLinetypeManual.new class ScaleShapeManual(ScaleShape): _constructor = ggplot2_env['scale_shape_manual'] scale_shape_manual = ScaleShapeManual.new guides = ggplot2.guides guide_colorbar = ggplot2.guide_colorbar guide_colourbar = ggplot2.guide_colourbar guide_legend = ggplot2.guide_legend class Options(robjects.ListVector): def __repr__(self): s = '' % (type(self), id(self)) return s class Element(Options): pass class ElementText(Element): _constructor = ggplot2.element_text @classmethod def new(cls, family='', face='plain', colour='black', size=10, hjust=0.5, vjust=0.5, angle=0, lineheight=1.1, color=NULL): res = cls(cls._constructor(family=family, face=face, colour=colour, size=size, hjust=hjust, vjust=vjust, angle=angle, lineheight=lineheight)) return res element_text = ElementText.new class ElementLine(Element): _constructor = ggplot2.element_line @classmethod def new(cls, colour=NULL, size=NULL, linetype=NULL, lineend=NULL, color=NULL, arrow=NULL, inherit_blank=False): res = cls( cls._constructor(colour=colour, size=size, linetype=linetype, lineend=lineend, color=color, arrow=arrow, inherit_blank=inherit_blank) ) return res element_line = ElementLine.new class ElementRect(Element): _constructor = ggplot2.element_rect @classmethod def new(cls, fill=NULL, colour=NULL, size=NULL, linetype=NULL, color=NULL): res = cls(cls._constructor(fill=fill, colour=colour, size=size, linetype=linetype, color=color)) return res element_rect = ElementRect.new class Labs(Options): _constructor = ggplot2.labs @classmethod def new(cls, **kwargs): res = cls(cls._constructor(**kwargs)) return res labs = Labs.new class Theme(Options): pass class ElementBlank(Theme): _constructor = ggplot2.element_blank @classmethod def new(cls): res = cls(cls._constructor()) return res element_blank = ElementBlank.new theme_get = ggplot2.theme_get class ThemeGrey(Theme): _constructor = ggplot2.theme_grey @classmethod def new(cls, base_size=12): res = cls(cls._constructor(base_size=base_size)) return res theme_grey = ThemeGrey.new class ThemeClassic(Theme): _constructor = ggplot2.theme_classic @classmethod def new(cls, base_size=12, base_family=''): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_classic = ThemeClassic.new class ThemeDark(Theme): _constructor = ggplot2.theme_dark @classmethod def new(cls, base_size=12, base_family=''): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_dark = ThemeDark.new class ThemeLight(Theme): _constructor = ggplot2.theme_light @classmethod def new(cls, base_size=12, base_family=''): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_light = ThemeLight.new class ThemeBW(Theme): _constructor = ggplot2.theme_bw @classmethod def new(cls, base_size=12): res = cls(cls._constructor(base_size=base_size)) return res theme_bw = ThemeBW.new class ThemeGray(Theme): _constructor = ggplot2.theme_gray @classmethod def new(cls, base_size=12): res = cls(cls._constructor(base_size=base_size)) return res theme_gray = ThemeGray.new theme_grey = theme_gray class ThemeMinimal(Theme): _constructor = ggplot2.theme_minimal @classmethod def new(cls, base_size=12, base_family=''): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_minimal = ThemeMinimal.new class ThemeLinedraw(Theme): _constructor = ggplot2.theme_linedraw @classmethod def new(cls, base_size=12, base_family=""): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_linedraw = ThemeLinedraw.new class ThemeVoid(Theme): _constructor = ggplot2.theme_void @classmethod def new(cls, base_size=12, base_family=''): res = cls(cls._constructor(base_size=base_size, base_family=base_family)) return res theme_void = ThemeVoid.new theme_set = ggplot2.theme_set theme_update = ggplot2.theme_update gplot = ggplot2.qplot map_data = ggplot2.map_data theme = ggplot2_env['theme'] ggtitle = ggplot2.ggtitle xlab = ggplot2.xlab ylab = ggplot2.ylab guide_axis = ggplot2.guide_axis guide_bins = ggplot2.guide_bins guide_colorbar = ggplot2.guide_colorbar guide_colourbar = guide_colorbar guide_colorsteps = ggplot2.guide_colorsteps guide_coloursteps = guide_colorsteps guide_geom = ggplot2.guide_geom guide_legend = ggplot2.guide_legend guide_merge = ggplot2.guide_merge guide_none = ggplot2.guide_none guide_train = ggplot2.guide_train guide_transform = ggplot2.guide_transform def dict2rvec(d): """Convert a python dict[str, str] into an R named vector. """ r_x = StrVector(list(d.values())) r_x.names = list(d.keys()) return r_x as_labeller = ggplot2.as_labeller # TODO: This side effect can create a issues that # are hard to troubleshoot (import order). original_rpy2py = conversion.get_conversion().rpy2py def ggplot2_conversion(robj): pyobj = original_rpy2py(robj) try: rcls = pyobj.rclass except AttributeError: # conversion lead to something that is no # longer an R object return pyobj if (rcls is not None) and (rcls[0] == 'gg'): pyobj = GGPlot(pyobj) return pyobj conversion.rpy2py = ggplot2_conversion ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/grdevices.py0000644000175100001770000000444414543120705017717 0ustar00runnerdocker""" Mapping of the R library "grDevices" for graphical devices """ import io import os import tempfile from contextlib import contextmanager from rpy2.robjects.packages import importr, WeakPackage grdevices = importr('grDevices') grdevices = WeakPackage(grdevices._env, grdevices.__rname__, translation=grdevices._translation, exported_names=grdevices._exported_names, on_conflict="warn", version=grdevices.__version__, symbol_r2python=grdevices._symbol_r2python, symbol_resolve=grdevices._symbol_resolve) # non-interactive file devices png = grdevices.png jpeg = grdevices.jpeg bmp = grdevices.bmp tiff = grdevices.tiff svg = grdevices.svg postscript = grdevices.postscript pdf = grdevices.pdf cairo_pdf = grdevices.cairo_pdf # X11 = grdevices.X11 # OS X quartz = grdevices.quartz # devices dev_list = grdevices.dev_list dev_cur = grdevices.dev_cur dev_next = grdevices.dev_next dev_prev = grdevices.dev_prev dev_set = grdevices.dev_set dev_off = grdevices.dev_off @contextmanager def render_to_file(device, *device_args, **device_kwargs): """ Context manager that returns a R figures in a file object. :param device: an R "device" function. This function is expected to take a filename as its first argument. """ # TODO: better function signature to check that a file name is passed. current = dev_cur()[0] try: device(*device_args, **device_kwargs) yield None finally: if current != dev_cur()[0]: dev_off() @contextmanager def render_to_bytesio(device, *device_args, **device_kwargs): """ Context manager that returns a R figures in a :class:`io.BytesIO` object. :param device: an R "device" function. This function is expected to take a filename as its first argument. """ fn = tempfile.mktemp() b = io.BytesIO() current = dev_cur()[0] try: device(fn, *device_args, **device_kwargs) yield b finally: if current != dev_cur()[0]: dev_off() if os.path.exists(fn): with open(fn, 'rb') as fh: b.write(fh.read()) os.unlink(fn) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/grid.py0000644000175100001770000001671714543120705016677 0ustar00runnerdocker""" Mapping of the R library "grid" for graphics. The R library provides a low-level coordinate system and graphic primitives to built visualizations. """ import warnings import rpy2.rinterface as rinterface import rpy2.robjects as robjects import rpy2.robjects.conversion as conversion from rpy2.robjects.packages import importr, WeakPackage NULL = robjects.NULL grid = importr('grid') grid = WeakPackage(grid._env, grid.__rname__, translation=grid._translation, exported_names=grid._exported_names, on_conflict="warn", version=grid.__version__, symbol_r2python=grid._symbol_r2python, symbol_resolve=grid._symbol_resolve) original_converter = None converter = conversion.Converter('original grid conversion') py2rpy = converter.py2rpy rpy2py = converter.rpy2py grid_env = robjects.baseenv['as.environment']('package:grid') layout = grid.grid_layout newpage = grid.grid_newpage grill = grid.grid_grill edit = grid.grid_edit get = grid.grid_get remove = grid.grid_remove add = grid.grid_add xaxis = grid.grid_xaxis yaxis = grid.grid_yaxis class BaseGrid(robjects.RObject): @classmethod def r(cls, *args, **kwargs): """ Constructor (as it looks like on the R side).""" res = cls._r_constructor(*args, **kwargs) return cls(res) class Unit(BaseGrid): """ Vector of unit values (as in R's grid package) """ _r_constructor = grid_env['unit'] unit = Unit.r class Gpar(BaseGrid): """ Graphical parameters """ _r_constructor = grid_env['gpar'] _get_gpar = grid_env['get.gpar'] def get(self, names=None): return self._get_gpar(names) gpar = Gpar.r class Grob(BaseGrid): """ Graphical object """ _r_constructor = grid_env['grob'] _draw = grid_env['grid.draw'] def draw(self, recording=True): """ Draw a graphical object (calling the R function grid::grid.raw())""" self._draw(self, recording=recording) grob = Grob.r class Rect(Grob): _r_constructor = grid_env['rectGrob'] rect = Rect.r class Lines(Grob): _r_constructor = grid_env['linesGrob'] lines = Lines.r class Circle(Grob): _r_constructor = grid_env['circleGrob'] circle = Circle.r class Points(Grob): _r_constructor = grid_env['pointsGrob'] points = Points.r class Text(Grob): _r_constructor = grid_env['textGrob'] text = Text.r class GTree(Grob): """ gTree """ _gtree = grid_env['gTree'] _grobtree = grid_env['grobTree'] @classmethod def gtree(cls, **kwargs): """ Constructor (uses the R function grid::gTree())""" res = cls._gtree(**kwargs) return cls(res) @classmethod def grobtree(cls, **kwargs): """ Constructor (uses the R function grid::grobTree())""" res = cls._grobtree(**kwargs) return cls(res) class Axis(GTree): pass class XAxis(Axis): _xaxis = xaxis _xaxisgrob = grid.xaxisGrob @classmethod def xaxis(cls, **kwargs): """ Constructor (uses the R function grid::xaxis())""" res = cls._xaxis(**kwargs) return cls(res) @classmethod def xaxisgrob(cls, **kwargs): """ Constructor (uses the R function grid::xaxisgrob())""" res = cls._xaxisgrob(**kwargs) return cls(res) class YAxis(Axis): _yaxis = yaxis _yaxisgrob = grid.yaxisGrob @classmethod def yaxis(cls, **kwargs): """ Constructor (uses the R function grid::yaxis())""" res = cls._yaxis(**kwargs) return cls(res) @classmethod def yaxisgrob(cls, **kwargs): """ Constructor (uses the R function grid::yaxisgrob())""" res = cls._yaxisgrob(**kwargs) return cls(res) class Viewport(robjects.RObject): """ Drawing context. Viewports can be thought of as nodes in a scene graph. """ _pushviewport = grid_env['pushViewport'] _popviewport = grid_env['popViewport'] _current = grid_env['current.viewport'] _plotviewport = grid_env['plotViewport'] _downviewport = grid_env['downViewport'] _seek = grid_env['seekViewport'] _upviewport = grid_env['upViewport'] _viewport = grid_env['viewport'] def push(self, recording=True): self._pushviewport(self, recording=recording) @classmethod def pop(cls, n): """ Pop n viewports from the stack. """ cls._popviewport(n) @classmethod def current(cls): """ Return the current viewport in the stack. """ cls._current() @classmethod def default(cls, **kwargs): cls._plotviewport(**kwargs) @classmethod def down(cls, name, strict=False, recording=True): """ Return the number of Viewports it went down """ cls._downviewport(name, strict=strict, recording=recording) @classmethod def seek(cls, name, recording=True): """ Seek and return a Viewport given its name """ cls._seek(name, recording=recording) @classmethod def up(cls, n, recording=True): """ Go up n viewports """ cls._downviewport(n, recording=recording) @classmethod def viewport(cls, **kwargs): """ Constructor: create a Viewport """ res = cls._viewport(**kwargs) res = cls(res) return res viewport = Viewport.viewport _grid_dict = { 'gpar': Gpar, 'grob': Grob, 'gTree': GTree, 'unit': Unit, 'xaxis': XAxis, 'yaxis': YAxis, 'viewport': Viewport } original_py2rpy = None original_rpy2py = None # TODO: remove after v3.5.0 def grid_rpy2py(robj): warnings.warn('This function is deprecated.', category=DeprecationWarning) pyobj = original_rpy2py(robj) if not isinstance(pyobj, robjects.RS4): rcls = pyobj.rclass if rcls is NULL: rcls = (None, ) try: cls = _grid_dict[rcls[0]] pyobj = cls(pyobj) except KeyError: pass return pyobj converter._rpy2py_nc_map.update( { rinterface.FloatSexpVector: conversion.NameClassMap( lambda obj: robjects.default_converter.rpy2py(obj), {'unit': Unit.r} ), rinterface.ListSexpVector: conversion.NameClassMap( lambda obj: robjects.default_converter.rpy2py(obj), {'gpar': Gpar.r, 'grob': Grob.r} ), } ) def activate(): warnings.warn('The global conversion available with activate() ' 'is deprecated and will be removed in the next ' 'major release. Use a local converter.', category=DeprecationWarning) global original_converter # If module is already activated, there is nothing to do. if original_converter is not None: return original_converter = conversion.converter new_converter = conversion.Converter('grid conversion', template=original_converter) for k, v in py2rpy.registry.items(): if k is object: continue new_converter.py2rpy.register(k, v) for k, v in rpy2py.registry.items(): if k is object: continue new_converter.rpy2py.register(k, v) conversion.set_conversion(new_converter) def deactivate(): global original_converter # If module has never been activated or already deactivated, # there is nothing to do if original_converter is None: return conversion.set_conversion(original_converter) original_converter = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/lib/tidyr.py0000644000175100001770000000233514543120705017074 0ustar00runnerdockerfrom rpy2.robjects.packages import (importr, WeakPackage) from rpy2.robjects.lib import dplyr import warnings with warnings.catch_warnings(): warnings.simplefilter("ignore") tidyr = importr('tidyr', on_conflict="warn") TARGET_VERSION = '1.2.' if not tidyr.__version__.startswith(TARGET_VERSION): warnings.warn( 'This was designed against tidyr versions starting with %s ' 'but you have %s' % (TARGET_VERSION, tidyr.__version__)) tidyr = WeakPackage(tidyr._env, tidyr.__rname__, translation=tidyr._translation, exported_names=tidyr._exported_names, on_conflict="warn", version=tidyr.__version__, symbol_r2python=tidyr._symbol_r2python, symbol_resolve=tidyr._symbol_resolve) def _wrap(rfunc): def func(dataf, *args, **kwargs): cls = type(dataf) res = rfunc(dataf, *args, **kwargs) return cls(res) return func class DataFrame(dplyr.DataFrame): gather = _wrap(tidyr.gather) spread = _wrap(tidyr.spread) summarize = dplyr._wrap(dplyr.summarize, None) DataFrame.summarise = DataFrame.summarize ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/methods.py0000644000175100001770000002561414543120705016643 0ustar00runnerdockerimport abc from types import SimpleNamespace from rpy2.robjects.robject import RObjectMixin import rpy2.rinterface as rinterface from rpy2.rinterface import StrSexpVector from rpy2.robjects import help as rhelp from rpy2.robjects import conversion _get_exported_value = rinterface.baseenv['::'] getmethod = _get_exported_value('methods', 'getMethod') require = rinterface.baseenv.find('require') require(StrSexpVector(('methods', )), quietly=rinterface.BoolSexpVector((True, ))) class RS4(RObjectMixin, rinterface.SexpS4): """ Python representation of an R instance of class 'S4'. """ def slotnames(self): """ Return the 'slots' defined for this object """ return methods_env['slotNames'](self) def do_slot(self, name): return (conversion.get_conversion() .rpy2py(super(RS4, self).do_slot(name))) def extends(self): """Return the R classes this extends. This calls the R function methods::extends().""" return methods_env['extends'](self.rclass) @staticmethod def isclass(name): """ Return whether the given name is a defined class. """ name = conversion.get_conversion().py2rpy(name) return methods_env['isClass'](name)[0] def validobject(self, test=False, complete=False): """ Return whether the instance is 'valid' for its class. """ cv = conversion.get_conversion() test = cv.py2rpy(test) complete = cv.py2rpy(complete) return methods_env['validObject'](self, test=test, complete=complete)[0] class ClassRepresentation(RS4): """ Definition of an R S4 class """ slots = property(lambda x: [y[0] for y in x.do_slot('slots')], None, None, "Slots (attributes) for the class") basenames = property(lambda x: [y[0] for y in x.do_slot('contains')], None, None, "Parent classes") contains = basenames isabstract = property(lambda x: x.do_slot('virtual')[0], None, None, "Is the class an abstract class ?") virtual = isabstract packagename = property(lambda x: x.do_slot('package')[0], None, None, "R package in which the class is defined") package = packagename classname = property(lambda x: x.do_slot('className')[0], None, None, "Name of the R class") def getclassdef(cls_name: str, packagename=rinterface.MissingArg): cls_def = methods_env['getClassDef']( StrSexpVector((cls_name,)), package=StrSexpVector((packagename, )) ) cls_def = ClassRepresentation(cls_def) cls_def.__rname__ = cls_name return cls_def class RS4_Type(abc.ABCMeta): def __new__(mcs, name, bases, cls_dict): try: cls_rname = cls_dict['__rname__'] except KeyError: cls_rname = name try: accessors = cls_dict['__accessors__'] except KeyError: accessors = [] for rname, where, \ python_name, as_property, \ docstring in accessors: if where is None: where = rinterface.globalenv else: where = StrSexpVector(('package:%s' % where, )) if python_name is None: python_name = rname signature = StrSexpVector((cls_rname, )) r_meth = getmethod(StrSexpVector((rname, )), signature=signature, where=where) r_meth = conversion.get_conversion().rpy2py(r_meth) if as_property: cls_dict[python_name] = property(r_meth, None, None, doc=docstring) else: cls_dict[python_name] = lambda self: r_meth(self) return type.__new__(mcs, name, bases, cls_dict) # playground to experiment with more metaclass-level automation class RS4Auto_Type(abc.ABCMeta): """ This type (metaclass) takes an R S4 class and create a Python class out of it, copying the R documention page into the Python docstring. A class with this metaclass has the following optional attributes: __rname__, __rpackagename__, __attr__translation, __meth_translation__. """ def __new__(mcs, name, bases, cls_dict): try: cls_rname = cls_dict['__rname__'] except KeyError: cls_rname = name try: cls_rpackagename = cls_dict['__rpackagename__'] except KeyError: cls_rpackagename = None try: cls_attr_translation = cls_dict['__attr_translation__'] except KeyError: cls_attr_translation = {} try: cls_meth_translation = cls_dict['__meth_translation__'] except KeyError: cls_meth_translation = {} cls_def = getclassdef(cls_rname, cls_rpackagename) # documentation / help if cls_rpackagename is None: cls_dict['__doc__'] = "Undocumented class from the R workspace." else: pack_help = rhelp.Package(cls_rpackagename) page_help = None try: # R's classes are sometimes documented with a prefix 'class.' page_help = pack_help.fetch('%s-class' % cls_def.__rname__) except rhelp.HelpNotFoundError: pass if page_help is None: try: page_help = pack_help.fetch(cls_def.__rname__) except rhelp.HelpNotFoundError: pass if page_help is None: cls_dict['__doc__'] = ('Unable to fetch R documentation ' 'for the class') else: cls_dict['__doc__'] = ''.join(page_help.to_docstring()) for slt_name in cls_def.slots: # TODO: sanity check on the slot name try: slt_name = cls_attr_translation[slt_name] except KeyError: # no translation: abort pass # TODO: isolate the slot documentation and have it here cls_dict[slt_name] = property(lambda self: self.do_slot(slt_name), None, None, None) # Now tackle the methods all_generics = methods_env['getGenerics']() findmethods = methods_env['findMethods'] # does not seem elegant, but there is probably nothing else to do # than loop across all generics r_cls_rname = StrSexpVector((cls_rname, )) for funcname in all_generics: all_methods = findmethods(StrSexpVector((funcname, )), classes=r_cls_rname) # skip if no methods (issue #301). R's findMethods() result # does not have an attribute "names" if of length zero. if len(all_methods) == 0: continue # all_methods contains all method/signature pairs # having the class we are considering somewhere in the signature # (the R/S4 systems allows multiple dispatch) for name, meth in zip(all_methods.do_slot("names"), all_methods): # R/S4 is storing each method/signature as a string, # with the argument type separated by the character '#' # We will re-use that name for the Python name # (no multiple dispatch in python, the method name # will not be enough), replacing the '#'s with '__'s. signature = name.split("#") meth_name = '__'.join(signature) # function names ending with '<-' indicate that the function # is a setter of some sort. We reflect that by adding a 'set_' # prefix to the Python name (and of course remove the # suffix '<-'). if funcname.endswith('<-'): meth_name = 'set_%s__%s' % (funcname[:-2], meth_name) else: meth_name = '%s__%s' % (funcname, meth_name) # finally replace remaining '.'s in the Python name with '_'s meth_name = meth_name.replace('.', '_') # TODO: sanity check on the function name try: meth_name = cls_meth_translation[meth_name] except KeyError: # no translation: abort pass # TODO: isolate the slot documentation and have it here if meth_name in cls_dict: raise Exception("Duplicated attribute/method name.") cls_dict[meth_name] = meth return abc.ABCMeta.__new__(mcs, name, bases, cls_dict) def set_accessors(cls, cls_name, where, acs): # set accessors (to be abandonned for the metaclass above ?) if where is None: where = rinterface.globalenv else: where = "package:" + str(where) where = StrSexpVector((where, )) for r_name, python_name, as_property, docstring in acs: if python_name is None: python_name = r_name r_meth = getmethod(StrSexpVector((r_name, )), signature=StrSexpVector((cls_name, )), where=where) r_meth = conversion.get_conversion().rpy2py(r_meth) if as_property: setattr(cls, python_name, property(r_meth, None, None)) else: setattr(cls, python_name, lambda self: r_meth(self)) def get_classnames(packname): res = methods_env['getClasses']( where=StrSexpVector(("package:%s" % packname, )) ) return tuple(res) # Namespace to store the definition of RS4 classes rs4classes = SimpleNamespace() def _getclass(rclsname): if hasattr(rs4classes, rclsname): rcls = getattr(rs4classes, rclsname) else: # dynamically create a class rcls = type(rclsname, (RS4, ), dict()) setattr(rs4classes, rclsname, rcls) return rcls def rs4instance_factory(robj): """ Return an RS4 objects (R objects in the 'S4' class system) as a Python object of type inheriting from `robjects.methods.RS4`. The types are located in the namespace `robjects.methods.rs4classes`, and a dummy type is dynamically created whenever necessary. """ clslist = None if len(robj.rclass) > 1: raise ValueError( 'Currently unable to handle more than one class per object' ) for rclsname in robj.rclass: rcls = _getclass(rclsname) return rcls(robj) if clslist is None: return robj methods_env = rinterface.baseenv.find('as.environment')( StrSexpVector(('package:methods', )) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/numpy2ri.py0000644000175100001770000002074314543120705016763 0ustar00runnerdockerimport rpy2.robjects as ro import rpy2.robjects.conversion as conversion import rpy2.rinterface as rinterface import rpy2.rlike.container as rlc from rpy2.rinterface import (Sexp, StrSexpVector, ByteSexpVector, RTYPES) import numpy # type: ignore import warnings # TODO: move this to rinterface. RINT_SIZE = 32 original_converter = None # The possible kind codes are listed at # http://numpy.scipy.org/array_interface.shtml _kinds = { # "t" -> not really supported by numpy 'b': ro.vectors.BoolVector, 'i': ro.vectors.IntVector, # "u" -> special-cased below 'f': ro.vectors.FloatVector, 'c': ro.vectors.ComplexVector, # "O" -> special-cased below 'S': ro.vectors.ByteVector, 'U': ro.vectors.StrVector, # "V" -> special-cased below # TODO: datetime64 ? # "datetime64": } _vectortypes = set( ( # TODO: CHARSXP is an fact a scalar. RTYPES.CHARSXP, RTYPES.LGLSXP, RTYPES.INTSXP, RTYPES.REALSXP, RTYPES.CPLXSXP, RTYPES.STRSXP ) ) converter = conversion.Converter('original numpy conversion') py2rpy = converter.py2rpy rpy2py = converter.rpy2py def numpy_O_py2rpy(o): if all(isinstance(x, str) for x in o): res = StrSexpVector(o) elif all(isinstance(x, bytes) for x in o): res = ByteSexpVector(o) else: res = conversion.get_conversion().py2rpy(list(o)) return res def _numpyarray_to_r(a, func): # "F" means "use column-major order" vec = func(numpy.ravel(a, order='F')) # TODO: no dimnames ? # TODO: optimize what is below needed/possible ? # (other ways to create R arrays ?) dim = ro.vectors.IntVector(a.shape) res = rinterface.baseenv['array'](vec, dim=dim) return res def unsignednumpyint_to_rint(intarray): """Convert a numpy array of unsigned integers to an R array.""" if intarray.itemsize >= (RINT_SIZE / 8): raise ValueError( 'Cannot convert numpy array of {numpy_type!s} ' '(R integers are signed {RINT_SIZE}-bit integers).' .format(numpy_type=intarray.dtype.type, RINT_SIZE=RINT_SIZE) ) else: res = _numpyarray_to_r(intarray, _kinds['i']) return res @py2rpy.register(numpy.ndarray) def numpy2rpy(o): """ Augmented conversion function, converting numpy arrays into rpy2.rinterface-level R structures. """ if not o.dtype.isnative: raise ValueError('Cannot pass numpy arrays with non-native ' 'byte orders at the moment.') # Most types map onto R arrays: if o.dtype.kind in _kinds: res = _numpyarray_to_r(o, _kinds[o.dtype.kind]) # R does not support unsigned types: elif o.dtype.kind == 'u': res = unsignednumpyint_to_rint(o) # Array-of-PyObject is treated like a Python list: elif o.dtype.kind == 'O': res = numpy_O_py2rpy(o) # Record arrays map onto R data frames: elif o.dtype.kind == 'V': if o.dtype.names is None: raise ValueError('Nothing can be done for this numpy array ' 'type "%s" at the moment.' % (o.dtype,)) df_args = [] cv = conversion.get_conversion() for field_name in o.dtype.names: df_args.append((field_name, cv.py2rpy(o[field_name]))) res = ro.baseenv["data.frame"].rcall(tuple(df_args)) # It should be impossible to get here: else: raise ValueError('Unknown numpy array type "%s".' % str(o.dtype)) return res @py2rpy.register(numpy.integer) def npint_py2rpy(obj): return rinterface.IntSexpVector([obj, ]) @py2rpy.register(numpy.floating) def npfloat_py2rpy(obj): return rinterface.FloatSexpVector([obj, ]) @py2rpy.register(object) def nonnumpy2rpy(obj): # allow array-like objects to also function with this module. if not isinstance(obj, numpy.ndarray) and hasattr(obj, '__array__'): obj = obj.__array__() return ro.default_converter.py2rpy(obj) elif original_converter is None: # This means that the conversion module was not "activated". # For now, go with the default_converter. # TODO: the conversion system needs an overhaul badly. return ro.default_converter.py2rpy(obj) else: # The conversion module was "activated" return original_converter.py2rpy(obj) @py2rpy.register(rlc.OrdDict) def orddict_py2rpy(obj): rlist = ro.vectors.ListVector.from_length(len(obj)) rlist.names = ro.vectors.StrVector(tuple(obj.keys())) with conversion.get_conversion().context() as cv: # TODO: OrdDict.values() is broken. Use .items() for now. for i, (k, v) in enumerate(obj.items()): rlist[i] = cv.py2rpy(v) return rlist # TODO: delete ? # @py2ro.register(numpy.ndarray) # def numpy2ro(obj): # res = numpy2ri(obj) # return ro.vectors.rtypeof2rotype[res.typeof](res) def _factor_to_numpy_string_array(obj): levels = obj.do_slot('levels') res = numpy.array( tuple( None if x is rinterface.NA_Character else levels[x-1] for x in obj ) ) return res def rpy2py_data_frame(obj): # TODO: R "factor" vectors will not convert well by default # (will become integers), so we build a temporary list o2 # with the factors as strings. This fix might good to have # as a default. o2 = list() # An added complication is that the conversion defined # in this module will make __getitem__ at the robjects # level return numpy arrays with conversion.get_conversion().context() as cv: for column in rinterface.ListSexpVector(obj): if 'factor' in column.rclass: levels = column.do_slot('levels') column = tuple( None if x is rinterface.NA_Integer else levels[x-1] for x in column ) o2.append(cv.rpy2py(column)) names = obj.do_slot('names') if names == rinterface.NULL: res = numpy.rec.fromarrays(o2) else: res = numpy.rec.fromarrays(o2, names=tuple(names)) return res def rpy2py_list(obj: rinterface.ListSexpVector): # not a data.frame, yet is it still possible to convert it if not isinstance(obj, ro.vectors.ListVector): obj = ro.vectors.ListVector(obj) res = rlc.OrdDict(obj.items()) return res @rpy2py.register(rinterface.FloatSexpVector) def rpy2py_floatvector(obj): return numpy.array(obj) @rpy2py.register(rinterface.CharSexp) def rpy2py_charvector(obj): if obj == rinterface.NA_Character: return None else: return obj @rpy2py.register(rinterface.StrSexpVector) def rpy2py_strvector(obj): res = numpy.array(obj) res[res == rinterface.NA_Character] = None return res @rpy2py.register(Sexp) def rpy2py_sexp(obj): if (obj.typeof in _vectortypes) and (obj.typeof != RTYPES.VECSXP): res = numpy.array(obj) else: res = ro.default_converter.rpy2py(obj) return res converter._rpy2py_nc_map.update( { rinterface.IntSexpVector: conversion.NameClassMap( numpy.array, {'factor': _factor_to_numpy_string_array} ), rinterface.ListSexpVector: conversion.NameClassMap( rpy2py_list, {'data.frame': rpy2py_data_frame} ) } ) def activate(): warnings.warn('The global conversion available with activate() ' 'is deprecated and will be removed in the next major ' 'release. Use a local converter.', category=DeprecationWarning) global original_converter # If module is already activated, there is nothing to do if original_converter is not None: return original_converter = conversion.converter_ctx.get() new_converter = conversion.Converter('numpy conversion', template=original_converter) for k, v in py2rpy.registry.items(): if k is object: continue new_converter.py2rpy.register(k, v) for k, v in rpy2py.registry.items(): if k is object: continue new_converter.rpy2py.register(k, v) conversion.set_conversion(new_converter) def deactivate(): global original_converter # If module has never been activated or already deactivated, # there is nothing to do if original_converter is None: return conversion.set_conversion(original_converter) original_converter = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/packages.py0000644000175100001770000004604514543120705016757 0ustar00runnerdockerimport os import typing import warnings from types import ModuleType from warnings import warn import rpy2.rinterface as rinterface from . import conversion from rpy2.robjects.functions import (SignatureTranslatedFunction, docstring_property, DocumentedSTFunction) from rpy2.robjects import Environment from rpy2.robjects.packages_utils import ( default_symbol_r2python, default_symbol_resolve, _map_symbols, _fix_map_symbols ) import rpy2.robjects.help as rhelp _require = rinterface.baseenv['require'] _library = rinterface.baseenv['library'] _as_env = rinterface.baseenv['as.environment'] _package_has_namespace = rinterface.baseenv['packageHasNamespace'] _system_file = rinterface.baseenv['system.file'] _get_namespace = rinterface.baseenv['getNamespace'] _get_namespace_version = rinterface.baseenv['getNamespaceVersion'] _get_namespace_exports = rinterface.baseenv['getNamespaceExports'] _loaded_namespaces = rinterface.baseenv['loadedNamespaces'] _globalenv = rinterface.globalenv _new_env = rinterface.baseenv["new.env"] StrSexpVector = rinterface.StrSexpVector # Fetching symbols in the namespace "utils" assumes that "utils" is loaded # (currently the case by default in R). _data = rinterface.baseenv['::'](StrSexpVector(('utils', )), StrSexpVector(('data', ))) _reval = rinterface.baseenv['eval'] _options = rinterface.baseenv['options'] def no_warnings(func): """ Decorator to run R functions without warning. """ def run_withoutwarnings(*args, **kwargs): warn_i = _options().do_slot('names').index('warn') oldwarn = _options()[warn_i][0] _options(warn=-1) try: res = func(*args, **kwargs) except Exception as e: # restore the old warn setting before propagating # the exception up _options(warn=oldwarn) raise e _options(warn=oldwarn) return res return run_withoutwarnings @no_warnings def _eval_quiet(expr): return _reval(expr) # FIXME: should this be part of the API for rinterface ? # (may be it is already the case and there is code # duplicaton ?) def reval(string: str, envir: typing.Optional[rinterface.SexpEnvironment] = None): """ Evaluate a string as R code :param string: R code :type string: a :class:`str` :param envir: Optional environment to evaluate the R code. """ p = rinterface.parse(string) res = _reval(p, envir=envir) return res def quiet_require(name: str, lib_loc=None): """ Load an R package /quietly/ (suppressing messages to the console). """ if lib_loc is None: lib_loc = "NULL" else: lib_loc = "\"%s\"" % (lib_loc.replace('"', '\\"')) expr_txt = ("suppressPackageStartupMessages(" "base::require(%s, lib.loc=%s))" % (name, lib_loc)) expr = rinterface.parse(expr_txt) ok = _eval_quiet(expr) return ok class PackageData(object): """ Datasets in an R package. In R datasets can be distributed with a package. Datasets can be: - serialized R objects - R code (that produces the dataset) For a given R packages, datasets are stored separately from the rest of the code and are evaluated/loaded lazily. The lazy aspect has been conserved and the dataset are only loaded or generated when called through the method 'fetch()'. """ _packagename = None _lib_loc = None _datasets = None def __init__(self, packagename, lib_loc=rinterface.NULL): self._packagename = packagename self._lib_loc def _init_setlist(self): _datasets = dict() # 2D array of information about datatsets tmp_m = _data(**{'package': StrSexpVector((self._packagename, )), 'lib.loc': self._lib_loc})[2] nrows, ncols = tmp_m.do_slot('dim') c_i = 2 for r_i in range(nrows): _datasets[tmp_m[r_i + c_i * nrows]] = None # FIXME: check if instance methods are overriden self._datasets = _datasets def names(self): """ Names of the datasets""" if self._datasets is None: self._init_setlist() return self._datasets.keys() def fetch(self, name): """ Fetch the dataset (loads it or evaluates the R associated with it. In R, datasets are loaded into the global environment by default but this function returns an environment that contains the dataset(s). """ if self._datasets is None: self._init_setlist() if name not in self._datasets: raise KeyError('Data set "%s" cannot be found' % name) env = _new_env() _data(StrSexpVector((name, )), **{'package': StrSexpVector((self._packagename, )), 'lib.loc': self._lib_loc, 'envir': env}) return Environment(env) class Package(ModuleType): """ Models an R package (and can do so from an arbitrary environment - with the caution that locked environments should mostly be considered). """ _env = None __rname__ = None _translation = None _rpy2r = None _exported_names = None _symbol_r2python = None __version__: typing.Optional[str] = None __rdata__: typing.Optional[PackageData] = None def __init__(self, env, name, translation={}, exported_names=None, on_conflict='fail', version=None, symbol_r2python=default_symbol_r2python, symbol_resolve=default_symbol_resolve): """ Create a Python module-like object from an R environment, using the specified translation if defined. - env: R environment - name: package name - translation: `dict` with R names as keys and corresponding Python names as values - exported_names: `set` of names/symbols to expose to instance users - on_conflict: 'fail' or 'warn' (default: 'fail') - version: version string for the package - symbol_r2python: function to convert R symbols into Python symbols. The default translate `.` into `_`. - symbol_resolve: function to check the Python symbols obtained from `symbol_r2python`. """ super(Package, self).__init__(name) self._env = env self.__rname__ = name self._translation = translation mynames = tuple(self.__dict__) self._rpy2r = {} if exported_names is None: exported_names = set(self._env.keys()) self._exported_names = exported_names self._symbol_r2python = symbol_r2python self._symbol_resolve = symbol_resolve self.__fill_rpy2r__(on_conflict=on_conflict) self._exported_names = self._exported_names.difference(mynames) self.__version__ = version def __update_dict__(self, on_conflict='fail'): """ Update the __dict__ according to what is in the R environment """ for elt in self._rpy2r: del self.__dict__[elt] self._rpy2r.clear() self.__fill_rpy2r__(on_conflict=on_conflict) def __fill_rpy2r__(self, on_conflict='fail'): """ Fill the attribute _rpy2r. - on_conflict: 'fail' or 'warn' (default: 'fail') """ assert on_conflict in ('fail', 'warn') name = self.__rname__ (symbol_mapping, conflicts, resolutions) = _map_symbols( self._env, translation=self._translation, symbol_r2python=self._symbol_r2python, symbol_resolve=self._symbol_resolve ) msg_prefix = ('Conflict when converting R symbols' ' in the package "%s"' ' to Python symbols: \n-' % self.__rname__) exception = LibraryError _fix_map_symbols(symbol_mapping, conflicts, on_conflict, msg_prefix, exception) symbol_mapping.update(resolutions) reserved_pynames = set(dir(self)) cv = conversion.get_conversion() for rpyname, rnames in symbol_mapping.items(): # last paranoid check if len(rnames) > 1: raise ValueError( 'Only one R name should be associated with %s ' '(and we have %s)' % (rpyname, str(rnames)) ) rname = rnames[0] if rpyname in reserved_pynames: raise LibraryError('The symbol ' + rname + ' in the package "' + name + '"' + ' is conflicting with' + ' a Python object attribute') self._rpy2r[rpyname] = rname if (rpyname != rname) and (rname in self._exported_names): self._exported_names.remove(rname) self._exported_names.add(rpyname) try: riobj = self._env[rname] except rinterface.embedded.RRuntimeError as rre: warn(str(rre)) rpyobj = cv.rpy2py(riobj) if hasattr(rpyobj, '__rname__'): rpyobj.__rname__ = rname # TODO: shouldn't the original R name be also in the __dict__ ? self.__dict__[rpyname] = rpyobj def __repr__(self): s = super(Package, self).__repr__() return 'rpy2.robjects.packages.Package as a %s' % s # alias STF = SignatureTranslatedFunction class SignatureTranslatedPackage(Package): """ R package in which the R functions had their signatures 'translated' (that this the named parameters were made to to conform Python's rules for vaiable names).""" def __fill_rpy2r__(self, on_conflict='fail'): (super(SignatureTranslatedPackage, self) .__fill_rpy2r__(on_conflict=on_conflict)) for name, robj in self.__dict__.items(): if isinstance(robj, rinterface.Sexp) and \ robj.typeof == rinterface.RTYPES.CLOSXP: self.__dict__[name] = STF( self.__dict__[name], on_conflict=on_conflict, symbol_r2python=self._symbol_r2python, symbol_resolve=self._symbol_resolve ) # alias STP = SignatureTranslatedPackage class SignatureTranslatedAnonymousPackage(SignatureTranslatedPackage): def __init__(self, string, name): env = Environment() reval(string, env) super(SignatureTranslatedAnonymousPackage, self).__init__(env, name) # alias STAP = SignatureTranslatedAnonymousPackage class InstalledSTPackage(SignatureTranslatedPackage): @docstring_property(__doc__) def __doc__(self): doc = list(['Python representation of an R package.']) if not self.__rname__: doc.append('') else: try: doc.append(rhelp.docstring(self.__rname__, self.__rname__ + '-package', sections=['\\description'])) except rhelp.HelpNotFoundError: doc.append('[R help was not found]') return os.linesep.join(doc) def __fill_rpy2r__(self, on_conflict='fail'): (super(SignatureTranslatedPackage, self) .__fill_rpy2r__(on_conflict=on_conflict)) for name, robj in self.__dict__.items(): if isinstance(robj, rinterface.Sexp) and \ robj.typeof == rinterface.RTYPES.CLOSXP: self.__dict__[name] = DocumentedSTFunction( self.__dict__[name], packagename=self.__rname__ ) class InstalledPackage(Package): @docstring_property(__doc__) def __doc__(self): doc = list(['Python representation of an R package.', 'R arguments:', '']) if not self.__rname__: doc.append('') else: try: doc.append(rhelp.docstring(self.__rname__, self.__rname__ + '-package', sections=['\\description'])) except rhelp.HelpNotFoundError: doc.append('[R help was not found]') return os.linesep.join(doc) class WeakPackage(Package): """ 'Weak' R package, with which looking for symbols results in a warning (and a None returned) whenever the desired symbol is not found (rather than a traditional `AttributeError`). """ def __getattr__(self, name): res = self.__dict__.get(name) if res is None: warnings.warn( "The symbol '%s' is not in this R namespace/package." % name ) return res class LibraryError(ImportError): """ Error occuring when importing an R library """ pass class PackageNotInstalledError(LibraryError): """ Error occuring because the R package to import is not installed.""" pass class InstalledPackages(object): """ R packages installed. """ def __init__(self, lib_loc=None): libraryiqr = _library(**{'lib.loc': lib_loc}) lib_results_i = libraryiqr.do_slot('names').index('results') self.lib_results = libraryiqr[lib_results_i] self.nrows, self.ncols = self.lib_results.do_slot('dim') self.colnames = self.lib_results.do_slot('dimnames')[1] # column names self.lib_packname_i = self.colnames.index('Package') def isinstalled(self, packagename: str): if not isinstance(packagename, rinterface.StrSexpVector): rinterface.StrSexpVector((packagename, )) else: if len(packagename) > 1: raise ValueError("Only specify one package name at a time.") nrows = self.nrows lib_results, lib_packname_i = self.lib_results, self.lib_packname_i for i in range(0+lib_packname_i*nrows, nrows*(lib_packname_i+1), 1): if lib_results[i] == packagename: return True return False def __iter__(self): """ Iterate through rows, yield tuples at each iteration """ lib_results = self.lib_results nrows, ncols = self.nrows, self.ncols colrg = range(0, ncols) for row_i in range(nrows): yield tuple(lib_results[x*nrows+row_i] for x in colrg) def isinstalled(name: str, lib_loc=None): """ Find whether an R package is installed :param name: name of an R package :param lib_loc: specific location for the R library (default: None) :rtype: a :class:`bool` """ instapack = InstalledPackages(lib_loc) return instapack.isinstalled(name) def importr(name: str, lib_loc=None, robject_translations={}, signature_translation=True, suppress_messages=True, on_conflict='fail', symbol_r2python=default_symbol_r2python, symbol_resolve=default_symbol_resolve, data=True): """ Import an R package. Arguments: - name: name of the R package - lib_loc: specific location for the R library (default: None) - robject_translations: dict (default: {}) - signature_translation: (True or False) - suppress_message: Suppress messages R usually writes on the console (defaut: True) - on_conflict: 'fail' or 'warn' (default: 'fail') - symbol_r2python: function to translate R symbols into Python symbols - symbol_resolve: function to check the Python symbol obtained from `symbol_r2python`. - data: embed a PackageData objects under the attribute name __rdata__ (default: True) Return: - an instance of class SignatureTranslatedPackage, or of class Package """ if not isinstalled(name, lib_loc=lib_loc): raise PackageNotInstalledError( 'The R package "%s" is not installed.' % name ) if suppress_messages: ok = quiet_require(name, lib_loc=lib_loc) else: ok = _require(name, **{'lib.loc': rinterface.StrSexpVector((lib_loc, ))})[0] if not ok: raise LibraryError("The R package %s could not be imported" % name) exported_names: typing.Optional[typing.Set[str]] if _package_has_namespace(name, _system_file(package=name)): env = _get_namespace(name) version = _get_namespace_version(name)[0] exported_names = set(_get_namespace_exports(name)) else: env = _as_env(rinterface.StrSexpVector(['package:'+name, ])) exported_names = None version = None pack: typing.Union[InstalledSTPackage, InstalledPackage] if signature_translation: pack = InstalledSTPackage(env, name, translation=robject_translations, exported_names=exported_names, on_conflict=on_conflict, version=version, symbol_r2python=symbol_r2python, symbol_resolve=symbol_resolve) else: pack = InstalledPackage(env, name, translation=robject_translations, exported_names=exported_names, on_conflict=on_conflict, version=version, symbol_r2python=symbol_r2python, symbol_resolve=symbol_resolve) if data: if pack.__rdata__ is not None: warn('While importing the R package "%s", the rpy2 Package object ' 'is masking a translated R symbol "__rdata__" already present' % name) pack.__rdata__ = PackageData(name, lib_loc=lib_loc) return pack def data(package): """ Return the PackageData for the given package.""" return package.__rdata__ def wherefrom(symbol: str, startenv: rinterface.SexpEnvironment = rinterface.globalenv): """ For a given symbol, return the environment this symbol is first found in, starting from 'startenv'. """ env = startenv while True: if symbol in env: break env = env.enclos if env.rsame(rinterface.emptyenv): break return conversion.get_conversion().rpy2py(env) class ParsedCode(rinterface.ExprSexpVector): pass class SourceCode(str): _parsed = None def parse(self): if self._parsed is None: self._parsed = ParsedCode(rinterface.parse(self)) return self._parsed def as_namespace(self, name): """ Name for the namespace """ return SignatureTranslatedAnonymousPackage(self, name) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/packages_utils.py0000644000175100001770000001176514543120705020200 0ustar00runnerdocker""" Utility module with functions related to R packages (having these in this utility module rather than in packages.py prevents circular imports). """ from rpy2 import rinterface from warnings import warn from collections import defaultdict _packages = rinterface.baseenv['.packages'] _libpaths = rinterface.baseenv['.libPaths'] _find_package = rinterface.baseenv['find.package'] def get_packagepath(package: str) -> str: """ return the path to an R package installed """ res = _find_package(rinterface.StrSexpVector((package, ))) return res[0] # Functions to translate R symbols to Python symbols. # The functions are in this module in order to facilitate # their access from other modules (without circular dependencies). # It is not necessarily the absolute best place to have the functions though. def default_symbol_r2python(rname: str) -> str: """Replace each dot (.) with an underscore (_).""" return rname.replace('.', '_') def default_symbol_resolve(symbol_mapping): """Resolve any conflict in a symbol mapping. The argument `symbol_mapping` maps candidate new symbol names (e.g., the names of Python attributes in the namespace returned by :func:`importr`) to a sequence of original symbol names (e.g., the names of objects in an R package). The purpose of this function is to resolved conflicts, that is situations where there is more than one original symbol name associated with a new symbol name. :param symbol_mapping: a :class:`dict` or dict-like object. :return: A 2-tuple with conflicts (a :class:`dict` mapping the new symbol to a sequence of symbols already matching) and resolutions (a :class:`dict` mapping new symbol). """ # dict to store the Python symbol -> R symbols mapping causing problems. conflicts = dict() resolutions = dict() for py_symbol, r_symbols in symbol_mapping.items(): n_r_symbols = len(r_symbols) if n_r_symbols == 1: continue elif n_r_symbols == 2: # more than one R symbol associated with this Python symbol try: idx = r_symbols.index(py_symbol) # there is an R symbol identical to the proposed Python symbol; # we keep that pair mapped, and change the Python symbol for # the other R symbol(s) according to PEP 0008 for i, r_name in enumerate(r_symbols): if i == idx: resolutions[py_symbol] = [r_name, ] else: new_py_symbol = py_symbol + '_' resolutions[new_py_symbol] = [r_name, ] except ValueError: # I am unsure about what to do at this point: # add it as a conflict conflicts[py_symbol] = r_symbols else: # no automatic resolution if more than 2 conflicts[py_symbol] = r_symbols return conflicts, resolutions def _map_symbols(rnames, translation=dict(), symbol_r2python=default_symbol_r2python, symbol_resolve=default_symbol_resolve): """ :param names: an iterable of rnames :param translation: a mapping for R name->python name :param symbol_r2python: a function to translate an R symbol into a (presumably valid) Python symbol :param symbol_resolve: a function to check a prospective set of translation and resolve conflicts if needed """ symbol_mapping = defaultdict(list) for rname in rnames: if rname in translation: rpyname = translation[rname] else: rpyname = symbol_r2python(rname) symbol_mapping[rpyname].append(rname) conflicts, resolutions = symbol_resolve(symbol_mapping) return (symbol_mapping, conflicts, resolutions) def _fix_map_symbols(symbol_mapping, conflicts, on_conflict, msg_prefix, exception): """ :param symbol_mapping: as returned by `_map_symbols` :param conflicts: as returned by `_map_symbols` :param on_conflict: action to take if conflict :param msg_prefix: prefix for error message :param exception: exception to raise """ if len(conflicts) > 0: msg = msg_prefix msg += '\n- '.join(('%s -> %s' % (k, ', '.join(v)) for k, v in conflicts.items())) if on_conflict == 'fail': msg += ('\nTo turn this exception into a simple ' 'warning use the parameter ' '`on_conflict="warn"`') raise exception(msg) elif on_conflict == 'warn': for k, v in conflicts.items(): if k in v: symbol_mapping[k] = [k, ] else: del symbol_mapping[k] warn(msg) else: raise ValueError('Invalid value for parameter "on_conflict"') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/pandas2ri.py0000644000175100001770000003260014543120705017054 0ustar00runnerdocker"""This module handles the conversion of data structures between R objects handled by rpy2 and pandas objects.""" import rpy2.robjects.conversion as conversion import rpy2.rinterface as rinterface from rpy2.rinterface_lib import na_values from rpy2.rinterface import IntSexpVector from rpy2.rinterface import ListSexpVector from rpy2.rinterface import SexpVector from rpy2.rinterface import StrSexpVector import datetime import functools import math import numpy # type: ignore import pandas # type: ignore import pandas.core.series # type: ignore from pandas.core.frame import DataFrame as PandasDataFrame # type: ignore from pandas.core.dtypes.api import is_datetime64_any_dtype # type: ignore import warnings from collections import OrderedDict from rpy2.robjects.vectors import (BoolVector, DataFrame, DateVector, FactorVector, FloatVector, FloatSexpVector, StrVector, IntVector, POSIXct) # The pandas converter requires numpy. import rpy2.robjects.numpy2ri as numpy2ri original_converter = None ISOdatetime = rinterface.baseenv['ISOdatetime'] as_vector = rinterface.baseenv['as.vector'] converter = conversion.Converter('original pandas conversion') py2rpy = converter.py2rpy rpy2py = converter.rpy2py # numpy types for Pandas columns that require (even more) special handling dt_O_type = numpy.dtype('O') # pandas types for series of integer (optional missing) values. integer_array_types = ('Int8', 'Int16', 'Int32', 'Int64', 'UInt8', 'UInt16', 'UInt32', 'UInt64') @py2rpy.register(PandasDataFrame) def py2rpy_pandasdataframe(obj): if obj.index.duplicated().any(): warnings.warn('DataFrame contains duplicated elements in the index, ' 'which will lead to loss of the row names in the ' 'resulting data.frame') od = OrderedDict() for name, values in obj.items(): try: od[name] = conversion.converter_ctx.get().py2rpy(values) except Exception as e: warnings.warn('Error while trying to convert ' 'the column "%s". Fall back to string conversion. ' 'The error is: %s' % (name, str(e))) od[name] = conversion.converter_ctx.get().py2rpy( values.astype('string') ) return DataFrame(od) @py2rpy.register(pandas.Index) def py2rpy_pandasindex(obj): if obj.dtype.kind == 'O': return StrVector(obj) else: # pandas2ri should definitely not have to know which paths remain to be # converted by numpy2ri # Answer: the thing is that pandas2ri builds on the conversion # rules defined by numpy2ri - deferring to numpy2ri is allowing # us to reuse that code. return numpy2ri.numpy2rpy(obj) @py2rpy.register(pandas.Categorical) def py2rpy_categorical(obj): for c in obj.categories: if not isinstance(c, str): raise ValueError('Converting pandas "Category" series to ' 'R factor is only possible when categories ' 'are strings.') res = IntSexpVector(list(rinterface.NA_Integer if x == -1 else x+1 for x in obj.codes)) res.do_slot_assign('levels', StrSexpVector(obj.categories)) if obj.ordered: res.rclass = StrSexpVector(('ordered', 'factor')) else: res.rclass = StrSexpVector(('factor',)) return res def py2rpy_categoryseries(obj): warnings.warn('Use py2rpy_categorical(obj.cat).', DeprecationWarning) return py2rpy_categorical(obj.cat) def _populate_r_vector_with_na(na_value): def f(iterable, r_vector, set_elt, cast_value): for i, v in enumerate(iterable): if ( v is None or v is pandas.NA or (isinstance(v, float) and math.isnan(v)) ): v = na_value set_elt(r_vector, i, cast_value(v)) return f _str_populate_r_vector = _populate_r_vector_with_na(na_values.NA_Character) _bool_populate_r_vector = _populate_r_vector_with_na(na_values.NA_Logical) _float_populate_r_vector = _populate_r_vector_with_na(na_values.NA_Real) def _int_populate_r_vector(iterable, r_vector, set_elt, cast_value): for i, v in enumerate(iterable): if v is None or v is pandas.NA: v = math.nan set_elt(r_vector, i, cast_value(v)) _PANDASTYPE2RPY2 = { datetime.date: DateVector, int: functools.partial( IntVector.from_iterable, populate_func=_int_populate_r_vector ), pandas.BooleanDtype: functools.partial( BoolVector.from_iterable, populate_func=_bool_populate_r_vector ), None: BoolVector, bool: BoolVector, str: functools.partial( StrVector.from_iterable, populate_func=_str_populate_r_vector ), pandas.Float64Dtype: functools.partial( FloatVector.from_iterable, populate_func=_float_populate_r_vector ), bytes: (numpy2ri.converter.py2rpy.registry[ numpy.ndarray ]) } @py2rpy.register(pandas.core.series.Series) def py2rpy_pandasseries(obj): if obj.dtype.name == 'O': warnings.warn('Element "%s" is of dtype "O" and converted ' 'to R vector of strings.' % obj.name) res = StrVector(obj) elif obj.dtype.name == 'category': res = py2rpy_categorical(obj.cat) res = FactorVector(res) elif is_datetime64_any_dtype(obj.dtype): # time series if obj.dt.tz: if obj.dt.tz is datetime.timezone.utc: tzname = 'UTC' else: tzname = obj.dt.tz.zone else: tzname = '' d = [IntVector([x.year for x in obj]), IntVector([x.month for x in obj]), IntVector([x.day for x in obj]), IntVector([x.hour for x in obj]), IntVector([x.minute for x in obj]), FloatSexpVector([x.second + x.microsecond * 1e-6 for x in obj])] res = ISOdatetime(*d, tz=StrSexpVector([tzname])) # TODO: can the POSIXct be created from the POSIXct constructor ? # (is ' 0 else -1 for x in numpy.array(obj)] res = pandas.Categorical.from_codes( codes, categories=list(obj.do_slot('levels')), ordered='ordered' in obj.rclass ) return res converter._rpy2py_nc_map.update( { rinterface.IntSexpVector: conversion.NameClassMap( numpy2ri.rpy2py, {'factor': _to_pandas_factor} ), rinterface.ListSexpVector: conversion.NameClassMap( numpy2ri.rpy2py_list, {'data.frame': lambda obj: rpy2py(DataFrame(obj))} ) } ) def activate(): warnings.warn('The global conversion available with activate() ' 'is deprecated and will be removed in the next ' 'major release. Use a local converter.', category=DeprecationWarning) global original_converter # If module is already activated, there is nothing to do. if original_converter is not None: return original_converter = conversion.Converter( 'snapshot before pandas conversion', template=conversion.get_conversion()) numpy2ri.activate() new_converter = conversion.Converter('snapshot before pandas conversion', template=conversion.get_conversion()) numpy2ri.deactivate() for k, v in py2rpy.registry.items(): if k is object: continue new_converter.py2rpy.register(k, v) for k, v in rpy2py.registry.items(): if k is object: continue new_converter.rpy2py.register(k, v) conversion.set_conversion(new_converter) def deactivate(): global original_converter # If module has never been activated or already deactivated, # there is nothing to do if original_converter is None: return conversion.set_conversion(original_converter) original_converter = None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/robject.py0000644000175100001770000001470614543120705016630 0ustar00runnerdockerimport abc import os import typing import warnings import weakref import rpy2.rinterface import rpy2.rinterface_lib.callbacks from rpy2.robjects import conversion rpy2.rinterface.initr_simple() def _add_warn_reticulate_hook(): msg = """ WARNING: The R package "reticulate" only fixed recently an issue that caused a segfault when used with rpy2: https://github.com/rstudio/reticulate/pull/1188 Make sure that you use a version of that package that includes the fix. """ rpy2.rinterface.evalr(f""" setHook(packageEvent("reticulate", "onLoad"), function(...) cat({repr(msg)})) """) _add_warn_reticulate_hook() class RSlots(object): """ Attributes of an R object as a Python mapping. R objects can have attributes (slots) that are identified by a string key (a name) and that can have any R object as the associated value. This class represents a view of those attributes that is a Python mapping. The proxy to the underlying "parent" R object is held as a weak reference. The attributes are therefore not protected from garbage collection unless bound to a Python symbol or in an other container. """ __slots__ = ['_robj', ] def __init__(self, robj): self._robj = weakref.proxy(robj) def __getitem__(self, key: str): value = self._robj.do_slot(key) return conversion.get_conversion().rpy2py(value) def __setitem__(self, key: str, value): rpy2_value = conversion.get_conversion().py2rpy(value) self._robj.do_slot_assign(key, rpy2_value) def __len__(self): return len(self._robj.list_attrs()) def keys(self): for k in self._robj.list_attrs(): yield k __iter__ = keys def items(self): for k in self._robj.list_attrs(): v = self[k] yield (k, v) def values(self): for k in self._robj.list_attrs(): v = self[k] yield v _get_exported_value = rpy2.rinterface.baseenv['::'] class RObjectMixin(abc.ABC): """ Abstract class to provide methods common to all RObject instances. """ __rname__: typing.Optional[str] = None __tempfile = rpy2.rinterface.baseenv.find("tempfile") __file = rpy2.rinterface.baseenv.find("file") __fifo = rpy2.rinterface.baseenv.find("fifo") __sink = rpy2.rinterface.baseenv.find("sink") __close = rpy2.rinterface.baseenv.find("close") __readlines = rpy2.rinterface.baseenv.find("readLines") __unlink = rpy2.rinterface.baseenv.find("unlink") __show = _get_exported_value('methods', 'show') __print = _get_exported_value('base', 'print') __slots = None @property def slots(self): """ Attributes of the underlying R object as a Python mapping. The attributes can accessed and assigned by name (as if they were in a Python `dict`).""" if self.__slots is None: self.__slots = RSlots(self) return self.__slots def __str__(self): s = [] with (rpy2.rinterface_lib .callbacks.obj_in_module(rpy2.rinterface_lib.callbacks, 'consolewrite_print', s.append)): try: self.__show(self) # There can be situation where an invalid call to R`s # show is made. Possibly some form of signature overriding # that goes through in R through dispatch (although it # should not?). In that case this is an problem upstream # and this try/except is a workaround until it gets fixed. # (issue #908). except rpy2.rinterface.embedded.RRuntimeError as rre: warnings.warn(f'Invalid call to "show()" in R: {rre}') self.__print(self) s = str.join('', s) return s def __getstate__(self, ): return (super().__getstate__(), self.__dict__.copy()) def __setstate__(self, state): rds, __dict__ = state super().__setstate__(rds) self.__dict__.update(__dict__) def __repr__(self): res = [super(RObjectMixin, self).__repr__()] try: res.append( 'R classes: {}' .format(tuple(self.rclass)) ) except Exception: res.append('Unable to fetch R classes.') return os.linesep.join(res) def r_repr(self): """ String representation for an object that can be directly evaluated as R code. """ return repr_robject(self, linesep='\n') @property def rclass(self): """ R class for the object, stored as an R string vector. When setting the rclass, the new value will be: - wrapped in a Python tuple if a string (the R class is a vector of strings, and this is made for convenience) - wrapped in a StrSexpVector Note that when setting the class R may make a copy of the whole object (R is mostly a functional language). If this must be avoided, and if the number of parent classes before and after the change are compatible, the class name can be changed in-place by replacing vector elements. """ try: res = super(RObjectMixin, self).rclass res = rpy2.rinterface.sexp.rclass_get(self.__sexp__) return res except rpy2.rinterface._rinterface.embedded.RRuntimeError as rre: if self.typeof == rpy2.rinterface.RTYPES.SYMSXP: # Unevaluated expression: has no class. return (None, ) else: raise rre @rclass.setter def rclass(self, value): if isinstance(value, str): value = (value, ) new_cls = rpy2.rinterface.StrSexpVector(value) rpy2.rinterface.sexp.rclass_set(self.__sexp__, new_cls) def repr_robject(o, linesep=os.linesep): s = rpy2.rinterface.baseenv.find("deparse")(o) s = str.join(linesep, s) return s class RObject(RObjectMixin, rpy2.rinterface.Sexp): """ Base class for all non-vector R objects. """ def __setattr__(self, name, value): if name == '_sexp': if not isinstance(value, rpy2.rinterface.Sexp): raise ValueError( '_attr must contain an object ' 'that inherits from rpy2.rinterface.Sexp ' '(not from %s)' % type(value) ) super(RObject, self).__setattr__(name, value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/robjects/vectors.py0000644000175100001770000014345014543120705016664 0ustar00runnerdockerimport abc import collections.abc from rpy2.robjects.robject import RObjectMixin import rpy2.rinterface as rinterface from rpy2.rinterface_lib import sexp from . import conversion import rpy2.rlike.container as rlc import datetime try: import zoneinfo # type: ignore except ImportError: from backports import zoneinfo # type: ignore import copy import itertools import math import os import jinja2 # type: ignore import time import tzlocal from time import struct_time, mktime import typing import warnings from rpy2.rinterface import (Sexp, ListSexpVector, StrSexpVector, IntSexpVector, ByteSexpVector, BoolSexpVector, ComplexSexpVector, PairlistSexpVector, FloatSexpVector, NA_Real, NA_Integer, NA_Character, NA_Logical, NULL, MissingArg) globalenv_ri = rinterface.globalenv baseenv_ri = rinterface.baseenv r_concat = baseenv_ri['c'] as_character = baseenv_ri['as.character'] utils_ri = baseenv_ri['as.environment']( rinterface.StrSexpVector(("package:utils", )) ) # The default timezone can be used for time or datetime objects. default_timezone = None class ExtractDelegator(object): """ Delegate the R 'extraction' ("[") and 'replacement' ("[<-") of items in a vector or vector-like object. This can help making syntactic niceties possible.""" _extractfunction = rinterface.baseenv['['] _replacefunction = rinterface.baseenv['[<-'] def __init__(self, parent): self._parent = parent def __call__(self, *args, **kwargs): """ Subset the "R-way.", using R's "[" function. In a nutshell, R indexing differs from Python indexing on: - indexing can be done with integers or strings (that are 'names') - an index equal to TRUE will mean everything selected (because of the recycling rule) - integer indexing starts at one - negative integer indexing means exclusion of the given integers - an index is itself a vector of elements to select """ conv_args = [None, ] * (len(args)+1) conv_args[0] = self._parent cv = conversion.get_conversion() for i, x in enumerate(args, 1): if x is MissingArg: conv_args[i] = x else: conv_args[i] = cv.py2rpy(x) kwargs = copy.copy(kwargs) for k, v in kwargs.items(): kwargs[k] = cv.py2rpy(v) fun = self._extractfunction res = fun(*conv_args, **kwargs) res = cv.rpy2py(res) return res def __getitem__(self, item): res = self(item) return res def __setitem__(self, item, value): """ Assign a given value to a given index position in the vector. The index position can either be: - an int: x[1] = y - a tuple of ints: x[1, 2, 3] = y - an item-able object (such as a dict): x[{'i': 1}] = y """ fun = self._replacefunction cv = conversion.get_conversion() if type(item) is tuple: args = list([None, ] * (len(item)+2)) for i, v in enumerate(item): if v is MissingArg: continue args[i+1] = cv.py2rpy(v) args[-1] = cv.py2rpy(value) args[0] = self._parent res = fun(*args) elif (type(item) is dict) or (type(item) is rlc.TaggedList): args = rlc.TaggedList.from_items(item) for i, (k, v) in enumerate(args.items()): args[i] = cv.py2rpy(v) args.append(cv.py2rpy(value), tag=None) args.insert(0, self._parent, tag=None) res = fun.rcall(tuple(args.items()), globalenv_ri) else: args = [self._parent, cv.py2rpy(item), cv.py2rpy(value)] res = fun(*args) # TODO: check refcount and copying self._parent.__sexp__ = res.__sexp__ class DoubleExtractDelegator(ExtractDelegator): """ Delegate the R 'extraction' ("[[") and "replacement" ("[[<-") of items in a vector or vector-like object. This can help making syntactic niceties possible.""" _extractfunction = rinterface.baseenv['[['] _replacefunction = rinterface.baseenv['[[<-'] class VectorOperationsDelegator(object): """ Delegate operations such as __getitem__, __add__, etc... to the corresponding R function. This permits a convenient coexistence between operators on Python sequence object with their R conterparts. """ def __init__(self, parent): """ The parent in expected to inherit from Vector. """ self._parent = parent def __add__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('+')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __sub__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('-')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __matmul__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find("%*%")(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __mul__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('*')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __pow__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('^')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __floordiv__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('%/%')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __truediv__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('/')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __mod__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('%%')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __or__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('|')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __and__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('&')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __invert__(self): res = globalenv_ri.find('!')(self._parent) return conversion.get_conversion().rpy2py(res) # Comparisons def __lt__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('<')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __le__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('<=')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __eq__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('==')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __ne__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('!=')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __gt__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('>')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __ge__(self, x): cv = conversion.get_conversion() res = globalenv_ri.find('>=')(self._parent, cv.py2rpy(x)) return cv.rpy2py(res) def __neg__(self): res = globalenv_ri.find('-')(self._parent) return res def __contains__(self, what): res = globalenv_ri.find('%in%')(what, self._parent) return res[0] _sample = rinterface.baseenv['sample'] class Vector(RObjectMixin): """Vector(seq) -> Vector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python object. In the later case, a conversion will be attempted using conversion.get_conversion().py2rpy(). R vector-like object. Items can be accessed with: - the method "__getitem__" ("[" operator) - the delegators rx or rx2 """ _html_template = jinja2.Template( """ {{ classname }} with {{ nelements }} elements. {% for elt in elements %} {% endfor %}
{{ elt }}
""") def _add_rops(self): self.ro = VectorOperationsDelegator(self) self.rx = ExtractDelegator(self) self.rx2 = DoubleExtractDelegator(self) def __add__(self, x): cv = conversion.get_conversion() res = baseenv_ri.find("c")(self, cv.py2rpy(x)) res = cv.rpy2py(res) return res def __getitem__(self, i): res = super().__getitem__(i) if isinstance(res, Sexp): res = conversion.get_conversion().rpy2py(res) return res def __setitem__(self, i, value): value = conversion.get_conversion().py2rpy(value) super().__setitem__(i, value) @property def names(self): """Names for the items in the vector.""" res = super().names res = conversion.get_conversion().rpy2py(res) return res @names.setter def names(self, value): res = globalenv_ri.find("names<-")( self, conversion.get_conversion().py2rpy(value) ) self.__sexp__ = res.__sexp__ def items(self): """ iterator on names and values """ # TODO: should be a view ? if super().names.rsame(rinterface.NULL): it_names = itertools.cycle((None, )) else: it_names = iter(self.names) it_self = iter(self) for v, k in zip(it_self, it_names): yield (k, v) def sample(self: collections.abc.Sized, n: int, replace: bool = False, probabilities: typing.Optional[collections.abc.Sized] = None): """ Draw a random sample of size n from the vector. If 'replace' is True, the sampling is done with replacement. The optional argument 'probabilities' can indicate sampling probabilities.""" assert isinstance(n, int) assert isinstance(replace, bool) if probabilities is not None: if len(probabilities) != len(self): raise ValueError('The sequence of probabilities must ' 'match the length of the vector.') if not isinstance(probabilities, rinterface.FloatSexpVector): probabilities = FloatVector(probabilities) res = _sample(self, IntVector((n,)), replace=BoolVector((replace, )), prob=probabilities) res = conversion.rpy2py(res) return res def repr_format_elt(self, elt, max_width=12): max_width = int(max_width) if elt in (NA_Real, NA_Integer, NA_Character, NA_Logical): res = repr(elt) elif isinstance(elt, int): res = '%8i' % elt elif isinstance(elt, float): res = '%8f' % elt else: if isinstance(elt, str): elt = repr(elt) else: elt = type(elt).__name__ if len(elt) < max_width: res = elt else: res = "%s..." % (str(elt[:(max_width - 3)])) return res def _iter_formatted(self, max_items=9): format_elt = self.repr_format_elt ln = len(self) half_items = max_items // 2 if ln == 0: return elif ln < max_items: for elt in conversion.noconversion(self): yield format_elt(elt, max_width=math.floor(52 / ln)) else: for elt in conversion.noconversion(self)[:half_items]: yield format_elt(elt) yield '...' for elt in conversion.noconversion(self)[-half_items:]: yield format_elt(elt) def __repr_content__(self): return ''.join(('[', ', '.join(self._iter_formatted()), ']')) def __repr__(self): return super().__repr__() + os.linesep + \ self.__repr_content__() def _repr_html_(self, max_items=7): d = {'elements': self._iter_formatted(max_items=max_items), 'classname': type(self).__name__, 'nelements': len(self)} html = self._html_template.render(d) return html class StrVector(Vector, StrSexpVector): """Vector of string elements StrVector(seq) -> StrVector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either strings, or have a str() representation. """ _factorconstructor = rinterface.baseenv['factor'] NAvalue = rinterface.NA_Character def __init__(self, obj): super().__init__(obj) self._add_rops() def factor(self): """ factor() -> FactorVector Construct a factor vector from a vector of strings. """ res = self._factorconstructor(self) return conversion.rpy2py(res) class IntVector(Vector, IntSexpVector): """ Vector of integer elements IntVector(seq) -> IntVector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either integers, or have an int() representation. """ _tabulate = rinterface.baseenv['tabulate'] NAvalue = rinterface.NA_Integer def __init__(self, obj): super().__init__(obj) self._add_rops() def repr_format_elt(self, elt, max_width: int = 8): return repr(elt) def tabulate(self, nbins=None): """ Like the R function tabulate, count the number of times integer values are found """ if nbins is None: nbins = max(1, max(self)) res = self._tabulate(self) return conversion.rpy2py(res) class BoolVector(Vector, BoolSexpVector): """ Vector of boolean (logical) elements BoolVector(seq) -> BoolVector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either booleans, or have a bool() representation. """ NAvalue = rinterface.NA_Logical def __init__(self, obj): super().__init__(obj) self._add_rops() class ByteVector(Vector, ByteSexpVector): """ Vector of byte elements ByteVector(seq) -> ByteVector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either be bytes (integers >= 0 and <= 255). """ def __init__(self, obj): super().__init__(obj) self._add_rops() class ComplexVector(Vector, ComplexSexpVector): """ Vector of complex elements ComplexVector(seq) -> ComplexVector The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either complex, or have a complex() representation. """ NAvalue = rinterface.NA_Complex def __init__(self, obj): super().__init__(obj) self._add_rops() class FloatVector(Vector, FloatSexpVector): """ Vector of float (double) elements FloatVector(seq) -> FloatVector. The parameter 'seq' can be an instance inheriting from rinterface.SexpVector, or an arbitrary Python sequence. In the later case, all elements in the sequence should be either float, or have a float() representation. """ NAvalue = rinterface.NA_Real def __init__(self, obj): super().__init__(obj) self._add_rops() class FactorVector(IntVector): """ Vector of 'factors'. FactorVector(obj, levels = rinterface.MissingArg, labels = rinterface.MissingArg, exclude = rinterface.MissingArg, ordered = rinterface.MissingArg) -> FactorVector obj: StrVector or StrSexpVector levels: StrVector or StrSexpVector labels: StrVector or StrSexpVector (of same length as levels) exclude: StrVector or StrSexpVector ordered: boolean """ _factor = baseenv_ri['factor'] _levels = baseenv_ri['levels'] _levels_set = baseenv_ri['levels<-'] _nlevels = baseenv_ri['nlevels'] _isordered = baseenv_ri['is.ordered'] NAvalue = rinterface.NA_Integer def __init__(self, obj, levels=rinterface.MissingArg, labels=rinterface.MissingArg, exclude=rinterface.MissingArg, ordered=rinterface.MissingArg): if not isinstance(obj, Sexp): obj = StrSexpVector(obj) if ('factor' in obj.rclass) and \ all(p is rinterface.MissingArg for p in (labels, exclude, ordered)): res = obj else: res = self._factor(obj, levels=levels, labels=labels, exclude=exclude, ordered=ordered) super(FactorVector, self).__init__(res) def repr_format_elt(self, elt, max_width=8): max_width = int(max_width) levels = self._levels(self) if elt is NA_Integer: res = repr(elt) else: res = levels[elt-1] if len(res) >= max_width: res = "%s..." % (res[:(max_width - 3)]) return res def __levels_get(self): res = self._levels(self) return conversion.rpy2py(res) def __levels_set(self, value): res = self._levels_set(self, conversion.get_conversion().py2rpy(value)) self.__sexp__ = res.__sexp__ levels = property(__levels_get, __levels_set) def __nlevels_get(self): res = self._nlevels(self) return res[0] nlevels = property(__nlevels_get, None, None, "number of levels ") def __isordered_get(self): res = self._isordered(self) return res[0] isordered = property(__isordered_get, None, None, "are the levels in the factor ordered ?") def iter_labels(self): """ Iterate the over the labels, that is iterate over the items returning associated label for each item """ levels = self.levels for x in conversion.noconversion(self): yield (rinterface.NA_Character if x is rinterface.NA_Integer else levels[x-1]) class PairlistVector(Vector, PairlistSexpVector): """R pairlist vector.""" pass class ListVector(Vector, ListSexpVector): """ R list (vector of arbitray elements) ListVector(iterable) -> ListVector. The parameter 'iterable' can be: - an object with a method `items()`, such for example a dict, a rpy2.rlike.container.TaggedList, an rpy2.rinterface.SexpVector of type VECSXP. - an iterable of (name, value) tuples """ _vector = rinterface.baseenv['vector'] _html_template = jinja2.Template( """ {{ classname }} with {{ nelements }} elements. {% for name, elt in names_elements %} {% endfor %}
{{ name }} {{ elt }}
""") def __init__(self, tlist): if isinstance(tlist, rinterface.ListSexpVector): if tlist.typeof != rinterface.RTYPES.VECSXP: raise ValueError("tlist should have " "tlist.typeof == rinterface.RTYPES.VECSXP") super().__init__(tlist) elif hasattr(tlist, 'items') and callable(tlist.items): cv = conversion.get_conversion() kv = [(k, cv.py2rpy(v)) for k, v in tlist.items()] kv = tuple(kv) df = baseenv_ri.find("list").rcall(kv, globalenv_ri) super().__init__(df) elif hasattr(tlist, "__iter__"): if not callable(tlist.__iter__): raise ValueError('tlist should have a /method/ __iter__ ' '(not an attribute)') cv = conversion.get_conversion() kv = [(str(k), cv.py2rpy(v)) for k, v in tlist] kv = tuple(kv) df = baseenv_ri.find("list").rcall(kv, globalenv_ri) super().__init__(df) else: raise ValueError('tlist can only be either an iter-able or an ' 'instance of rpy2.rinterface.ListSexpVector, ' 'of R type VECSXP, or a Python dict.') self._add_rops() def _iter_repr(self, max_items=9): if len(self) <= max_items: for elt in conversion.noconversion(self): yield elt else: half_items = max_items // 2 for i in range(0, half_items): yield self[i] yield '...' for i in range(-half_items, 0): yield self[i] def __repr__(self): res = [] for i, elt in enumerate(self._iter_repr()): if isinstance(elt, ListVector): res.append(super().__repr__()) elif isinstance(elt, str) and elt == '...': res.append(elt) else: try: name = self.names[i] except TypeError: name = '' res.append(" %s: %s%s %s" % (name, type(elt), os.linesep, elt.__repr__())) res = super().__repr__() + os.linesep + \ os.linesep.join(res) return res def _repr_html_(self, max_items=7): elements = list() for e in self._iter_repr(max_items=max_items): if hasattr(e, '_repr_html_'): elements.append(e._repr_html_()) else: elements.append(e) names = list() rnames = self.names rnames_null = rinterface.NULL.rsame(rnames) if len(self) <= max_items: names.extend( rnames if not rnames_null else ['[no name]'] * len(self) ) else: half_items = max_items // 2 for i in range(0, half_items): try: name = (rnames[i] if not rnames_null else '[no name]') except TypeError: name = '[no name]' names.append(name) names.append('...') for i in range(-half_items, 0): try: name = rnames[i] except TypeError: name = '[no name]' names.append(name) d = {'names_elements': zip(names, elements), 'nelements': len(self), 'classname': type(self).__name__} html = self._html_template.render(d) return html @classmethod def from_length(cls, length): """ Create a list of given length """ res = ListVector._vector(StrSexpVector(("list", )), length) res = cls(res) return res class POSIXt(abc.ABC): """ POSIX time vector. This is an abstract class. """ def repr_format_elt(self, elt, max_width=12): max_width = int(max_width) str_elt = str(elt) if len(str_elt) < max_width: res = elt else: res = "%s..." % str_elt[:(max_width - 3)] return res def _iter_formatted(self, max_items=9): ln = len(self) half_items = max_items // 2 if ln == 0: return elif ln < max_items: str_vec = StrVector(as_character(self)) else: str_vec = r_concat( as_character( self.rx(IntSexpVector(tuple(range(1, (half_items-1))))) ), StrSexpVector(['...']), as_character( self.rx(IntSexpVector(tuple(range((ln-half_items), ln)))) )) for str_elt in str_vec: yield self.repr_format_elt(str_elt) class POSIXlt(POSIXt, ListVector): """ Representation of dates with a 11-component structure (similar to Python's time.struct_time). POSIXlt(seq) -> POSIXlt. The constructor accepts either an R vector or a sequence (an object with the Python sequence interface) of time.struct_time objects. """ _expected_colnames = { x: i for i, x in enumerate( ('sec', 'min', 'hour', 'mday', 'mon', 'year', 'wday', 'yday', 'isdst', 'zone', 'gmtoff')) } # R starts the week on Sunday while Python starts it on Monday _wday_r_to_py = (6, 0, 1, 2, 3, 4, 5) def __init__(self, seq): """ """ if isinstance(seq, Sexp): super()(seq) else: for elt in conversion.noconversion(seq): if not isinstance(elt, struct_time): raise ValueError( 'All elements must inherit from time.struct_time' ) as_posixlt = baseenv_ri['as.POSIXlt'] origin = StrSexpVector([time.strftime("%Y-%m-%d", time.gmtime(0)), ]) rvec = FloatSexpVector([mktime(x) for x in seq]) sexp = as_posixlt(rvec, origin=origin) super().__init__(sexp) def __getitem__(self, i): # "[[" operator returns the components of a time object # (and yes, this is confusing) aslist = ListVector(self) idx = self._expected_colnames seq = (aslist[idx['year']][i]+1900, aslist[idx['mon']][i]+1, aslist[idx['mday']][i], aslist[idx['hour']][i], aslist[idx['min']][i], aslist[idx['sec']][i], self._wday_r_to_py[aslist[idx['wday']][i]], aslist[idx['yday']][i]+1, aslist[idx['isdst']][i]) return struct_time( seq, {'tm_zone': aslist[idx['zone']][i], 'tmp_gmtoff': aslist[idx['gmtoff']][i]} ) def __repr__(self): return super(Sexp, self).__repr__() def get_timezone(): """Return the system's timezone settings.""" if default_timezone: timezone = default_timezone else: timezone = tzlocal.get_localzone() return timezone class DateVector(FloatVector): """ Representation of dates as number of days since 1/1/1970. Date(seq) -> Date. The constructor accepts either an R vector floats or a sequence (an object with the Python sequence interface) of time.struct_time objects. """ def __init__(self, seq): """ Create a POSIXct from either an R vector or a sequence of Python datetime.date objects. """ if isinstance(seq, Sexp): init_param = seq elif isinstance(seq[0], datetime.date): init_param = DateVector.sexp_from_date(seq) else: raise TypeError( 'Unable to create an R Date vector from objects of type %s' % type(seq)) super().__init__(init_param) @classmethod def sexp_from_date(cls, seq): return cls(FloatVector([x.toordinal() for x in seq])) @staticmethod def isrinstance(obj) -> bool: """Return whether an R object an instance of Date.""" return obj.rclass[-1] == 'Date' class POSIXct(POSIXt, FloatVector): """ Representation of dates as seconds since Epoch. This form is preferred to POSIXlt for inclusion in a DataFrame. POSIXlt(seq) -> POSIXlt. The constructor accepts either an R vector floats or a sequence (an object with the Python sequence interface) of time.struct_time objects. """ _as_posixct = baseenv_ri['as.POSIXct'] _ISOdatetime = baseenv_ri['ISOdatetime'] def __init__(self, seq): """ Create a POSIXct from either an R vector or a sequence of Python dates. """ if isinstance(seq, Sexp): init_param = seq elif isinstance(seq[0], struct_time): init_param = POSIXct.sexp_from_struct_time(seq) elif isinstance(seq[0], datetime.datetime): init_param = POSIXct.sexp_from_datetime(seq) else: raise TypeError( 'All elements must inherit from time.struct_time or ' 'datetime.datetime.') super().__init__(init_param) @staticmethod def _sexp_from_seq(seq, tz_info_getter, isodatetime_columns): """ return a POSIXct vector from a sequence of time.struct_time elements. """ tz_count = 0 tz_info = None for elt in conversion.noconversion(seq): tmp = tz_info_getter(elt) if tz_info is None: tz_info = tmp tz_count = 1 elif tz_info == tmp: tz_count += 1 else: # different time zones # TODO: create a list of time zones with tz_count times # tz_info, add the current tz_info and append further. raise ValueError( 'Sequences of dates with different time zones not ' 'yet allowed.' ) if tz_info is None: tz_info = default_timezone if default_timezone else '' # We could use R's as.POSIXct instead of ISOdatetime # since as.POSIXct is used by it anyway, but the overall # interface for dates and conversion between formats # is not exactly straightforward. Someone with more # time should look into this. d = isodatetime_columns(seq) sexp = POSIXct._ISOdatetime( *d, tz=StrSexpVector((str(tz_info), )) ) return sexp @staticmethod def sexp_from_struct_time(seq): def f(seq): return [IntVector([x.tm_year for x in seq]), IntVector([x.tm_mon for x in seq]), IntVector([x.tm_mday for x in seq]), IntVector([x.tm_hour for x in seq]), IntVector([x.tm_min for x in seq]), IntVector([x.tm_sec for x in seq])] return POSIXct._sexp_from_seq(seq, lambda elt: elt.tm_zone, f) @staticmethod def sexp_from_datetime(seq): """ return a POSIXct vector from a sequence of datetime.datetime elements. """ def f(seq): return [IntVector([x.year for x in seq]), IntVector([x.month for x in seq]), IntVector([x.day for x in seq]), IntVector([x.hour for x in seq]), IntVector([x.minute for x in seq]), IntVector([x.second for x in seq])] def get_tz(elt): return elt.tzinfo if elt.tzinfo else None return POSIXct._sexp_from_seq(seq, get_tz, f) @staticmethod def isrinstance(obj) -> bool: """Is an R object an instance of POSIXct.""" return obj.rclass[0] == 'POSIXct' @staticmethod def _datetime_from_timestamp(ts, tz) -> datetime.datetime: """Platform-dependent conversion from timestamp to datetime""" if os.name != 'nt' or ts > 0: return datetime.datetime.fromtimestamp(ts, tz) else: dt_utc = (datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(seconds=ts)) dt = dt_utc.replace(tzinfo=tz) offset = dt.utcoffset() if offset is None: return dt else: return dt + offset def iter_localized_datetime(self): """Iterator yielding localized Python datetime objects.""" try: r_tzone_name = self.do_slot('tzone')[0] except LookupError: warnings.warn('R object inheriting from "POSIXct" but without ' 'attribute "tzone".') r_tzone_name = '' if r_tzone_name == '': # R is implicitly using the local timezone, while Python # time libraries will assume UTC. r_tzone = get_timezone() else: r_tzone = zoneinfo.ZoneInfo(r_tzone_name) for x in self: yield ( None if math.isnan(x) else POSIXct._datetime_from_timestamp(x, r_tzone) ) class Array(Vector): """ An R array """ _dimnames_get = baseenv_ri['dimnames'] _dimnames_set = baseenv_ri['dimnames<-'] _dim_get = baseenv_ri['dim'] _dim_set = baseenv_ri['dim<-'] _isarray = baseenv_ri['is.array'] def __dim_get(self): res = self._dim_get(self) res = conversion.get_conversion().rpy2py(res) return res def __dim_set(self, value): # TODO: R will create a copy of the object upon assignment # of a new dimension attribute. raise NotImplementedError("Not yet implemented") value = conversion.get_conversion().py2rpy(value) self._dim_set(self, value) dim = property(__dim_get, __dim_set, None, "Get or set the dimension of the array.") @property def names(self) -> sexp.Sexp: """ Return a list of name vectors (like the R function 'dimnames' does).""" res = self._dimnames_get(self) res = conversion.get_conversion().rpy2py(res) return res @names.setter def names(self, value) -> None: """ Set list of name vectors (like the R function 'dimnames' does).""" value = conversion.get_conversion().rpy2py(value) res = self._dimnames_set(self, value) self.__sexp__ = res.__sexp__ dimnames = names class IntArray(Array, IntVector): pass class ByteArray(Array, ByteVector): pass class FloatArray(Array, FloatVector): pass class BoolArray(Array, BoolVector): pass class ComplexArray(Array, ComplexVector): pass class StrArray(Array, StrVector): pass class Matrix(Array): """ An R matrix """ _transpose = baseenv_ri['t'] _rownames = baseenv_ri['rownames'] _colnames = baseenv_ri['colnames'] _dot = baseenv_ri['%*%'] _matmul = baseenv_ri['%*%'] _crossprod = baseenv_ri['crossprod'] _tcrossprod = baseenv_ri['tcrossprod'] _svd = baseenv_ri['svd'] _eigen = baseenv_ri['eigen'] def __nrow_get(self): """ Number of rows. :rtype: integer """ return self.dim[0] nrow = property(__nrow_get, None, None, "Number of rows") def __ncol_get(self): """ Number of columns. :rtype: integer """ return self.dim[1] ncol = property(__ncol_get, None, None, "Number of columns") def __rownames_get(self): """ Row names :rtype: SexpVector """ res = self._rownames(self) return conversion.get_conversion().rpy2py(res) def __rownames_set(self, rn): if isinstance(rn, StrSexpVector): if len(rn) != self.nrow: raise ValueError('Invalid length.') if self.dimnames is NULL: dn = ListVector.from_length(2) dn[0] = rn self.do_slot_assign('dimnames', dn) else: dn = self.dimnames dn[0] = rn else: raise ValueError( 'The rownames attribute can only be an R string vector.' ) rownames = property(__rownames_get, __rownames_set, None, "Row names") def __colnames_get(self): """ Column names :rtype: SexpVector """ res = self._colnames(self) return conversion.get_conversion().rpy2py(res) def __colnames_set(self, cn): if isinstance(cn, StrSexpVector): if len(cn) != self.ncol: raise ValueError('Invalid length.') if self.dimnames is NULL: dn = ListVector.from_length(2) dn[1] = cn self.do_slot_assign('dimnames', dn) else: dn = self.dimnames dn[1] = cn else: raise ValueError( 'The colnames attribute can only be an R string vector.' ) colnames = property(__colnames_get, __colnames_set, None, "Column names") def transpose(self): """ transpose the matrix """ res = self._transpose(self) return conversion.get_conversion().rpy2py(res) def __matmul__(self, x): """ Matrix multiplication. """ cv = conversion.get_conversion() res = self._matmul(self, cv.py2rpy(x)) return cv.rpy2py(res) def crossprod(self, m): """ crossproduct X'.Y""" cv = conversion.get_conversion() res = self._crossprod(self, cv.rpy2py(m)) return cv.rpy2py(res) def tcrossprod(self, m): """ crossproduct X.Y'""" res = self._tcrossprod(self, m) return conversion.get_conversion().rpy2py(res) def svd(self, nu=None, nv=None, linpack=False): """ SVD decomposition. If nu is None, it is given the default value min(tuple(self.dim)). If nv is None, it is given the default value min(tuple(self.dim)). """ if nu is None: nu = min(tuple(self.dim)) if nv is None: nv = min(tuple(self.dim)) res = self._svd(self, nu=nu, nv=nv) return conversion.get_conversion().rpy2py(res) def dot(self, m): """ Matrix multiplication """ res = self._dot(self, m) return conversion.get_conversion().rpy2py(res) def eigen(self): """ Eigen values """ res = self._eigen(self) return conversion.get_conversion().rpy2py(res) class DataFrame(ListVector): """ R 'data.frame'. """ _dataframe_name = rinterface.StrSexpVector(('data.frame',)) _read_csv = utils_ri['read.csv'] _write_table = utils_ri['write.table'] _cbind = rinterface.baseenv['cbind.data.frame'] _rbind = rinterface.baseenv['rbind.data.frame'] _is_list = rinterface.baseenv['is.list'] _html_template = jinja2.Template( """ R/rpy2 DataFrame ({{ nrows }} x {{ ncolumns }}) {% for name in column_names %} {% endfor %} {% for row_i in rows %} {% for col_i in columns %} {% endfor %} {% endfor %}
{{ name }}
{{ elements[col_i][row_i] }}
""") def __init__(self, obj, stringsasfactor=False, checknames=False): """ Create a new data frame. :param obj: object inheriting from rpy2.rinterface.SexpVector, or inheriting from TaggedList or a mapping name -> value :param stringsasfactors: Boolean indicating whether vectors of strings should be turned to vectors. Note that factors will not be turned to string vectors. :param checknames: Boolean indicating whether column names should be transformed to R syntactically valid names. """ if isinstance(obj, rinterface.ListSexpVector): if obj.typeof != rinterface.RTYPES.VECSXP: raise ValueError( "obj should be of typeof RTYPES.VECSXP " " (and we get %s)" % rinterface.RTYPES(obj.typeof) ) if ( self._is_list(obj)[0] or globalenv_ri.find('inherits')( obj, self._dataframe_name )[0] ): # TODO: is it really a good idea to pass R lists # to the constructor ? super().__init__(obj) return else: raise ValueError( "When passing R objects to build a DataFrame, " "the R object must be a list or inherit from " "the R class 'data.frame'." ) elif isinstance(obj, rlc.TaggedList): cv = conversion.get_conversion() kv = [(k, cv.py2rpy(v)) for k, v in obj.items()] else: cv = conversion.get_conversion() try: kv = [(str(k), cv.py2rpy(v)) for k, v in obj.items()] except AttributeError: raise ValueError( 'obj can only be' 'an instance of rpy2.rinterface.ListSexpVector, ' 'an instance of TaggedList, ' 'or an objects with a methods items() that returns ' '(key, value) pairs ' '(such a Python dict, rpy2.rlike.container OrdDict).') # Check if there is a conflicting column name if 'stringsAsFactors' in (k for k, v in kv): warnings.warn('The column name "stringsAsFactors" is ' 'conflicting with named parameter ' 'in underlying R function "data.frame()".') else: kv.extend((('stringsAsFactors', stringsasfactor), ('check.names', checknames))) # Call R's data frame constructor kv = tuple(kv) df = baseenv_ri.find("data.frame").rcall(kv, globalenv_ri) super().__init__(df) def _repr_html_(self, max_items=7): names = list() if len(self) <= max_items: names.extend(self.names) else: half_items = max_items // 2 for i in range(0, half_items): try: name = self.names[i] except TypeError: name = '[no name]' names.append(name) names.append('...') for i in range(-half_items, 0): try: name = self.names[i] except TypeError: name = '[no name]' names.append(name) elements = list() for e in self._iter_repr(max_items=max_items): if hasattr(e, '_repr_html_'): elements.append(tuple(e._iter_formatted())) else: elements.append(['...', ]) d = {'column_names': names, 'rows': range(len(elements[0]) if len(elements) else 0), 'columns': tuple(range(len(names))), 'nrows': self.nrow, 'ncolumns': self.ncol, 'elements': elements} html = self._html_template.render(d) return html def _get_nrow(self): """ Number of rows. :rtype: integer """ return baseenv_ri["nrow"](self)[0] nrow = property(_get_nrow, None, None) def _get_ncol(self): """ Number of columns. :rtype: integer """ return baseenv_ri["ncol"](self)[0] ncol = property(_get_ncol, None, None) def _get_rownames(self): res = baseenv_ri["rownames"](self) return conversion.get_conversion().rpy2py(res) def _set_rownames(self, rownames): res = baseenv_ri["rownames<-"]( self, conversion.get_conversion().py2rpy(rownames) ) self.__sexp__ = res.__sexp__ rownames = property(_get_rownames, _set_rownames, None, 'Row names') def _get_colnames(self): res = baseenv_ri["colnames"](self) return conversion.get_conversion().rpy2py(res) def _set_colnames(self, colnames): res = baseenv_ri["colnames<-"]( self, conversion.get_conversion().py2rpy(colnames) ) self.__sexp__ = res.__sexp__ colnames = property(_get_colnames, _set_colnames, None) def __getitem__(self, i): # Make sure this is not a List returned # 3rd-party conversions could return objects # that no longer inherit from rpy2's R objects. # We need to use the low-level __getitem__ # to bypass the conversion mechanism. # R's data.frames have no representation at the C-API level # (they are lists) tmp = rinterface.ListSexpVector.__getitem__(self, i) if tmp.typeof == rinterface.RTYPES.VECSXP: return DataFrame(tmp) else: return conversion.get_conversion().rpy2py(tmp) def cbind(self, *args, **kwargs): """ bind objects as supplementary columns """ new_args = [self, ] + [conversion.rpy2py(x) for x in args] new_kwargs = dict( [(k, conversion.rpy2py(v)) for k, v in kwargs.items()] ) res = self._cbind(*new_args, **new_kwargs) return conversion.get_conversion().rpy2py(res) def rbind(self, *args, **kwargs): """ bind objects as supplementary rows """ new_args = [conversion.rpy2py(x) for x in args] new_kwargs = dict( [(k, conversion.rpy2py(v)) for k, v in kwargs.items()] ) res = self._rbind(self, *new_args, **new_kwargs) return conversion.rpy2py(res) def head(self, *args, **kwargs): """ Call the R generic 'head()'. """ res = utils_ri['head'](self, *args, **kwargs) return conversion.rpy2py(res) @classmethod def from_csvfile(cls, path, header=True, sep=',', quote='"', dec='.', row_names=rinterface.MissingArg, col_names=rinterface.MissingArg, fill=True, comment_char='', na_strings=[], as_is=False): """ Create an instance from data in a .csv file. :param path: string with a path :param header: boolean (heading line with column names or not) :param sep: separator character :param quote: quote character :param row_names: column name, or column index for column names (warning: indexing starts at one in R) :param fill: boolean (fill the lines when less entries than columns) :param comment_char: comment character :param na_strings: a list of strings which are interpreted to be NA values :param as_is: boolean (keep the columns of strings as such, or turn them into factors) """ cv = conversion.get_conversion() path = cv.py2rpy(path) header = cv.py2rpy(header) sep = cv.py2rpy(sep) quote = cv.py2rpy(quote) dec = cv.py2rpy(dec) if row_names is not rinterface.MissingArg: row_names = cv.py2rpy(row_names) if col_names is not rinterface.MissingArg: col_names = cv.py2rpy(col_names) fill = cv.py2rpy(fill) comment_char = cv.py2rpy(comment_char) as_is = cv.py2rpy(as_is) na_strings = cv.py2rpy(na_strings) res = DataFrame._read_csv(path, **{'header': header, 'sep': sep, 'quote': quote, 'dec': dec, 'row.names': row_names, 'col.names': col_names, 'fill': fill, 'comment.char': comment_char, 'na.strings': na_strings, 'as.is': as_is}) return cls(res) def to_csvfile(self, path, quote=True, sep=',', eol=os.linesep, na='NA', dec='.', row_names=True, col_names=True, qmethod='escape', append=False): """ Save the data into a .csv file. :param path : string with a path :param quote : quote character :param sep : separator character :param eol : end-of-line character(s) :param na : string for missing values :param dec : string for decimal separator :param row_names : boolean (save row names, or not) :param col_names : boolean (save column names, or not) :param comment_char : method to 'escape' special characters :param append : boolean (append if the file in the path is already existing, or not) """ cv = conversion.get_conversion() path = cv.py2rpy(path) append = cv.py2rpy(append) sep = cv.py2rpy(sep) eol = cv.py2rpy(eol) na = cv.py2rpy(na) dec = cv.py2rpy(dec) row_names = cv.py2rpy(row_names) col_names = cv.py2rpy(col_names) qmethod = cv.py2rpy(qmethod) res = self._write_table( self, **{'file': path, 'quote': quote, 'sep': sep, 'eol': eol, 'na': na, 'dec': dec, 'row.names': row_names, 'col.names': col_names, 'qmethod': qmethod, 'append': append}) return res def iter_row(self): """ iterator across rows """ for i in range(self.nrow): yield self.rx(i+1, rinterface.MissingArg) def iter_column(self): """ iterator across columns """ for i in range(self.ncol): yield self.rx(rinterface.MissingArg, i+1) class IntMatrix(Matrix, IntVector): pass class ByteMatrix(Matrix, ByteVector): pass class FloatMatrix(Matrix, FloatVector): pass class BoolMatrix(Matrix, BoolVector): pass class ComplexMatrix(Matrix, ComplexVector): pass class StrMatrix(Matrix, StrVector): pass rtypeof2rotype = { rinterface.RTYPES.INTSXP: IntVector, rinterface.RTYPES.REALSXP: FloatVector, rinterface.RTYPES.STRSXP: StrVector, rinterface.RTYPES.CPLXSXP: ComplexVector, rinterface.RTYPES.LGLSXP: BoolVector } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/situation.py0000644000175100001770000004064614543120705015406 0ustar00runnerdocker""" This module is currently primarily intended to be used as a script. It will print information about the rpy2's environment (Python version, R version, rpy2 version, etc...). """ import argparse import enum import logging import os import shlex import subprocess import sys from typing import Optional import warnings logger = logging.getLogger(__name__) if sys.maxsize > 2**32: r_version_folder = 'x64' else: r_version_folder = 'i386' ENVVAR_CFFI_TYPE: str = 'RPY2_CFFI_MODE' class CFFI_MODE(enum.Enum): API = 'API' ABI = 'ABI' BOTH = 'BOTH' ANY = 'ANY' def get_cffi_mode(default=CFFI_MODE.ANY): cffi_mode = os.environ.get(ENVVAR_CFFI_TYPE, '') res = default for m in (CFFI_MODE.API, CFFI_MODE.ABI, CFFI_MODE.BOTH, CFFI_MODE.ANY): if cffi_mode.upper() == m.value: res = m logger.info(f'cffi mode is {m}') return res def assert_python_version(): if not (sys.version_info[0] >= 3 and sys.version_info[1] >= 7): msg = 'Python >=3.3 is required to run rpy2' logger.error(msg) raise RuntimeError(msg) def r_version_from_subprocess(): cmd = ('R', '--version') logger.debug('Looking for R version with: {}'.format(' '.join(cmd))) try: tmp = subprocess.check_output(cmd, stderr=subprocess.STDOUT) except Exception as e: # FileNotFoundError, WindowsError, etc logger.error(f'Unable to determine the R version: {e}') return None r_version = tmp.decode('ascii', 'ignore').split(os.linesep) if r_version[0].startswith('WARNING'): r_version = r_version[1] else: r_version = r_version[0].strip() logger.info(f'R version found: {r_version}') return r_version def r_home_from_subprocess() -> Optional[str]: """Return the R home directory from calling 'R RHOME'.""" cmd = ('R', 'RHOME') logger.debug('Looking for R home with: {}'.format(' '.join(cmd))) tmp = subprocess.check_output(cmd, universal_newlines=True) # may raise FileNotFoundError, WindowsError, etc r_home = tmp.split(os.linesep) if r_home[0].startswith('WARNING'): res = r_home[1] else: res = r_home[0].strip() return res # TODO: move all Windows all code into an os-specific module ? def r_home_from_registry() -> Optional[str]: """Return the R home directory from the Windows Registry.""" from packaging.version import Version try: import winreg # type: ignore except ImportError: import _winreg as winreg # type: ignore # There are two possible locations for RHOME in the registry # We prefer the user installation (which the user has more control # over). Thus, HKEY_CURRENT_USER is the first item in the list and # the for-loop breaks at the first hit. for w_hkey in [ winreg.HKEY_CURRENT_USER, # type: ignore winreg.HKEY_LOCAL_MACHINE # type: ignore ]: try: with winreg.OpenKeyEx( # type:ignore w_hkey, 'Software\\R-core\\R' ) as hkey: # >v4.x.x: grab the highest version installed def get_version(i): try: return Version(winreg.EnumKey(hkey, i)) except Exception: return None latest = max( ( v for v in ( get_version(i) for i in range( winreg.QueryInfoKey(hkey)[0] # type: ignore ) ) if v is not None ) ) with winreg.OpenKeyEx( # type: ignore hkey, f'{latest}' ) as subkey: r_home = winreg.QueryValueEx( # type: ignore subkey, "InstallPath" )[0] # check for an earlier version if not r_home: r_home = winreg.QueryValueEx( # type: ignore hkey, 'InstallPath' )[0] except Exception: # FileNotFoundError, WindowsError, OSError, etc. pass else: # We have a path RHOME if sys.version_info[0] == 2: # Python 2 path compatibility r_home = r_home.encode(sys.getfilesystemencoding()) # Break the loop, because we have a hit. break else: # for-loop did not break - RHOME is unknown. logger.error('Unable to determine R home.') r_home = None return r_home def r_ld_library_path_from_subprocess(r_home: str) -> str: """Get the LD_LIBRARY_PATH settings added by R.""" cmd = (os.path.join(r_home, 'bin', 'Rscript'), '-e', 'cat(Sys.getenv("LD_LIBRARY_PATH"))') logger.debug('Looking for LD_LIBRARY_PATH with: {}'.format(' '.join(cmd))) try: r_lib_path = subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.PIPE) logger.info(f'R library path: {r_lib_path}') except Exception as e: # FileNotFoundError, WindowsError, etc. logger.error(f'Unable to determine R library path: {e}') r_lib_path = '' logger.info(f'LD_LIBRARY_PATH: {r_lib_path}') return r_lib_path def get_rlib_rpath(r_home: str) -> str: """Get the path for the R shared library/libraries.""" lib_path = os.path.join(r_home, get_r_libnn(r_home)) return lib_path # TODO: Does r_ld_library_path_from_subprocess() supersed this? def get_rlib_path(r_home: str, system: str) -> str: """Get the path for the R shared library.""" if system == 'FreeBSD' or system == 'Linux': lib_path = os.path.join(r_home, 'lib', 'libR.so') elif system == 'Darwin': lib_path = os.path.join(r_home, 'lib', 'libR.dylib') elif system == 'Windows': # i386 os.environ['PATH'] = os.pathsep.join( (os.environ['PATH'], os.path.join(r_home, 'bin', r_version_folder)) ) lib_path = os.path.join(r_home, 'bin', r_version_folder, 'R.dll') else: raise ValueError( 'The system {system} is currently not supported.' .format(system=system) ) return lib_path def get_r_home() -> Optional[str]: """Get R's home directory (aka R_HOME). If an environment variable R_HOME is found it is returned, and if none is found it is trying to get it from an R executable in the PATH. On Windows, a third last attempt is made by trying to obtain R_HOME from the registry. If all attempt are unfruitful, None is returned. """ r_home = os.environ.get('R_HOME') if not r_home: try: r_home = r_home_from_subprocess() except Exception as e: if os.name == 'nt': r_home = r_home_from_registry() if r_home is None: logger.error(f'Unable to determine R home: {e}') logger.info(f'R home found: {r_home}') return r_home def get_r_exec(r_home: str) -> str: """Get the path of the R executable/binary. :param: R HOME directory :return: Path to the R executable/binary""" if sys.platform == 'win32' and '64 bit' in sys.version: r_exec = os.path.join(r_home, 'bin', 'x64', 'R') else: r_exec = os.path.join(r_home, 'bin', 'R') logger.info(f'R exec path: {r_exec}') return r_exec def _get_r_cmd_config(r_home: str, about: str, allow_empty=False): """Get the output of calling 'R CMD CONFIG '. :param r_home: R HOME directory :param about: argument passed to the command line 'R CMD CONFIG' :param allow_empty: allow the output to be empty :return: a tuple (lines of output)""" r_exec = get_r_exec(r_home) cmd = (r_exec, 'CMD', 'config', about) logger.debug('Looking for R CONFIG with: {}'.format(' '.join(cmd))) output = subprocess.check_output( cmd, universal_newlines=True ).split(os.linesep) # Twist if 'R RHOME' spits out a warning if output[0].startswith('WARNING'): msg = 'R emitting a warning: {}'.format(output[0]) warnings.warn(msg) logger.debug(msg) res = output[1:] else: res = output logger.debug(res) return res def get_r_libnn(r_home: str): return _get_r_cmd_config(r_home, 'LIBnn', allow_empty=False)[0] _R_LIBS = ('LAPACK_LIBS', 'BLAS_LIBS') _R_FLAGS = ('--ldflags', '--cppflags') def get_r_flags(r_home: str, flags: str): """Get the parsed output of calling 'R CMD CONFIG '. Returns a tuple (parsed_args, unknown_args), with parsed_args having the attribute `l`, 'L', and 'I'.""" assert flags in _R_FLAGS parser = argparse.ArgumentParser() parser.add_argument('-I', action='append') parser.add_argument('-L', action='append') parser.add_argument('-l', action='append') res = shlex.split( ' '.join( _get_r_cmd_config(r_home, flags, allow_empty=False))) return parser.parse_known_args(res) def get_r_libs(r_home: str, libs: str): assert libs in _R_LIBS parser = argparse.ArgumentParser() parser.add_argument('-I', action='append') parser.add_argument('-L', action='append') parser.add_argument('-l', action='append') res = shlex.split( ' '.join( _get_r_cmd_config(r_home, libs, allow_empty=False))) return parser.parse_known_args(res) class CExtensionOptions(object): """Options to compile C extensions.""" def __init__(self): self.extra_link_args = [] self.extra_compile_args = ['-std=c99'] self.include_dirs = [] self.libraries = [] self.library_dirs = [] def add_include(self, args, unknown): """Add include directories. :param args: args as returned by get_r_flags(). :param unknown: unknown arguments a returned by get_r_flags().""" if args.I is None: warnings.warn('No include specified') else: self.include_dirs.extend(args.I) self.extra_compile_args.extend(unknown) def add_lib(self, args, unknown, ignore=('R', )): """Add libraries. :param args: args as returned by get_r_flags(). :param unknown: unknown arguments a returned by get_r_flags().""" if args.L is None: if args.l is None: # hmmm... no libraries at all warnings.warn('No libraries as -l arguments to the compiler.') else: self.libraries.extend([x for x in args.l if x not in ignore]) else: self.library_dirs.extend(args.L) self.libraries.extend(args.l) self.extra_link_args.extend(unknown) def _make_bold_unix(text): return '%s%s%s' % ('\033[1m', text, '\033[0m') def _make_bold_win32(text): return text def iter_info(): make_bold = _make_bold_win32 if os.name == 'nt' else _make_bold_unix yield make_bold('rpy2 version:') try: # TODO: the repeated import is needed, without which Python # raises an UnboundLocalError (local variable reference before # assignment). import rpy2 # noqa: F811 yield rpy2.__version__ except ImportError: yield 'rpy2 cannot be imported' yield make_bold('Python version:') yield sys.version yield make_bold("Looking for R's HOME:") r_home = os.environ.get('R_HOME') yield ' Environment variable R_HOME: %s' % r_home r_home_default = None if os.name == 'nt': r_home_default = r_home_from_registry() yield ' InstallPath in the registry: %s' % r_home_default r_user = os.environ.get('R_USER') yield ' Environment variable R_USER: %s' % r_user else: try: r_home_default = r_home_from_subprocess() except Exception as e: logger.error(f'Unable to determine R home: {e}') yield ' Calling `R RHOME`: %s' % r_home_default yield ( ' Environment variable R_LIBS_USER: %s' % os.environ.get('R_LIBS_USER') ) if r_home is not None and r_home_default is not None: if os.path.abspath(r_home) != r_home_default: yield (' Warning: The environment variable R_HOME ' 'differs from the default R in the PATH.') else: if r_home_default is None: yield (' Warning: There is no R in the PATH and no ' 'R_HOME defined.') else: r_home = r_home_default # not applicable for Windows if os.name != 'nt': yield make_bold("R's value for LD_LIBRARY_PATH:") if r_home is None: yield ' *** undefined when not R home can be determined' else: yield r_ld_library_path_from_subprocess(r_home) try: import rpy2.rinterface_lib.openrlib rlib_status = 'OK' except ImportError as ie: try: import rpy2 rlib_status = '*** Error while loading: %s ***' % str(ie) except ImportError: rlib_status = '*** rpy2 is not installed' except OSError as ose: rlib_status = str(ose) yield make_bold("R version:") yield ' In the PATH: %s' % r_version_from_subprocess() yield ' Loading R library from rpy2: %s' % rlib_status r_libs = os.environ.get('R_LIBS') yield make_bold('Additional directories to load R packages from:') yield r_libs yield make_bold('C extension compilation:') c_ext = CExtensionOptions() if r_home is None: yield (' Warning: R cannot be found, so no compilation flags ' 'can be extracted.') else: try: c_ext.add_lib(*get_r_flags(r_home, '--ldflags')) c_ext.add_include(*get_r_flags(r_home, '--cppflags')) yield ' include:' yield ' %s' % c_ext.include_dirs yield ' libraries:' yield ' %s' % c_ext.libraries yield ' library_dirs:' yield ' %s' % c_ext.library_dirs yield ' extra_compile_args:' yield ' %s' % c_ext.extra_compile_args yield ' extra_link_args:' yield ' %s' % c_ext.extra_link_args except subprocess.CalledProcessError: yield (' Warning: Unable to get R compilation flags.') yield 'Directory for the R shared library:' yield get_r_libnn(r_home) yield make_bold('CFFI extension type') yield f' Environment variable: {ENVVAR_CFFI_TYPE}' yield f' Value: {get_cffi_mode()}' import importlib for cffi_type in ('abi', 'api'): rinterface_cffi_spec = importlib.util.find_spec(f'_rinterface_cffi_{cffi_type}') yield f' {cffi_type.upper()}: {"PRESENT" if rinterface_cffi_spec else "ABSENT"}' def set_default_logging(): logformatter = logging.Formatter('%(name)s: %(message)s') loghandler = logging.StreamHandler() loghandler.setFormatter(logformatter) logger.addHandler(loghandler) if __name__ == '__main__': parser = argparse.ArgumentParser( 'Command-line tool to report the rpy2' 'environment and help diagnose issues') parser.add_argument('action', nargs='?', choices=('info', 'LD_LIBRARY_PATH'), default='info', help=('Action to perform. "info" shows all info, ' 'LD_LIBRARY_PATH returns optionally required ' 'additions to the environment variable')) parser.add_argument('-v', '--verbose', choices=('ERROR', 'WARNING', 'INFO', 'DEBUG'), default='WARNING', help=('Verbosity level. Options are given by ' 'increasing order of verbosity ' '(defaut: %(default)s)')) args = parser.parse_args() logger.name = 'rpy2.situation' logger.setLevel(getattr(logging, args.verbose)) set_default_logging() if args.action == 'info': for row in iter_info(): print(row) elif args.action == 'LD_LIBRARY_PATH': r_home = get_r_home() if not r_home: print('R cannot be found in the PATH and RHOME cannot be found.') sys.exit(1) print(r_ld_library_path_from_subprocess(r_home)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8619466 rpy2-3.5.15/rpy2/tests/0000755000175100001770000000000014543120757014154 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/__init__.py0000644000175100001770000000000014543120705016244 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8619466 rpy2-3.5.15/rpy2/tests/ipython/0000755000175100001770000000000014543120757015646 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/ipython/__init__.py0000644000175100001770000000000014543120705017736 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/ipython/test_ggplot.py0000644000175100001770000000104214543120705020541 0ustar00runnerdockerimport pytest from rpy2.robjects.packages import PackageNotInstalledError from rpy2.robjects.vectors import DataFrame try: import rpy2.robjects.lib.ggplot2 has_ggplot2 = True msg = '' from rpy2.ipython import ggplot except PackageNotInstalledError as error: has_ggplot2 = False msg = str(error) @pytest.mark.skipif(not has_ggplot2, reason=msg) def test_image_png(): dataf = DataFrame({'x': 1, 'Y': 2}) g = rpy2.robjects.lib.ggplot2.ggplot(dataf) img = ggplot.image_png(g) assert img ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/ipython/test_html.py0000644000175100001770000000251014543120705020212 0ustar00runnerdockerimport pytest import types import warnings from rpy2.robjects import vectors from rpy2.robjects.packages import importr try: import IPython except ModuleNotFoundError as no_ipython: warnings.warn(str(no_ipython)) IPython = None if IPython is None: html = types.SimpleNamespace(html_vector_horizontal=None, html_rlist=None, html_rdataframe=None, html_sourcecode=None, html_ridentifiedobject=None) else: from rpy2.ipython import html base = importr('base') @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.parametrize( 'o,func', [(vectors.IntVector([1, 2, 3]), html.html_vector_horizontal), (vectors.FloatVector([1, 2, 3]), html.html_vector_horizontal), (vectors.StrVector(['a', 'b' 'c']), html.html_vector_horizontal), (vectors.FactorVector(['a', 'b' 'c']), html.html_vector_horizontal), (vectors.ListVector({'a': 1, 'b': 2}), html.html_rlist), (vectors.DataFrame({'a': 1, 'b': 'z'}), html.html_rdataframe), ('x <- c(1, 2, 3)', html.html_sourcecode), (base.c, html.html_ridentifiedobject)]) def test_html_func(o, func): res = func(o) assert isinstance(res, str) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/ipython/test_rmagic.py0000644000175100001770000004114314543120705020515 0ustar00runnerdockerimport pytest import textwrap import types import warnings from itertools import product import rpy2.rinterface_lib.callbacks import rpy2.rinterface_lib._rinterface_capi import rpy2.robjects import rpy2.robjects.conversion from .. import utils # Currently numpy is a testing requirement, but rpy2 should work without numpy try: import numpy as np has_numpy = True import rpy2.robjects.numpy2ri except: has_numpy = False try: import pandas as pd has_pandas = True import rpy2.robjects.pandas2ri except: has_pandas = False try: import IPython except ModuleNotFoundError as no_ipython: warnings.warn(str(no_ipython)) IPython = None if IPython is None: rmagic = None get_ipython = None else: from rpy2.ipython import rmagic from IPython.testing.globalipapp import get_ipython from io import StringIO np_string_type = 'U' # from IPython.core.getipython import get_ipython from rpy2 import rinterface from rpy2.robjects import r, vectors, globalenv import rpy2.robjects.packages as rpacks @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.fixture(scope='module') def clean_globalenv(): yield for name in rinterface.globalenv.keys(): del rinterface.globalenv[name] @pytest.fixture(scope='module') def ipython_with_magic(): if IPython is None: return None ip = get_ipython() # This is just to get a minimally modified version of the changes # working ip.run_line_magic('load_ext', 'rpy2.ipython') return ip @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') def test_RInterpreterError(): line = 123 err = 'Arrh!' stdout = 'Kaput' rie = rmagic.RInterpreterError(line, err, stdout) assert str(rie).startswith(rie.msg_prefix_template % (line, err)) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.parametrize( 'arg,expected', (('foo', ('foo', 'foo')), ('bar=foo', ('bar', 'foo')), ('bar=baz.foo', ('bar', 'baz.foo')) ) ) def test__parse_input_argument(arg, expected): assert expected == rmagic._parse_input_argument(arg) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.skipif(not has_numpy, reason='numpy not installed') def test_push(ipython_with_magic, clean_globalenv): for obj in (np.arange(5), [1,2]): ipython_with_magic.push({'X': obj}) ipython_with_magic.run_line_magic('Rpush', 'X') np.testing.assert_almost_equal(np.asarray(r('X')), ipython_with_magic.user_ns['X']) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.skipif(not has_numpy, reason='numpy not installed') def test_push_localscope(ipython_with_magic, clean_globalenv): """Test that Rpush looks for variables in the local scope first.""" ipython_with_magic.run_cell( textwrap.dedent( """ def rmagic_addone(u): %Rpush u %R result = u+1 %Rpull result return result[0] u = 0 result = rmagic_addone(12344) """) ) result = ipython_with_magic.user_ns['result'] np.testing.assert_equal(result, 12345) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.parametrize('rcode,exception_expr', (('"a" + 1', 'rmagic.RInterpreterError'), ('"a" + ', 'rpy2.rinterface_lib._rinterface_capi.RParsingError'))) def test_run_cell_with_error(ipython_with_magic, clean_globalenv, rcode, exception_expr): """Run an R block with an error.""" with pytest.raises(eval(exception_expr)): ipython_with_magic.run_line_magic('R', rcode) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.skipif(not has_pandas, reason='pandas is not available in python') @pytest.mark.skipif(not has_numpy, reason='numpy not installed') def test_push_dataframe(ipython_with_magic, clean_globalenv): df = pd.DataFrame([{'a': 1, 'b': 'bar'}, {'a': 5, 'b': 'foo', 'c': 20}]) ipython_with_magic.push({'df':df}) ipython_with_magic.run_line_magic('Rpush', 'df') # This is converted to factors, which are currently converted back to Python # as integers, so for now we test its representation in R. sio = StringIO() with utils.obj_in_module(rpy2.rinterface_lib.callbacks, 'consolewrite_print', sio.write): r('print(df$b[1])') assert '[1] "bar"' in sio.getvalue() # Values come packaged in arrays, so we unbox them to test. assert r('df$a[2]')[0] == 5 missing = r('df$c[1]')[0] assert np.isnan(missing), missing @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.skipif(not has_numpy, reason='numpy not installed') def test_pull(ipython_with_magic, clean_globalenv): r('Z=c(11:20)') ipython_with_magic.run_line_magic('Rpull', 'Z') np.testing.assert_almost_equal(np.asarray(r('Z')), ipython_with_magic.user_ns['Z']) np.testing.assert_almost_equal(ipython_with_magic.user_ns['Z'], np.arange(11,21)) def _test_Rconverter(ipython_with_magic, clean_globalenv, dataf_py, cls): # If we get to dropping numpy requirement, we might use something # like the following: # assert tuple(buffer(a).buffer_info()) == tuple(buffer(b).buffer_info()) # store it in the notebook's user namespace ipython_with_magic.user_ns['dataf_py'] = dataf_py # equivalent to: # %Rpush dataf_np # that is send Python object 'dataf_py' into R's globalenv # as 'dataf_r'. The current conversion rules should make it an # R data frame. ipython_with_magic.run_line_magic('Rpush', 'dataf_py') # Now retreive 'dataf_py' from R's globalenv. Twice because # we want to test whether copies are made fromr_dataf_py = ipython_with_magic.run_line_magic( 'Rget', 'dataf_py' ) fromr_dataf_py_again = ipython_with_magic.run_line_magic( 'Rget', 'dataf_py' ) assert isinstance(fromr_dataf_py, cls) assert len(dataf_py) == len(fromr_dataf_py) # retrieve `dataf_py` from R into `fromr_dataf_py` in the notebook. ipython_with_magic.run_cell_magic('R', '-o dataf_py', 'dataf_py') dataf_py_roundtrip = ipython_with_magic.user_ns['dataf_py'] assert tuple(fromr_dataf_py['x']) == tuple(dataf_py_roundtrip['x']) assert tuple(fromr_dataf_py['y']) == tuple(dataf_py_roundtrip['y']) @pytest.mark.skipif(IPython is None, reason='The optional package IPython cannot be imported.') @pytest.mark.skipif(has_pandas or (not has_numpy), reason='numpy not installed') def test_Rconverter_numpy(ipython_with_magic, clean_globalenv): # If we get to dropping numpy requirement, we might use something # like the following: # assert tuple(buffer(a).buffer_info()) == tuple(buffer(b).buffer_info()) # numpy recarray (numpy's version of a data frame) dataf_np= np.array( [(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c')], dtype=[('x', ' 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._WRITECONSOLE_EXCEPTION_LOG % msg)) def testSetResetConsole(): def make_callback(): reset = 0 def f(): nonlocal reset reset += 1 return f f = make_callback() with utils.obj_in_module(callbacks, 'consolereset', f): callbacks._consolereset() assert f.__closure__[0].cell_contents == 1 def test_resetconsole_error(caplog): error_msg = "Doesn't work." def f(): raise Exception(error_msg) with utils.obj_in_module(callbacks, 'consolereset', f),\ caplog.at_level(logging.ERROR, logger='callbacks.logger'): caplog.clear() callbacks._consolereset() assert len(caplog.record_tuples) > 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._RESETCONSOLE_EXCEPTION_LOG % error_msg)) @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_flushconsole(): def make_callback(): count = 0 def f(): nonlocal count count += 1 return f f = make_callback() with utils.obj_in_module(callbacks, 'consoleflush', f): assert f.__closure__[0].cell_contents == 0 rinterface.globalenv.find('flush.console')() assert f.__closure__[0].cell_contents == 1 @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_flushconsole_with_error(caplog): msg = "Doesn't work." def f(): raise Exception(msg) with utils.obj_in_module(callbacks, 'consoleflush', f),\ caplog.at_level(logging.ERROR, logger='callbacks.logger'): caplog.clear() rinterface.globalenv.find('flush.console')() assert len(caplog.record_tuples) > 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._FLUSHCONSOLE_EXCEPTION_LOG % msg)) def test_consoleread(): msg_orig = 'yes' def sayyes(prompt): return msg_orig with utils.obj_in_module(callbacks, 'consoleread', sayyes): prompt = openrlib.ffi.new('char []', b'foo') n = 1000 buf = openrlib.ffi.new('char [%i]' % n) res = callbacks._consoleread(prompt, buf, n, 0) assert res == 1 msg = openrlib.ffi.string(buf).decode('utf-8') assert msg_orig == msg.rstrip() def test_consoleread_empty(): def sayyes(prompt): return '' with utils.obj_in_module(callbacks, 'consoleread', sayyes): prompt = openrlib.ffi.new('char []', b'foo') n = 1000 buf = openrlib.ffi.new('char [%i]' % n) res = callbacks._consoleread(prompt, buf, n, 0) assert res == 0 msg = openrlib.ffi.string(buf).decode('utf-8') assert msg.rstrip() == '' def test_console_read_with_error(caplog): msg = "Doesn't work." def f(prompt): raise Exception(msg) with utils.obj_in_module(callbacks, 'consoleread', f),\ caplog.at_level(logging.ERROR, logger='callbacks.logger'): caplog.clear() prompt = openrlib.ffi.new('char []', b'foo') n = 1000 buf = openrlib.ffi.new('char [%i]' % n) res = callbacks._consoleread(prompt, buf, n, 0) assert res == 0 assert len(caplog.record_tuples) > 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._READCONSOLE_EXCEPTION_LOG % msg)) def test_showmessage_default(capsys): buf = 'foo' callbacks.showmessage(buf) captured = capsys.readouterr() assert captured.out.split('\n')[1] == buf def test_show_message(): def make_callback(): count = 0 def f(message): nonlocal count count += 1 return f f = make_callback() with utils.obj_in_module(callbacks, 'showmessage', f): assert f.__closure__[0].cell_contents == 0 msg = openrlib.ffi.new('char []', b'foo') callbacks._showmessage(msg) assert f.__closure__[0].cell_contents == 1 def test_show_message_with_error(caplog): error_msg = "Doesn't work." def f(message): raise Exception(error_msg) with utils.obj_in_module(callbacks, 'showmessage', f),\ caplog.at_level(logging.ERROR, logger='callbacks.logger'): caplog.clear() msg = openrlib.ffi.new('char []', b'foo') callbacks._showmessage(msg) assert len(caplog.record_tuples) > 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._SHOWMESSAGE_EXCEPTION_LOG % error_msg)) def test_choosefile_default(): inputvalue = 'foo' with utils.obj_in_module(builtins, 'input', lambda x: inputvalue): assert callbacks.choosefile('foo') == inputvalue @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_choosefile(): me = "me" def chooseMe(new): return me with utils.obj_in_module(callbacks, 'choosefile', chooseMe): res = rinterface.baseenv['file.choose']() assert me == res[0] @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_choosefile_error(): def f(prompt): raise Exception("Doesn't work.") with utils.obj_in_module(callbacks, 'consolewrite_print', utils.noconsole): with utils.obj_in_module(callbacks, 'choosefile', f): with pytest.raises(rinterface.embedded.RRuntimeError): with pytest.warns(rinterface.RRuntimeWarning): rinterface.baseenv["file.choose"]() def test_showfiles_default(capsys): with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp.write(b'abc') tmp.close() filenames = (tmp, ) headers = ('', ) wtitle = '' pager = '' captured = capsys.readouterr() callbacks.showfiles(tuple(x.name for x in filenames), headers, wtitle, pager) captured.out.endswith('---') os.unlink(tmp.name) @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_showfiles(): sf = [] def f(filenames, headers, wtitle, pager): sf.append(wtitle) for tf in filenames: sf.append(tf) with utils.obj_in_module(callbacks, 'showfiles', f): file_path = rinterface.baseenv['file.path'] r_home = rinterface.baseenv['R.home'] filename = file_path(r_home(rinterface.StrSexpVector(('doc', ))), rinterface.StrSexpVector(('COPYRIGHTS', ))) rinterface.baseenv['file.show'](filename) assert filename[0] == sf[1] assert 'R Information' == sf[0] @pytest.mark.skipif(os.name == 'nt', reason='Not supported on Windows') def test_showfiles_error(caplog): msg = "Doesn't work." def f(filenames, headers, wtitle, pager): raise Exception(msg) with utils.obj_in_module(callbacks, 'showfiles', f),\ caplog.at_level(logging.ERROR, logger='callbacks.logger'): file_path = rinterface.baseenv['file.path'] r_home = rinterface.baseenv['R.home'] filename = file_path(r_home(rinterface.StrSexpVector(('doc', ))), rinterface.StrSexpVector(('COPYRIGHTS', ))) caplog.clear() rinterface.baseenv['file.show'](filename) assert len(caplog.record_tuples) > 0 for x in caplog.record_tuples: assert x == ('rpy2.rinterface_lib.callbacks', logging.ERROR, (callbacks ._SHOWFILE_EXCEPTION_LOG % msg)) @pytest.mark.skip(reason='WIP (should be run from worker process).') def test_cleanup(): def f(saveact, status, runlast): return None with utils.obj_in_module(callbacks, 'cleanup', f): r_quit = rinterface.baseenv['q'] with pytest.raises(rinterface.embedded.RRuntimeError): r_quit() def test_busy(): busylist = [] def busy(which): busylist.append(which) with utils.obj_in_module(callbacks, 'busy', busy): which = 1 callbacks._busy(which) assert tuple(busylist) == (1,) def test_callback(): callbacklist = [] def callback(): callbacklist.append(1) with utils.obj_in_module(callbacks, 'callback', callback): callbacks._callback() assert tuple(callbacklist) == (1,) def test_yesnocancel(): def yesnocancel(question): return 1 question = openrlib.ffi.new('char []', b'What ?') with utils.obj_in_module(callbacks, 'yesnocancel', yesnocancel): res = callbacks._yesnocancel(question) assert res == 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_conversion.py0000644000175100001770000000044314543120705022066 0ustar00runnerdockerimport pytest from rpy2.rinterface_lib import _rinterface_capi as _rinterface import rpy2.rinterface_lib.conversion def test__int_to_sexp(): with pytest.raises(ValueError): rpy2.rinterface_lib.conversion._int_to_sexp( _rinterface._MAX_INT + 1 ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_embedded_r.py0000644000175100001770000002675414543120705021770 0ustar00runnerdockerimport gc import multiprocessing import os import pickle import pytest from rpy2 import rinterface import rpy2 import rpy2.rinterface_lib._rinterface_capi as _rinterface import signal import sys import subprocess import tempfile import textwrap import time rinterface.initr() def is_AQUA_or_Windows(function): platform = rinterface.baseenv.find('.Platform') names = platform.do_slot('names') platform_gui = names[names.index('GUI')] platform_ostype = names[names.index('OS.type')] if (platform_gui != 'AQUA') and (platform_ostype != 'windows'): return False else: return True class CustomException(Exception): pass def _call_with_ended_r(queue): import rpy2.rinterface_cffi as rinterface rinterface.initr() rdate = rinterface.baseenv['date'] rinterface.endr(0) try: rdate() res = (False, None) except RuntimeError as re: res = (True, re) except Exception as e: res = (False, e) queue.put(res) def _init_r(): from rpy2 import rinterface rinterface.initr() @pytest.mark.skip(reason='Spawned process seems to share ' 'initialization state with parent.') def test_call_error_when_ended_r(): q = multiprocessing.Queue() ctx = multiprocessing.get_context('spawn') p = ctx.Process(target=_call_with_ended_r, args=(q,)) p.start() res = q.get() p.join() assert res[0] # TODO: is this test still making sense ? def test_get_initoptions(): options = rinterface.embedded.get_initoptions() assert len(rinterface.embedded._options) == len(options) for o1, o2 in zip(rinterface.embedded._options, options): assert o1 == o2 def test_set_initoptions_after_init(): with pytest.raises(RuntimeError): rinterface.embedded.set_initoptions(('aa', '--verbose', '--no-save')) def test_initr(): preserve_hash = True args = () if os.name != 'nt': args = (preserve_hash,) proc = multiprocessing.Process(target=_init_r, args=args) proc.start() proc.join() def test_parse_ok(): xp = rinterface.parse('2 + 3') assert xp.typeof == rinterface.RTYPES.EXPRSXP assert 2.0 == xp[0][1][0] assert 3.0 == xp[0][2][0] def test_parse_unicode(): xp = rinterface.parse(u'"\u21a7"') assert len(xp) == 1 assert len(xp[0]) == 1 def test_parse_incomplete_error(): with pytest.raises(_rinterface.RParsingError) as rpe: rinterface.parse("2 + 3 /") assert rpe.value.status == (_rinterface .PARSING_STATUS.PARSE_INCOMPLETE) def test_parse_error(): with pytest.raises(_rinterface.RParsingError): rinterface.parse("2 + 3 , 1") def test_parse_error_when_evaluting(): with pytest.raises(_rinterface.RParsingError): rinterface.parse("list(''=1)") def test_parse_invalid_string(): with pytest.raises(TypeError): rinterface.parse(3) @pytest.mark.parametrize( 'envir', (None, rinterface.globalenv, rinterface.ListSexpVector([]))) def test_evalr(envir): res = rinterface.evalr('1 + 2', envir=envir) assert tuple(res) == (3, ) @pytest.mark.parametrize( 'envir', (None, rinterface.globalenv) ) @pytest.mark.parametrize( 'expr,visibility', (('x <- 1', False), ('1', True)) ) def test_evalr_expr_with_visible(envir, expr, visibility): value, vis = rinterface.evalr_expr_with_visible( rinterface.parse(expr), envir=envir) assert vis[0] == visibility def test_rternalize_decorator(): @rinterface.rternalize def rfun(x, y): return x[0]+y[0] res = rfun(1, 2) assert res[0] == 3 def test_rternalize_decorator_signature(): @rinterface.rternalize(signature=True) def rfun(x, y): return x[0]+y[0] res = rfun(1, 2) assert res[0] == 3 @pytest.mark.parametrize('signature', ((True, ), (False, ))) def test_rternalize(signature): def f(x, y): return x[0]+y[0] rfun = rinterface.rternalize(f, signature=signature) res = rfun(1, 2) assert res[0] == 3 def test_rternalize_return_sexp(): def f(x, y): return rinterface.IntSexpVector([x[0], y[0]]) rfun = rinterface.rternalize(f, signature=False) res = rfun(1, 2) assert tuple(res) == (1, 2) def test_rternalize_namedargs(): def f(x, y, z=None): if z is None: return x[0]+y[0] else: return z[0] rfun = rinterface.rternalize(f, signature=False) res = rfun(1, 2) assert res[0] == 3 res = rfun(1, 2, z=8) assert res[0] == 8 @pytest.mark.parametrize('signature', ((True, ), (False, ))) def test_rternalize_extraargs(signature): def f(): return 1 rfun = rinterface.rternalize(f, signature=signature) assert rfun()[0] == 1 with pytest.raises(rinterface.embedded.RRuntimeError, match=r'unused argument \(1\)'): rfun(1) @pytest.mark.parametrize( 'args', ((), (1,), (1, 2)) ) def test_rternalize_map_ellipsis_args(args): def f(x, *args): return len(args) rfun = rinterface.rternalize(f, signature=True) assert ('x', '...') == tuple(rinterface.baseenv['formals'](rfun).names) assert rfun(0, *args)[0] == len(args) @pytest.mark.parametrize( 'kwargs', ({}, {'y': 1}, {'y': 1, 'z': 2}) ) def test_rternalize_map_ellipsis_kwargs(kwargs): def f(x, **kwargs): return len(kwargs) rfun = rinterface.rternalize(f, signature=True) assert ('x', '...') == tuple(rinterface.baseenv['formals'](rfun).names) assert rfun(0, **kwargs)[0] == len(kwargs) def test_rternalize_map_ellipsis_args_kwargs_error(): def f(x, *args, y = 2, **kwargs): pass with pytest.raises(ValueError): rfun = rinterface.rternalize(f, signature=True) def test_rternalize_formals(): def f(a, /, b, c=1, *, d=2, e): return 1 rfun = rinterface.rternalize(f, signature=True) rnames = rinterface.baseenv['names'] rformals = rinterface.baseenv['formals'] rpaste = rinterface.baseenv['paste'] assert list(rnames(rformals(rfun))) == ['a', 'b', 'c', 'd', 'e'] def test_external_python(): def f(x): return 3 rpy_fun = rinterface.SexpExtPtr.from_pyobject(f) _python = rinterface.StrSexpVector(('.Python', )) res = rinterface.baseenv['.External'](_python, rpy_fun, 1) assert len(res) == 1 assert res[0] == 3 # TODO: what is this ? def testExternalPythonFromExpression(): xp_name = rinterface.StrSexpVector(('expression',)) xp = rinterface.baseenv['vector'](xp_name, 3) @pytest.mark.parametrize( 'rcode', ('while(TRUE) {}', """ i <- 0; while(TRUE) { i <- i+1; Sys.sleep(0.01); } """) ) def test_interrupt_r(rcode): expected_code = 42 # this is an arbitrary exit code that we check for below with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as rpy_code: rpy2_path = os.path.dirname(rpy2.__path__[0]) rpy_code_str = textwrap.dedent(""" import sys sys.path.insert(0, '%s') import rpy2.rinterface as ri from rpy2.rinterface_lib import callbacks from rpy2.rinterface_lib import embedded ri.initr() def f(x): # This flush is important to make sure we avoid a deadlock. print(x, flush=True) rcode = ''' message('executing-rcode') console.flush() %s ''' with callbacks.obj_in_module(callbacks, 'consolewrite_print', f): try: ri.baseenv['eval'](ri.parse(rcode)) except embedded.RRuntimeError: sys.exit(%d) """) % (rpy2_path, rcode, expected_code) rpy_code.write(rpy_code_str) cmd = (sys.executable, rpy_code.name) with open(os.devnull, 'w') as fnull: creationflags = 0 if os.name == 'nt': creationflags = subprocess.CREATE_NEW_PROCESS_GROUP # This context manager ensures that appropriate cleanup happens for the # process and for stdout. with subprocess.Popen(cmd, # Since we are reading from stdout, it's important to ensure we work # well with buffering. We make sure to flush when printing, but a viable # alternative is starting the Python process as unbuffered using `-u`. # If we weren't explicitly flushing then buffering could result in a # deadlock where the parent waits for the message from the child, but the # child is in an infinite loop that will only terminate if interrupted by # the parent. stdout=subprocess.PIPE, stderr=fnull, creationflags=creationflags) as child_proc: # We wait for the child process to send a message signalling that # R code is being executed since we want to ensure that we only send # the signal at that point. If we send it while Python is executing, # we would instead get a KeyboardInterrupt. for line in child_proc.stdout: if line == b'executing-rcode\n': break sigint = signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT child_proc.send_signal(sigint) # Wait for the process to terminate. Timeout ensures we don't wait indefinitely. ret_code = child_proc.wait(timeout=10) # This test checks for a specific exit code to ensure that the above code # block exited correctly. This is important to distinguish our expected # process interruption from other errors the test might encounter. assert ret_code == expected_code # TODO: still needed ? def test_rpy_memory(): x = rinterface.IntSexpVector(range(10)) x_rid = x.rid assert x_rid in set(z[0] for z in rinterface._rinterface.protected_rids()) del(x) # TODO: Why calling collect() twice ? gc.collect() gc.collect() s = set(z[0] for z in rinterface._rinterface.protected_rids()) assert x_rid not in s def test_object_dispatch_lang(): formula = rinterface.globalenv.find('formula') obj = formula(rinterface.StrSexpVector(['y ~ x', ])) assert isinstance(obj, rinterface.SexpVector) assert obj.typeof == rinterface.RTYPES.LANGSXP def test_object_dispatch_vector(): robj = rinterface.globalenv.find('letters') assert isinstance(robj, rinterface.SexpVector) def test_object_dispatch_closure(): robj = rinterface.globalenv.find('sum') assert isinstance(robj, rinterface.SexpClosure) def test_object_dispatch_rawvector(): rawfunc = rinterface.baseenv.find('raw') rawvec = rawfunc(rinterface.IntSexpVector((10, ))) assert rinterface.RTYPES.RAWSXP == rawvec.typeof def test_unserialize(): x = rinterface.IntSexpVector([1, 2, 3]) x_serialized = x.__getstate__() x_again = rinterface.Sexp( rinterface.unserialize(x_serialized)) identical = rinterface.baseenv['identical'] assert not x.rsame(x_again) assert identical(x, x_again)[0] def test_pickle(): x = rinterface.IntSexpVector([1, 2, 3]) with tempfile.NamedTemporaryFile() as f: pickle.dump(x, f) f.flush() f.seek(0) x_again = pickle.load(f) identical = rinterface.baseenv['identical'] assert identical(x, x_again)[0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_endr.py0000644000175100001770000000064614543120705020636 0ustar00runnerdockerimport pytest from rpy2 import rinterface from rpy2.rinterface import embedded @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='This test should be run independently of other tests.') def test_endr(): rinterface.initr() embedded.endr(1) assert (embedded.rpy2_embeddedR_isinitialized & embedded.RPY_R_Status.ENDED.value) == embedded.RPY_R_Status.ENDED.value ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_environment.py0000644000175100001770000001347014543120705022251 0ustar00runnerdocker# coding: utf-8 import pytest from .. import utils import rpy2.rinterface as rinterface rinterface.initr() def _just_pass(x): pass @pytest.fixture(scope='module') def silent_console_print(): with utils.obj_in_module(rinterface.callbacks, 'consolewrite_print', _just_pass): yield def test_new(): sexp = rinterface.globalenv sexp_new = rinterface.SexpEnvironment(sexp) assert sexp.rsame(sexp_new) sexp_new2 = rinterface.Sexp(sexp) assert sexp.rsame(sexp_new2) del(sexp) assert sexp_new.rsame(sexp_new2) with pytest.raises(ValueError): rinterface.SexpEnvironment('2') # TODO: the next few tests should be moved to testing the # cdata -> rinterface-object mapper def test_globalenv(): assert isinstance(rinterface.globalenv, rinterface.SexpEnvironment) def test_getitem(): with pytest.raises(KeyError): rinterface.globalenv['help'] assert isinstance(rinterface.globalenv.find('help'), rinterface.Sexp) def test_getitem_invalid(): env = rinterface.baseenv["new.env"]() with pytest.raises(TypeError): env[None] with pytest.raises(ValueError): env[''] def test_setitem_invalid(): env = rinterface.baseenv["new.env"]() with pytest.raises(TypeError): env[None] = 0 with pytest.raises(ValueError): env[''] = 0 def test_setitem_baseenv_invalid(): with pytest.raises(ValueError): rinterface.baseenv['pi'] = 42 def test_frame(): env = rinterface.baseenv["new.env"]() f = env.frame() # Outside of an R call stack a frame will be NULL, # or so I understand. assert f is rinterface.NULL def test_find_invalid_notstring(): with pytest.raises(TypeError): rinterface.globalenv.find(None) def test_find_invalid_empty(): with pytest.raises(ValueError): rinterface.globalenv.find('') def test_find_invalid_notfound(): with pytest.raises(KeyError): rinterface.globalenv.find('asdf') def test_find_closure(): help_R = rinterface.globalenv.find('help') assert isinstance(help_R, rinterface.SexpClosure) def test_find_vector(): pi_R = rinterface.globalenv.find('pi') assert isinstance(pi_R, rinterface.SexpVector) def test_find_environment(): ge_R = rinterface.globalenv.find(".GlobalEnv") assert isinstance(ge_R, rinterface.SexpEnvironment) def test_find_onlyfromloadedlibrary(): with pytest.raises(KeyError): rinterface.globalenv.find('survfit') try: rinterface.evalr('library("survival")') sfit_R = rinterface.globalenv.find('survfit') assert isinstance(sfit_R, rinterface.SexpClosure) finally: rinterface.evalr('detach("package:survival")') def test_find_functiononly_keyerror(): # now with the function-only option with pytest.raises(KeyError): rinterface.globalenv.find('pi', wantfun=True) def test_find_functiononly(): hist = rinterface.globalenv.find('hist', wantfun=False) assert rinterface.RTYPES.CLOSXP == hist.typeof rinterface.globalenv['hist'] = rinterface.StrSexpVector(['foo', ]) with pytest.raises(KeyError): rinterface.globalenv.find('hist', wantfun=True) # TODO: isn't this already tested elsewhere ? def test_subscript_emptystring(): ge = rinterface.globalenv with pytest.raises(ValueError): ge[''] def test_subscript(): ge = rinterface.globalenv obj = ge.find('letters') ge['a'] = obj a = ge['a'] assert ge.find('identical')(obj, a) def test_subscript_utf8(): env = rinterface.baseenv['new.env']() env['呵呵'] = rinterface.IntSexpVector((1,)) assert len(env) == 1 assert len(env['呵呵']) == 1 assert env['呵呵'][0] == 1 def test_subscript_missing_utf8(): env = rinterface.baseenv['new.env']() with pytest.raises(KeyError),\ pytest.warns(rinterface.RRuntimeWarning): env['呵呵'] def test_length(): new_env = rinterface.globalenv.find('new.env') env = new_env() assert len(env) == 0 env['a'] = rinterface.IntSexpVector([123, ]) assert len(env) == 1 env['b'] = rinterface.IntSexpVector([123, ]) assert len(env) == 2 def test_iter(): new_env = rinterface.globalenv.find('new.env') env = new_env() env['a'] = rinterface.IntSexpVector([123, ]) env['b'] = rinterface.IntSexpVector([456, ]) symbols = [x for x in env] assert len(symbols) == 2 for s in ['a', 'b']: assert s in symbols def test_keys(): new_env = rinterface.globalenv.find('new.env') env = new_env() env['a'] = rinterface.IntSexpVector([123, ]) env['b'] = rinterface.IntSexpVector([456, ]) symbols = tuple(env.keys()) assert len(symbols) == 2 for s in ['a', 'b']: assert s in symbols def test_del(): env = rinterface.globalenv.find("new.env")() env['a'] = rinterface.IntSexpVector([123, ]) env['b'] = rinterface.IntSexpVector([456, ]) assert len(env) == 2 del(env['a']) assert len(env) == 1 assert 'b' in env def test_del_keyerror(): with pytest.raises(KeyError): rinterface.globalenv.__delitem__('foo') def test_del_baseerror(): with pytest.raises(ValueError): rinterface.baseenv.__delitem__('letters') def test_enclos_get(): assert isinstance(rinterface.baseenv.enclos, rinterface.SexpEnvironment) env = rinterface.baseenv["new.env"]() assert isinstance(env.enclos, rinterface.SexpEnvironment) def test_enclos_baseenv_set(): env = rinterface.baseenv["new.env"]() orig_enclosing_env = rinterface.baseenv.enclos enclosing_env = rinterface.baseenv["new.env"]() env.enclos = enclosing_env assert isinstance(env.enclos, rinterface.SexpEnvironment) assert enclosing_env != env.enclos def test_enclos_baseenv_set_invalid(): with pytest.raises(AssertionError): rinterface.baseenv.enclos = 123 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_externalptr.py0000644000175100001770000000301014543120705022242 0ustar00runnerdockerimport pytest from .. import utils import rpy2.rinterface as rinterface rinterface.initr() def _just_pass(x): pass @pytest.fixture(scope='module') def silent_console_print(): with utils.obj_in_module(rinterface.callbacks, 'consolewrite_print', _just_pass): yield def test_from_pyobject(): pyobject = 'ahaha' sexp_new = rinterface.SexpExtPtr.from_pyobject(pyobject) # R External pointers are never copied. assert rinterface.RTYPES.EXTPTRSXP == sexp_new.typeof def test_from_pyobject_new_tag(): pyobject = 'ahaha' sexp_new = (rinterface.SexpExtPtr .from_pyobject(pyobject, tag='b')) assert sexp_new.typeof == rinterface.RTYPES.EXTPTRSXP assert sexp_new.TYPE_TAG == 'b' def test_from_pyobject_invalid_tag(): pyobject = 'ahaha' with pytest.raises(TypeError): rinterface.SexpExtPtr.from_pyobject(pyobject, tag=True) @pytest.mark.skip(reason='WIP') def test_from_pyobject_protected(): pyobject = 'ahaha' sexp_new = (rinterface.SexpExtPtr .from_pyobject(pyobject, protected=rinterface.StrSexpVector("c"))) assert sexp_new.typeof == rinterface.RTYPES.EXTPTRSXP assert sexp_new.__protected__[0] == 'c' @pytest.mark.skip(reason='WIP') def test_from_pyobject_invalid_protected(): pyobject = 'ahaha' with pytest.raises(TypeError): rinterface.SexpExtPtr.from_pyobject(pyobject, protected=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_functions.py0000644000175100001770000001256114543120705021715 0ustar00runnerdocker# coding: utf-8 import pytest import rpy2.rinterface as rinterface import rpy2.rlike.container as rlc rinterface.initr() def _noconsole(x): pass @pytest.fixture(scope='module') def silent_consolewrite(): module = rinterface.callbacks name = 'consolewrite_print' backup_func = getattr(module, name) setattr(module, name, _noconsole) try: yield finally: setattr(module, name, backup_func) def test_new(): x = 'a' with pytest.raises(ValueError): rinterface.SexpClosure(x) def test_typeof(): sexp = rinterface.globalenv.find('plot') assert sexp.typeof == rinterface.RTYPES.CLOSXP def test_r_error(): r_sum = rinterface.baseenv['sum'] letters = rinterface.baseenv['letters'] with pytest.raises(rinterface.embedded.RRuntimeError),\ pytest.warns(rinterface.RRuntimeWarning): r_sum(letters) def test_string_argument(): r_nchar = rinterface.baseenv['::']('base', 'nchar') res = r_nchar('foo') assert res[0] == 3 def test_utf8_argument_name(): c = rinterface.globalenv.find('c') d = dict([(u'哈哈', 1)]) res = c(**d) assert u'哈哈' == res.do_slot('names')[0] def test_emptystringparams(): d = dict([('', 1)]) with pytest.raises(ValueError): rinterface.baseenv['list'](**d) def test_closureenv_isenv(): exp = rinterface.parse('function() { }') fun = rinterface.baseenv['eval'](exp) assert isinstance(fun.closureenv, rinterface.SexpEnvironment) def test_closureenv(): assert 'y' not in rinterface.globalenv exp = rinterface.parse('function(x) { x[y] }') fun = rinterface.baseenv['eval'](exp) vec = rinterface.baseenv['letters'] assert isinstance(fun.closureenv, rinterface.SexpEnvironment) with pytest.raises(rinterface.embedded.RRuntimeError): with pytest.warns(rinterface.RRuntimeWarning): fun(vec) fun.closureenv['y'] = (rinterface .IntSexpVector([1, ])) assert 'a' == fun(vec)[0] fun.closureenv['y'] = (rinterface .IntSexpVector([2, ])) assert 'b' == fun(vec)[0] def test_call_s4_setClass(): # R's package "methods" can perform uncommon operations r_setClass = rinterface.globalenv.find('setClass') r_representation = rinterface.globalenv.find('representation') attrnumeric = rinterface.StrSexpVector(['numeric', ]) classname = rinterface.StrSexpVector(['Track', ]) classrepr = r_representation(x=attrnumeric, y=attrnumeric) r_setClass(classname, classrepr) # TODO: where is the test ? def test_call_OrdDict(): ad = rlc.OrdDict((('a', rinterface.IntSexpVector([2, ])), ('b', rinterface.IntSexpVector([1, ])), (None, rinterface.IntSexpVector([5, ])), ('c', rinterface.IntSexpVector([0, ])))) mylist = rinterface.baseenv['list'].rcall(tuple(ad.items()), rinterface.globalenv) names = [x for x in mylist.do_slot('names')] for i in range(4): assert ('a', 'b', '', 'c')[i] == names[i] def test_call_OrdDictEnv(): ad = rlc.OrdDict(((None, rinterface.parse('sum(x)')), )) env_a = rinterface.baseenv['new.env']() env_a['x'] = rinterface.IntSexpVector([1, 2, 3]) sum_a = rinterface.baseenv['eval'].rcall(tuple(ad.items()), env_a) assert 6 == sum_a[0] env_b = rinterface.baseenv['new.env']() env_b['x'] = rinterface.IntSexpVector([4, 5, 6]) sum_b = rinterface.baseenv['eval'].rcall(tuple(ad.items()), env_b) assert 15 == sum_b[0] def test_error_in_call(): r_sum = rinterface.baseenv['sum'] with pytest.raises(rinterface.embedded.RRuntimeError),\ pytest.warns(rinterface.RRuntimeWarning): r_sum(2, 'a') def test_missing_arg(): exp = rinterface.parse('function(x) { missing(x) }') fun = rinterface.baseenv['eval'](exp) nonmissing = rinterface.IntSexpVector([0, ]) missing = rinterface.MissingArg assert not fun(nonmissing)[0] assert fun(missing)[0] def test_scalar_convert_integer(): assert 'integer' == rinterface.baseenv['typeof'](int(1))[0] def test_scalar_convert_double(): assert 'double' == rinterface.baseenv['typeof'](1.0)[0] def test_scalar_convert_boolean(): assert 'logical' == rinterface.baseenv['typeof'](True)[0] @pytest.mark.parametrize('give_env', (True, False)) @pytest.mark.parametrize('use_rlock', (True, False)) def test_call_in_context(give_env, use_rlock): ls = rinterface.baseenv['ls'] get = rinterface.baseenv['get'] if give_env: env = rinterface.baseenv['new.env']() else: env = None assert 'foo' not in ls() with rinterface.local_context(env=env, use_rlock=use_rlock) as lc: lc['foo'] = 123 assert tuple(get('foo')) == (123, ) assert 'foo' not in ls() @pytest.mark.parametrize('use_rlock', (True, False)) def test_call_in_context_nested(use_rlock): ls = rinterface.baseenv['ls'] get = rinterface.baseenv['get'] assert 'foo' not in ls() with rinterface.local_context() as lc_a: lc_a['foo'] = 123 assert tuple(get('foo')) == (123, ) with rinterface.local_context(use_rlock=use_rlock) as lc_b: lc_b['foo'] = 456 assert tuple(get('foo')) == (456, ) assert tuple(get('foo')) == (123, ) assert 'foo' not in ls() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_memorymanagement.py0000644000175100001770000000173614543120705023254 0ustar00runnerdockerimport pytest from rpy2 import rinterface from rpy2.rinterface import memorymanagement rinterface.initr() def test_rmemory_manager(): with memorymanagement.rmemory() as rmemory: assert rmemory.count == 0 foo = rmemory.protect(rinterface.conversion._str_to_charsxp('foo')) assert rmemory.count == 1 del(foo) assert rmemory.count == 0 def test_rmemory_manager_unprotect(): with memorymanagement.rmemory() as rmemory: assert rmemory.count == 0 foo = rmemory.protect(rinterface.conversion._str_to_charsxp('foo')) with pytest.raises(ValueError): rmemory.unprotect(2) rmemory.unprotect(1) assert rmemory.count == 0 del(foo) assert rmemory.count == 0 def test_rmemory_manager_unprotect_invalid(): with memorymanagement.rmemory() as rmemory: assert rmemory.count == 0 with pytest.raises(ValueError): rmemory.unprotect(2) assert rmemory.count == 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_na.py0000644000175100001770000000655714543120705020313 0ustar00runnerdockerimport pytest import math import rpy2.rinterface as ri ri.initr() def test_r_to_NAInteger(): na_int = ri.NA_Integer r_na_int = ri.evalr("NA_integer_")[0] assert r_na_int is na_int def test_NAInteger_repr(): na = ri.NA_Integer assert repr(na) == 'NA_integer_' def test_NAInteger_str(): na = ri.NA_Integer assert str(na) == 'NA_integer_' def test_NAInteger_to_r(): na_int = ri.NA_Integer assert ri.baseenv["is.na"](na_int)[0] def test_bool_NAInteger(): with pytest.raises(ValueError): bool(ri.NA_Integer) @pytest.mark.skip( reason="Python changed the behavior for int-inheriting objects.") def test_NAInteger_binaryfunc(): na_int = ri.NAInteger assert (na_int + 2) is na_int def test_NAInteger_in_vector(): na_int = ri.NA_Integer x = ri.IntSexpVector((1, na_int, 2)) assert x[1] is na_int assert x[0] == 1 assert x[2] == 2 def test_R_to_NALogical(): r_na_lgl = ri.evalr('NA')[0] assert r_na_lgl is ri.NA def test_NALogical_repr(): na = ri.NA_Logical assert repr(na) == 'NA' def test_NALogical_str(): na = ri.NA_Logical assert str(na) == 'NA' def test_bool_NALogical(): with pytest.raises(ValueError): bool(ri.NA) def test_NALogical_to_r(): na_lgl = ri.NA_Logical assert ri.baseenv["is.na"](na_lgl)[0] is True def test_NALogical_in_vector(): na_bool = ri.NA_Logical x = ri.BoolSexpVector((True, na_bool, False)) assert x[0] is True assert x[1] is ri.NA_Logical assert x[2] is False def test_R_to_NAReal(): r_na_real = ri.evalr('NA_real_')[0] assert math.isnan(r_na_real) def test_NAReal_to_r(): na_real = ri.NA_Real assert ri.baseenv["is.na"](na_real)[0] def test_bool_NAReal(): with pytest.raises(ValueError): bool(ri.NA_Real) def test_NAReal_binaryfunc(): na_real = ri.NA_Real assert math.isnan(na_real + 2.0) def test_NAReal_in_vector(): na_float = ri.NA_Real x = ri.FloatSexpVector((1.1, na_float, 2.2)) assert math.isnan(x[1]) assert x[0] == 1.1 assert x[2] == 2.2 def test_NAReal_repr(): na_float = ri.NA_Real assert repr(na_float) == 'NA_real_' def test_NAReal_str(): na_float = ri.NA_Real assert str(na_float) == 'NA_real_' def test_r_to_NACharacter(): na_character = ri.NA_Character r_na_character = ri.evalr("NA_character_") assert r_na_character.typeof == ri.RTYPES.STRSXP assert len(r_na_character) == 1 assert r_na_character.get_charsxp(0).rid == na_character.rid def test_NACharacter_repr(): na = ri.NA_Character assert repr(na) == 'NA_character_' def test_NACharacter_str(): na = ri.NA_Character assert str(na) == 'NA_character_' def test_NACharacter_to_r(): na_character = ri.NA_Character assert ri.baseenv["is.na"](ri.StrSexpVector((na_character, )))[0] def test_NACharacter_in_vector(): na_str = ri.NA_Character x = ri.StrSexpVector(("ab", na_str, "cd")) assert x[0] == 'ab' assert x.get_charsxp(1).rid == na_str.rid assert x[2] == 'cd' def test_R_to_NAComplex(): r_na_complex = ri.evalr('NA_complex_')[0] assert math.isnan(r_na_complex.real) assert math.isnan(r_na_complex.imag) def test_NAComplex_to_r(): na_complex = ri.NA_Complex assert ri.baseenv["is.na"](na_complex)[0] def test_bool_NAComplex(): with pytest.raises(ValueError): bool(ri.NA_Complex) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_noinitialization.py0000644000175100001770000000244414543120705023270 0ustar00runnerdockerimport pytest from rpy2 import rinterface from rpy2.rinterface import embedded from rpy2.rinterface_lib import sexp @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_set_initoptions(): options = ('--foo', '--bar') default_options = embedded.get_initoptions() assert default_options != options try: embedded.set_initoptions(options) assert embedded.get_initoptions() == options finally: embedded.set_initoptions(default_options) @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_assert_isready(): with pytest.raises(embedded.RNotReadyError): embedded.assert_isready() @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_assert_environment_geitem(): with pytest.raises(embedded.RNotReadyError): sexp.globalenv['x'] @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_assert_rternalize(): with pytest.raises(embedded.RNotReadyError): rinterface.rternalize(lambda x: x) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_openrlib.py0000644000175100001770000000342514543120705021516 0ustar00runnerdockerimport pytest from rpy2.rinterface_lib import openrlib import rpy2.rinterface def test_dlopen_invalid(): with pytest.raises(ValueError): openrlib._dlopen_rlib(None) def test_get_dataptr_fallback(): with pytest.raises(NotImplementedError): openrlib._get_dataptr_fallback(None) def test_get_symbol_or_fallback(): func = openrlib._get_symbol_or_fallback('thereisnosuchsymbol', lambda x: 'fallback') assert func(None) == 'fallback' @pytest.mark.parametrize( 'rcls,value,func,fallback', ((rpy2.rinterface.IntSexpVector, [1, 2, 3], openrlib.INTEGER_ELT, openrlib._get_integer_elt_fallback), (rpy2.rinterface.BoolSexpVector, [True, True, False], openrlib.LOGICAL_ELT, openrlib._get_logical_elt_fallback), (rpy2.rinterface.FloatSexpVector, [1.1, 2.2, 3.3], openrlib.REAL_ELT, openrlib._get_real_elt_fallback) ) ) def test_get_vec_elt_fallback(rcls, value, func, fallback): rpy2.rinterface.initr() v = rcls(value) assert (func(v.__sexp__._cdata, 1) == fallback(v.__sexp__._cdata, 1)) @pytest.mark.parametrize( 'rcls,value,func,getter', ((rpy2.rinterface.IntSexpVector, [1, 2, 3], openrlib._set_integer_elt_fallback, openrlib._get_integer_elt_fallback), (rpy2.rinterface.BoolSexpVector, [True, True, False], openrlib._set_logical_elt_fallback, openrlib._get_logical_elt_fallback), (rpy2.rinterface.FloatSexpVector, [1.1, 2.2, 3.3], openrlib._set_real_elt_fallback, openrlib._get_real_elt_fallback) ) ) def test_set_vec_elt_fallback(rcls, value, func, getter): rpy2.rinterface.initr() v = rcls(value) func(v.__sexp__._cdata, 1, value[2]) assert getter(v.__sexp__._cdata, 1) == value[2] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_sexp.py0000644000175100001770000001711514543120705020664 0ustar00runnerdockerimport copy import gc import pytest import rpy2.rinterface as rinterface rinterface.initr() def test_invalid_init(): with pytest.raises(ValueError): rinterface.Sexp('a') def test_init_from_existing(): sexp = rinterface.baseenv.find('letters') sexp_new = rinterface.Sexp(sexp) assert sexp_new._sexpobject is sexp._sexpobject def test_typeof(): assert isinstance(rinterface.baseenv.typeof, int) def test_get(): sexp = rinterface.baseenv.find('letters') assert sexp.typeof == rinterface.RTYPES.STRSXP sexp = rinterface.baseenv.find('pi') assert sexp.typeof == rinterface.RTYPES.REALSXP sexp = rinterface.baseenv.find('options') assert sexp.typeof == rinterface.RTYPES.CLOSXP @pytest.mark.parametrize('cls', (rinterface.IntSexpVector, rinterface.ListSexpVector)) def test_list_attrs(cls): x = cls((1, 2, 3)) assert len(x.list_attrs()) == 0 x.do_slot_assign('a', rinterface.IntSexpVector((33, ))) assert len(x.list_attrs()) == 1 assert 'a' in x.list_attrs() def test_do_slot(): sexp = rinterface.baseenv.find('.Platform') names = sexp.do_slot('names') assert len(names) > 1 assert 'OS.type' in names def test_names(): sexp = rinterface.baseenv.find('.Platform') names = sexp.names assert len(names) > 1 assert 'OS.type' in names def test_names_set(): sexp = rinterface.IntSexpVector([1, 2, 3]) assert sexp.names.rid == rinterface.NULL.rid sexp.names = rinterface.StrSexpVector(['a', 'b', 'c']) assert len(sexp.names) > 1 assert tuple(sexp.names) == ('a', 'b', 'c') def test_names_set_invalid(): sexp = rinterface.IntSexpVector([1, 2, 3]) assert sexp.names.rid == rinterface.NULL.rid with pytest.raises(ValueError): sexp.names = ('a', 'b', 'c') def test_do_slot_missing(): sexp = rinterface.baseenv.find('pi') with pytest.raises(LookupError): sexp.do_slot('foo') def test_do_slot_not_string(): sexp = rinterface.baseenv.find('pi') with pytest.raises(ValueError): sexp.do_slot(None) def test_do_slot_empty_string(): sexp = rinterface.baseenv.find('pi') with pytest.raises(ValueError): sexp.do_slot('') def test_do_slot_assign_create(): sexp = rinterface.IntSexpVector([]) slot_value = rinterface.IntSexpVector([3, ]) sexp.do_slot_assign('foo', slot_value) slot_value_back = sexp.do_slot('foo') assert len(slot_value_back) == len(slot_value) assert all(x == y for x, y in zip(slot_value, slot_value_back)) def test_do_slot_reassign(): sexp = rinterface.IntSexpVector([]) slot_value_a = rinterface.IntSexpVector([3, ]) sexp.do_slot_assign('foo', slot_value_a) slot_value_b = rinterface.IntSexpVector([5, 6]) sexp.do_slot_assign('foo', slot_value_b) slot_value_back = sexp.do_slot('foo') assert len(slot_value_b) == len(slot_value_back) assert all(x == y for x, y in zip(slot_value_b, slot_value_back)) def test_do_slot_assign_empty_string(): sexp = rinterface.IntSexpVector([]) slot_value = rinterface.IntSexpVector([3, ]) with pytest.raises(ValueError): sexp.do_slot_assign('', slot_value) def test_sexp_rsame_true(): sexp_a = rinterface.baseenv.find("letters") sexp_b = rinterface.baseenv.find("letters") assert sexp_a.rsame(sexp_b) def test_sexp_rsame_false(): sexp_a = rinterface.baseenv.find("letters") sexp_b = rinterface.baseenv.find("pi") assert not sexp_a.rsame(sexp_b) def test_sexp_rsame_invalid(): sexp_a = rinterface.baseenv.find("letters") with pytest.raises(ValueError): sexp_a.rsame('foo') def test___sexp__(): sexp = rinterface.IntSexpVector([1, 2, 3]) sexp_count = sexp.__sexp_refcount__ sexp_cobj = sexp.__sexp__ d = dict(rinterface._rinterface.protected_rids()) assert sexp_count == d[sexp.rid] assert sexp_count == sexp.__sexp_refcount__ sexp2 = rinterface.IntSexpVector([4, 5, 6, 7]) sexp2_rid = sexp2.rid sexp2.__sexp__ = sexp_cobj del(sexp) gc.collect() d = dict(rinterface._rinterface.protected_rids()) assert d.get(sexp2_rid) is None def test_rclass_get(): sexp = rinterface.baseenv.find('character')(1) assert len(sexp.rclass) == 1 assert sexp.rclass[0] == 'character' sexp = rinterface.baseenv.find('matrix')(0) if rinterface.evalr('R.version$major')[0] >= '4': assert tuple(sexp.rclass) == ('matrix', 'array') else: assert tuple(sexp.rclass) == ('matrix', ) sexp = rinterface.baseenv.find('array')(0) assert len(sexp.rclass) == 1 assert sexp.rclass[0] == 'array' sexp = rinterface.baseenv.find('new.env')() assert len(sexp.rclass) == 1 assert sexp.rclass[0] == 'environment' def test_rclass_get_sym(): # issue #749 fit = rinterface.evalr(""" stats::lm(y ~ x, data=base::data.frame(y=1:10, x=2:11)) """) assert tuple(fit[9].rclass) == ('call', ) def test_rclass_set(): sexp = rinterface.IntSexpVector([1, 2, 3]) sexp.rclass = rinterface.StrSexpVector(['foo']) assert len(sexp.rclass) == 1 assert sexp.rclass[0] == 'foo' sexp.rclass = 'bar' assert len(sexp.rclass) == 1 assert sexp.rclass[0] == 'bar' def test_rclass_set_invalid(): sexp = rinterface.IntSexpVector([1, 2, 3]) with pytest.raises(TypeError): sexp.rclass = rinterface.StrSexpVector(123) def test__sexp__wrongtypeof(): sexp = rinterface.IntSexpVector([1, 2, 3]) cobj = sexp.__sexp__ sexp = rinterface.StrSexpVector(['a', 'b']) assert len(sexp) == 2 with pytest.raises(ValueError): sexp.__sexp__ = cobj def test__sexp__set(): x = rinterface.IntSexpVector([1, 2, 3]) x_s = x.__sexp__ x_rid = x.rid # The Python reference count of the capsule is incremented, # not the rpy2 reference count assert x.__sexp_refcount__ == 1 y = rinterface.IntSexpVector([4, 5, 6]) y_count = y.__sexp_refcount__ y_rid = y.rid assert y_count == 1 assert x_rid in [elt[0] for elt in rinterface._rinterface.protected_rids()] x.__sexp__ = y.__sexp__ # x_s is still holding a refcount to the capsule assert x_rid in [elt[0] for elt in rinterface._rinterface.protected_rids()] # when gone, the capsule will be collected and the id no longer preserved del(x_s) assert x_rid not in [elt[0] for elt in rinterface._rinterface.protected_rids()] assert x.rid == y.rid assert y_rid == y.rid @pytest.mark.xfail(reason='WIP') def test_deepcopy(): sexp = rinterface.IntSexpVector([1, 2, 3]) assert sexp.named == 0 rinterface.baseenv.find("identity")(sexp) assert sexp.named >= 2 sexp2 = sexp.__deepcopy__() assert sexp.typeof == sexp2.typeof assert list(sexp) == list(sexp2) assert not sexp.rsame(sexp2) assert sexp2.named == 0 # should be the same as above, but just in case: sexp3 = copy.deepcopy(sexp) assert sexp.typeof == sexp3.typeof assert list(sexp) == list(sexp3) assert not sexp.rsame(sexp3) assert sexp3.named == 0 def test_rid(): globalenv_id = rinterface.baseenv.find('.GlobalEnv').rid assert globalenv_id == rinterface.globalenv.rid def test_NULL_nonzero(): assert not rinterface.NULL def test_charsxp_encoding(): encoding = rinterface.NA_Character.encoding assert encoding == rinterface.sexp.CETYPE.CE_NATIVE def test_charsxp_nchar(): v = rinterface.StrSexpVector(['abc', 'de', '']) cs = v.get_charsxp(0) assert cs.nchar() == 3 cs = v.get_charsxp(1) assert cs.nchar() == 2 cs = v.get_charsxp(2) assert cs.nchar() == 0 def test_missingtype(): assert not rinterface.MissingArg ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_symbol.py0000644000175100001770000000103614543120705021205 0ustar00runnerdockerimport pytest import rpy2.rinterface as rinterface rinterface.initr() def test_new_invalid(): x = 1 with pytest.raises(TypeError): rinterface.SexpSymbol(x) def test_new_missing(): with pytest.raises(TypeError): rinterface.SexpSymbol() def test_new_fromstring(): symbol = rinterface.SexpSymbol('pi') evalsymbol = rinterface.baseenv['eval'](symbol) assert evalsymbol.rid == rinterface.baseenv['pi'].rid def test_new_str(): symbol = rinterface.SexpSymbol('pi') assert 'pi' == str(symbol) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_threading.py0000644000175100001770000000256014543120705021650 0ustar00runnerdockerimport pytest import rpy2.rinterface import rpy2.rinterface_lib.embedded from threading import Thread from rpy2.rinterface import embedded class ThreadWithExceptions(Thread): """Wrapper around Thread allowing to record exceptions from the thread.""" def run(self): self.exception = None try: self._target() except Exception as e: self.exception = e @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_threading__initr(): thread = ThreadWithExceptions(target=rpy2.rinterface_lib.embedded._initr) thread.start() thread.join() assert not thread.exception @pytest.mark.skipif(embedded.rpy2_embeddedR_isinitialized, reason='Can only be tested before R is initialized.') def test_threading_initr_simple(): # This initialization performs more post-initialization setup compared to _initr. # It will check whether R is initialized from the main thread. thread = ThreadWithExceptions(target=rpy2.rinterface.initr_simple) with pytest.warns( UserWarning, match=( r'R is not initialized by the main thread.\n' r'\W+Its taking over SIGINT cannot be reversed here.+' ) ): thread.start() thread.join() assert not thread.exception ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_bool.py0000644000175100001770000000333514543120705022221 0ustar00runnerdockerimport array import pytest import rpy2.rinterface as ri ri.initr() def test_init_from_seqr(): seq = [True, False, False] v = ri.BoolSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_from_int_memoryview(): a = array.array('i', (True, False, True)) mv = memoryview(a) vec = ri.BoolSexpVector.from_memoryview(mv) assert (True, False, True) == tuple(vec) def test_from_bool_memoryview(): a = array.array('b', (True, False, True)) mv = memoryview(a) with pytest.raises(ValueError): ri.BoolSexpVector.from_memoryview(mv) def test_getitem(): vec = ri.BoolSexpVector([True, False, False]) assert vec[1] is False def test_setitem(): vec = ri.BoolSexpVector([True, False, False]) vec[1] = True assert vec[1] is True def test_getslice(): vec = ri.BoolSexpVector([True, False, False]) vec = vec[0:2] assert len(vec) == 2 assert vec[0] is True assert vec[1] is False def test_getslice_negative(): vec = ri.BoolSexpVector([True, False, True]) vec_s = vec[-2:-1] assert len(vec_s) == 1 assert vec_s[0] is False def test_setslice(): vec = ri.BoolSexpVector([True, False, False]) vec[0:2] = [True, True] assert len(vec) == 3 assert vec[0] is True assert vec[1] is True def test_setslice_negative(): vec = ri.BoolSexpVector([True, False, False]) vec[-2:-1] = ri.BoolSexpVector([True, ]) assert len(vec) == 3 assert vec[1] is True def test_index(): x = ri.BoolSexpVector((True, False, False)) assert x.index(True) == 0 assert x.index(False) == 1 with pytest.raises(ValueError): x.index(2) with pytest.raises(ValueError): x.index('a') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_byte.py0000644000175100001770000000356314543120705022234 0ustar00runnerdockerimport array import pytest import rpy2.rinterface as ri ri.initr() def test_init_from_bytes_in_seq(): seq = (b'a', b'b', b'c') v = ri.ByteSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert ord(x) == y def test_init_from_seq_of_bytes(): seq = (b'a', b'b', b'c') v = ri.ByteSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert ord(x) == y def test_init_from_bytes(): seq = b'abc' v = ri.ByteSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_init_from_seq_invalid_byte(): seq = (b'a', [], b'c') with pytest.raises(ValueError): ri.ByteSexpVector(seq) def test_from_memoryview(): a = array.array('b', b'abcdefg') mv = memoryview(a) vec = ri.ByteSexpVector.from_memoryview(mv) assert tuple(b'abcdefg') == tuple(vec) def test_getitem(): vec = ri.ByteSexpVector((b'a', b'b', b'c')) assert vec[1] == ord(b'b') def test_getitem_slice(): vec = ri.ByteSexpVector((b'a', b'b', b'c')) assert tuple(vec[:2]) == (ord(b'a'), ord(b'b')) def test_setitem(): vec = ri.ByteSexpVector((b'a', b'b', b'c')) vec[1] = b'z' assert vec[1] == ord(b'z') def test_setitem_int(): vec = ri.ByteSexpVector((b'a', b'b', b'c')) vec[1] = ord(b'z') assert vec[1] == ord(b'z') def test_setitem_int_invalid(): vec = ri.ByteSexpVector((b'a', b'b', b'c')) with pytest.raises(ValueError): vec[1] = 333 def test_setitem_slice(): values = (b'a', b'b', b'c') vec = ri.ByteSexpVector(values) vec[:2] = b'yz' assert tuple(vec) == tuple(b'yzc') def test_setitem_slice_invalid(): values = (b'a', b'b', b'c') vec = ri.ByteSexpVector(values) with pytest.raises(TypeError): vec['foo'] = (333, ord(b'z')) with pytest.raises(ValueError): vec[:2] = (333, ord(b'z')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_complex.py0000644000175100001770000000303014543120705022725 0ustar00runnerdockerimport pytest import rpy2.rinterface as ri ri.initr() def test_init_from_seqr(): seq = [1+2j, 5+7j, 0+1j] v = ri.ComplexSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_init_from_seq_invalid_item(): seq = [1+2j, 'a', 0+1j] with pytest.raises(TypeError): ri.ComplexSexpVector(seq) def test_getitem(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) assert vec[1] == 5+7j def test_setitem(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) vec[1] = 100+3j assert vec[1] == 100+3j def test_getslice(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) vec_s = vec[0:2] assert len(vec_s) == 2 assert vec_s[0] == 1+2j assert vec_s[1] == 5+7j def test_getslice_negative(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) vec_s = vec[-2:-1] assert len(vec_s) == 1 assert vec_s[0] == 5+7j def test_setslice(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) vec[0:2] = ri.ComplexSexpVector([100+3j, 5-5j]) assert len(vec) == 3 assert vec[0] == 100+3j assert vec[1] == 5-5j def test_setslice_negative(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) vec[-2:-1] = ri.ComplexSexpVector([100+3j, ]) assert len(vec) == 3 assert vec[1] == 100+3j def test_index(): vec = ri.ComplexSexpVector([1+2j, 5+7j, 0+1j]) assert vec.index(5+7j) == 1 assert vec.index(0+1j) == 2 with pytest.raises(ValueError): vec.index(2) with pytest.raises(ValueError): vec.index('a') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_float.py0000644000175100001770000000325514543120705022374 0ustar00runnerdockerimport array import pytest import rpy2.rinterface as ri ri.initr() def test_init_from_seq(): seq = (1.0, 2.0, 3.0) v = ri.FloatSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_init_from_iter(): it = range(3) v = ri.FloatSexpVector(it) assert len(v) == 3 for x, y in zip(range(3), v): assert x == y def test_init_From_seq_invalid_float(): seq = (1.0, 'b', 3.0) with pytest.raises(ValueError): ri.FloatSexpVector(seq) def test_from_memoryview(): a = array.array('d', (x * 1.1 for x in range(3, 103))) mv = memoryview(a) vec = ri.FloatSexpVector.from_memoryview(mv) assert all(abs(x * 1.1 - y) < 0.001 for x, y in zip(range(3, 103), vec)) def test_from_int_memoryview(): a = array.array('i', range(3, 103)) mv = memoryview(a) with pytest.raises(ValueError): ri.FloatSexpVector.from_memoryview(mv) def test_from_long_memoryview(): a = array.array('l', range(3, 103)) mv = memoryview(a) with pytest.raises(ValueError): ri.FloatSexpVector.from_memoryview(mv) def test_getitem(): vec = ri.FloatSexpVector([1.0, 2.0, 3.0]) assert vec[1] == 2.0 def test_setitem(): vec = ri.FloatSexpVector([1.0, 2.0, 3.0]) vec[1] = 100.0 assert vec[1] == 100.0 def test_getslice(): vec = ri.FloatSexpVector([1.0, 2.0, 3.0]) vec = vec[0:2] assert len(vec) == 2 assert vec[0] == 1.0 assert vec[1] == 2.0 def test_setslice(): vec = ri.FloatSexpVector([1.0, 2.0, 3.0]) vec[0:2] = ri.FloatSexpVector([11.0, 12.0]) assert len(vec) == 3 assert vec[0] == 11.0 assert vec[1] == 12.0 assert vec[2] == 3.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_int.py0000644000175100001770000001264714543120705022066 0ustar00runnerdockerimport array import pytest import struct import sys import rpy2.rinterface as ri try: import numpy has_numpy = True except ImportError: has_numpy = False ri.initr() def test_init_from_iter(): seq = range(3) v = ri.IntSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_init_from_seq_invalid_item(): seq = (1, 'b', 3) with pytest.raises(ValueError): ri.IntSexpVector(seq) @pytest.mark.skip(reason='WIP') @pytest.mark.skipif(struct.calcsize('P') < 8, reason='Only relevant on 64 architectures.') def test_init_from_seq_invalid_overflow(): MAX_INT = ri._rinterface._MAX_INT v = ri.IntSexpVector((MAX_INT, 42)) assert v[0] == MAX_INT assert v[1] == 42 # check 64-bit architecture with pytest.raises(OverflowError): ri.IntSexpVector((MAX_INT + 1, )) def _test_from_int_memoryview(): a = array.array('i', range(3, 103)) mv = memoryview(a) vec = ri.IntSexpVector.from_memoryview(mv) assert tuple(range(3, 103)) == tuple(vec) def test_from_long_memoryview(): arrtype = 'l' if ri._rinterface.ffi.sizeof('int') == ri._rinterface.ffi.sizeof('long'): arrtype = 'q' a = array.array(arrtype, range(3, 103)) mv = memoryview(a) with pytest.raises(ValueError): ri.IntSexpVector.from_memoryview(mv) def test_from_intarray_object(): a = array.array('l', range(3, 103)) vec = ri.IntSexpVector.from_object(a) def test_from_longarray_object(): a = array.array('l', range(3, 103)) vec = ri.IntSexpVector.from_object(a) assert tuple(range(3, 103)) == tuple(vec) def test_from_tuple_object(): a = tuple(range(3, 103)) vec = ri.IntSexpVector.from_object(a) assert tuple(range(3, 103)) == tuple(vec) def test_getitem(): vec = ri.IntSexpVector(range(1, 10)) assert vec[1] == 2 def test_setitem(): vec = ri.IntSexpVector(range(1, 10)) vec[1] = 100 assert vec[1] == 100 def test_getslice(): vec = ri.IntSexpVector([1, 2, 3]) vec = vec[0:2] assert len(vec) == 2 assert vec[0] == 1 assert vec[1] == 2 def test_getslice_negative(): vec = ri.IntSexpVector([1, 2, 3]) vec_s = vec[-2:-1] assert len(vec_s) == 1 assert vec_s[0] == 2 def test_setslice(): vec = ri.IntSexpVector([1, 2, 3]) vec[0:2] = ri.IntSexpVector([11, 12]) assert len(vec) == 3 assert vec[0] == 11 assert vec[1] == 12 def test_setslice_negative(): vec = ri.IntSexpVector([1, 2, 3]) vec[-2:-1] = ri.IntSexpVector([33, ]) assert len(vec) == 3 assert vec[1] == 33 def test_index(): x = ri.IntSexpVector((1, 2, 3)) assert x.index(1) == 0 assert x.index(3) == 2 with pytest.raises(ValueError): x.index(33) with pytest.raises(ValueError): x.index('a') def test_getitem_negative_outffbound(): x = ri.IntSexpVector((1, 2, 3)) with pytest.raises(IndexError): x.__getitem__(-100) def test_getitem_outofbound(): x = ri.IntSexpVector((1, 2, 3)) with pytest.raises(IndexError): x.__getitem__(10) def test_getitem_outofbound_overmaxsize(): x = ri.IntSexpVector((1, 2, 3)) with pytest.raises(IndexError): x.__getitem__(sys.maxsize + 1) def test_getslice_missingboundary(): vec = ri.IntSexpVector(range(1, 11)) vec_slice = vec[:2] assert len(vec_slice) == 2 assert vec_slice[0] == 1 assert vec_slice[1] == 2 vec_slice = vec[8:] assert len(vec_slice) == 2 assert vec_slice[0] == 9 assert vec_slice[1] == 10 vec_slice = vec[-2:] assert len(vec_slice) == 2 assert vec_slice[0] == 9 assert vec_slice[1] == 10 def test_setitem_outffbound(): vec = ri.IntSexpVector(range(5)) with pytest.raises(IndexError): vec.__setitem__(10, 6) @pytest.mark.skipif(not has_numpy, reason='numpy currently required for memoryview to work.') def test_memoryview_2d(): shape = (5, 2) values = tuple(range(10)) # R arrays are column-major, therefore # a slice like [:, :] should look # like: # # 0 5 # 1 6 # 2 7 # 3 8 # 4 9 rarray = ri.baseenv['array']( ri.IntSexpVector(values), dim=ri.IntSexpVector(shape)) mv = rarray.memoryview() assert mv.f_contiguous is True assert mv.shape == shape assert mv.tolist() == [[0, 5], [1, 6], [2, 7], [3, 8], [4, 9]] rarray[0] = 10 assert mv.tolist() == [[10, 5], [1, 6], [2, 7], [3, 8], [4, 9]] @pytest.mark.skipif(not has_numpy, reason='numpy currently required for memoryview to work.') def test_memoryview_3d(): shape = (5, 2, 3) values = tuple(range(30)) # R arrays are column-major, therefore # a slice through the first index in the third dimension should look # like: # # 0 5 # 1 6 # 2 7 # 3 8 # 4 9 rarray = ri.baseenv['array']( ri.IntSexpVector(values), dim=ri.IntSexpVector(shape)) mv = rarray.memoryview() assert mv.f_contiguous is True assert mv.shape == shape assert tuple(x[0][0] for x in mv.tolist()) == (0, 1, 2, 3, 4) def test_array_protocol(): v = ri.IntSexpVector(range(10)) ai = v.__array_interface__ assert ai['version'] == 3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_lang.py0000644000175100001770000000255614543120705022213 0ustar00runnerdockerimport pytest import rpy2.rinterface as ri ri.initr() def test_init(): rgetattr = ri.baseenv.find('::') formula = rgetattr('stats', 'formula') f = formula(ri.StrSexpVector(['y ~ x', ])) assert f.typeof == ri.RTYPES.LANGSXP def test_init_invalid(): seq = True with pytest.raises(TypeError): ri.LangSexpVector(seq) def test_rclass(): rgetattr = ri.baseenv.find('::') formula = rgetattr('stats', 'formula') f = formula(ri.StrSexpVector(['y ~ x', ])) assert f.rclass[0] == 'formula' def test_getitem(): rgetattr = ri.baseenv.find('::') formula = rgetattr('stats', 'formula') f = formula(ri.StrSexpVector(['y ~ x', ])) y = f[0] assert y.typeof == ri.RTYPES.SYMSXP assert str(f[0]) == '~' assert str(f[1]) == 'y' assert str(f[2]) == 'x' def test_setitem(): rgetattr = ri.baseenv.find('::') formula = rgetattr('stats', 'formula') f = formula(ri.StrSexpVector(['y ~ x', ])) response = f[1] predictor = f[2] f[1] = predictor f[2] = response assert str(f[1]) == 'x' assert str(f[2]) == 'y' # put ExprSexp test here def test_expression(): expression = ri.baseenv.find('expression') e = expression(ri.StrSexpVector(['a', ]), ri.StrSexpVector(['b', ])) assert e.typeof == ri.RTYPES.EXPRSXP y = e[0] assert y.typeof == ri.RTYPES.STRSXP ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_list.py0000644000175100001770000000440614543120705022241 0ustar00runnerdockerimport pytest import operator from .. import utils import rpy2.rinterface as ri ri.initr() def test_init_from_seq(): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) v = ri.ListSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): utils.assert_equal_sequence(x, y) def test_init_From_seq_invalid_elt(): seq = (ri.FloatSexpVector([1.0]), lambda x: x, ri.StrSexpVector(['foo', 'bar'])) with pytest.raises(Exception): ri.ListSexpVector(seq) def test_getitem(): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) vec = ri.ListSexpVector(seq) utils.assert_equal_sequence(vec[1], ri.IntSexpVector([2, 3])) with pytest.raises(TypeError): vec[(2, 3)] @pytest.mark.parametrize( 'value_constructor,value_param,equal_func', ((ri.BoolSexpVector, [True, True, False], utils.assert_equal_sequence), (int, 9, operator.eq)) ) def test_setitem(value_constructor, value_param, equal_func): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) vec = ri.ListSexpVector(seq) value = value_constructor(value_param) vec[1] = value equal_func(vec[1], value) with pytest.raises(TypeError): vec[(2, 3)] = 123 def test_getslice(): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) vec = ri.ListSexpVector(seq) vec_s = vec[0:2] assert len(vec_s) == 2 utils.assert_equal_sequence(vec_s[0], ri.FloatSexpVector([1.0])) utils.assert_equal_sequence(vec_s[1], ri.IntSexpVector([2, 3])) def test_setslice(): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) vec = ri.ListSexpVector(seq) vec[0:2] = ri.ListSexpVector( [ri.FloatSexpVector([10.0]), ri.IntSexpVector([20, 30])] ) assert len(vec) == 3 utils.assert_equal_sequence(vec[0], ri.FloatSexpVector([10.0])) utils.assert_equal_sequence(vec[1], ri.IntSexpVector([20, 30])) utils.assert_equal_sequence(vec[2], ri.StrSexpVector(['foo', 'bar'])) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_numpy.py0000644000175100001770000000433414543120705022436 0ustar00runnerdockerimport pytest import rpy2.rinterface as rinterface try: import numpy has_numpy = True except ImportError: has_numpy = False rinterface.initr() def floatEqual(x, y, epsilon=0.00000001): return abs(x - y) < epsilon @pytest.mark.skipif(not has_numpy, reason='Package numpy is not installed.') def test_array_struct_int(): px = [1, -2, 3] x = rinterface.IntSexpVector(px) nx = numpy.asarray(x.memoryview()) assert nx.dtype.kind == 'i' for orig, new in zip(px, nx): assert orig == new # change value in the Python array... makes it change in the R vector nx[1] = 12 assert x[1] == 12 @pytest.mark.skipif(not has_numpy, reason='Package numpy is not installed.') def test_array_struct_double(): px = [1.0, -2.0, 3.0] x = rinterface.FloatSexpVector(px) nx = numpy.asarray(x.memoryview()) assert nx.dtype.kind == 'f' for orig, new in zip(px, nx): assert orig == new # change value in the Python array... makes it change in the R vector nx[1] = 333.2 assert x[1] == 333.2 @pytest.mark.skip(reason='WIP') @pytest.mark.skipif(not has_numpy, reason='Package numpy is not installed.') def test_array_struct_complex(): px = [1+2j, 2+5j, -1+0j] x = rinterface.ComplexSexpVector(px) nx = numpy.asarray(x.memoryview()) assert nx.dtype.kind == 'c' for orig, new in zip(px, nx): assert orig == new @pytest.mark.skipif(not has_numpy, reason='Package numpy is not installed.') def test_array_struct_boolean(): px = [True, False, True] x = rinterface.BoolSexpVector(px) nx = numpy.asarray(x.memoryview()) # not 'b' as R boolean vectors are array of ints. assert nx.dtype.kind == 'i' for orig, new in zip(px, nx): assert orig == new @pytest.mark.skipif(not has_numpy, reason='Package numpy is not installed.') def test_array_shape_len3(): extract = rinterface.baseenv['['] rarray = rinterface.baseenv['array']( rinterface.IntSexpVector(range(30)), dim=rinterface.IntSexpVector([5, 2, 3])) npyarray = numpy.array(rarray.memoryview()) for i in range(5): for j in range(2): for k in range(3): assert extract(rarray, i+1, j+1, k+1)[0] == npyarray[i, j, k] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_pairlist.py0000644000175100001770000000305514543120705023114 0ustar00runnerdockerimport rpy2.rinterface as ri ri.initr() def test_init_from_r(): pairlist = ri.baseenv.find('pairlist') pl = pairlist(a=ri.StrSexpVector(['1', ]), b=ri.StrSexpVector(['3', ])) assert pl.typeof == ri.RTYPES.LISTSXP def test_names(): pairlist = ri.baseenv.find('pairlist') pl = pairlist(a=ri.StrSexpVector(['1', ]), b=ri.StrSexpVector(['3', ])) assert tuple(pl.names) == ('a', 'b') def test_getitem_pairlist(): pairlist = ri.baseenv.find('pairlist') pl = pairlist(a=ri.StrSexpVector(['1', ]), b=ri.StrSexpVector(['3', ])) # R's behaviour is that subsetting returns an R list y = pl[0] assert y.typeof == ri.RTYPES.VECSXP assert len(y) == 1 assert y[0][0] == '1' assert y.names[0] == 'a' def test_getslice_pairlist(): pairlist = ri.baseenv.find('pairlist') vec = pairlist(a=ri.StrSexpVector(['1', ]), b=ri.StrSexpVector(['3', ]), c=ri.StrSexpVector(['6', ])) vec_slice = vec[0:2] assert vec_slice.typeof == ri.RTYPES.LISTSXP assert len(vec_slice) == 2 assert tuple(vec_slice[0][0]) == ('1', ) assert tuple(vec_slice[1][0]) == ('3', ) assert tuple(vec_slice.names) == ('a', 'b') def test_getslice_pairlist_issue380(): # Checks that root of issue #380 is fixed vec = ri.baseenv['.Options'] vec_slice = vec[0:2] assert len(vec_slice) == 2 assert vec_slice.typeof == ri.RTYPES.LISTSXP assert vec.names[0] == vec_slice.names[0] assert vec.names[1] == vec_slice.names[1] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vector_str.py0000644000175100001770000000451314543120705022075 0ustar00runnerdockerimport pytest import rpy2.rinterface as ri ri.initr() def test_init_from_seqr(): seq = ['foo', 'bar', 'baz'] v = ri.StrSexpVector(seq) assert len(v) == 3 for x, y in zip(seq, v): assert x == y def test_init_from_seq_invalid_item(): seq = ['foo', 0, 'baz'] with pytest.raises(Exception): ri.StrSexpVector(seq) def test_getitem(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) assert vec[1] == 'bar' with pytest.raises(TypeError): vec[(2, 3)] @pytest.mark.parametrize('value', ('boo', ri.NA_Character)) def test_setitem(value): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) vec[1] = value assert vec[1] == value with pytest.raises(TypeError): vec[(2, 3)] = value def test_getslice(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) vec_s = vec[0:2] assert len(vec_s) == 2 assert vec_s[0] == 'foo' assert vec_s[1] == 'bar' def test_getslice_negative(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) vec_s = vec[-2:-1] assert len(vec_s) == 1 assert vec_s[0] == 'bar' def test_setslice(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) vec[0:2] = ['boo', 'noo'] assert len(vec) == 3 assert vec[0] == 'boo' assert vec[1] == 'noo' def test_setslice_negative(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) vec[-2:-1] = ri.StrSexpVector(['boo', ]) assert len(vec) == 3 assert vec[1] == 'boo' def test_index(): vec = ri.StrSexpVector(['foo', 'bar', 'baz']) assert vec.index('bar') == 1 assert vec.index('baz') == 2 with pytest.raises(ValueError): vec.index(2) with pytest.raises(ValueError): vec.index('a') def test_non_asciil(): u_char = '\u21a7' b_char = b'\xe2\x86\xa7' assert(b_char == u_char.encode('utf-8')) sexp = ri.StrSexpVector((u'\u21a7', )) char = sexp[0] assert isinstance(char, str) # FIXME: the following line is failing on drone, but not locally assert u'\u21a7'.encode('utf-8') == char.encode('utf-8') # because of this, the following line is used to pass the test # until I have more reports from users or manage to reproduce # myself what is happening on drone.io. sexp = ri.evalr('c("ěščřžýáíé", "abc", "百折不撓")') assert all(isinstance(x, str) for x in sexp) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rinterface/test_vectors.py0000644000175100001770000000726214543120705021374 0ustar00runnerdockerimport subprocess import pytest import sys import os import textwrap import rpy2.rinterface as ri ri.initr() def floatEqual(x, y, epsilon=0.00000001): return abs(x - y) < epsilon def test_int(): sexp = ri.IntSexpVector([1, ]) isInteger = ri.globalenv.find("is.integer") assert isInteger(sexp)[0] def test_float(): sexp = ri.IntSexpVector([1.0, ]) isNumeric = ri.globalenv.find("is.numeric") assert isNumeric(sexp)[0] def test_str(): sexp = ri.StrSexpVector(["a", ]) isStr = ri.globalenv.find("is.character") assert isStr(sexp)[0] def test_bool(): sexp = ri.BoolSexpVector([True, ]) isBool = ri.globalenv.find("is.logical") assert isBool(sexp)[0] def test_complex(): sexp = ri.ComplexSexpVector([1+2j, ]) is_complex = ri.globalenv.find("is.complex") assert is_complex(sexp)[0] def test_byte(): seq = (b'a', b'b') sexp = ri.ByteSexpVector(seq) is_raw = ri.globalenv.find("is.raw") assert is_raw(sexp)[0] def test_del(): v = ri.IntSexpVector(range(10)) with pytest.raises(AttributeError): v.__delitem__(3) def test_from_bool(): sexp = ri.vector([True, ], ri.RTYPES.LGLSXP) isLogical = ri.globalenv.find('is.logical') assert isLogical(sexp)[0] assert sexp[0] is True sexp = ri.vector(['a', ], ri.RTYPES.LGLSXP) isLogical = ri.globalenv.find('is.logical') assert isLogical(sexp)[0] assert sexp[0] def test_from_int(): sexp = ri.vector([1, ], ri.RTYPES.INTSXP) isInteger = ri.globalenv.find('is.integer') assert isInteger(sexp)[0] with pytest.raises(ValueError): ri.vector(['a', ], ri.RTYPES.INTSXP) def test_from_invalid_no_length(): s = (x for x in range(30)) with pytest.raises(TypeError): ri.vector(s, ri.RTYPES.INTSXP) def test_from_float(): sexp = ri.vector([1.0, ], ri.RTYPES.REALSXP) isNumeric = ri.globalenv.find("is.numeric") assert isNumeric(sexp)[0] def test_from_float_nan(): with pytest.raises(ValueError): ri.vector(["a", ], ri.RTYPES.REALSXP) def test_from_complex(): sexp = ri.vector([1.0 + 1.0j, ], ri.RTYPES.CPLXSXP) isComplex = ri.globalenv.find('is.complex') assert isComplex(sexp)[0] def test_from_string(): sexp = ri.vector(['abc', ], ri.RTYPES.STRSXP) isCharacter = ri.globalenv.find('is.character') assert isCharacter(sexp)[0] def test_from_list(): seq = (ri.FloatSexpVector([1.0]), ri.IntSexpVector([2, 3]), ri.StrSexpVector(['foo', 'bar'])) sexp = ri.ListSexpVector(seq) isList = ri.globalenv.find('is.list') assert isList(sexp)[0] assert len(sexp) == 3 def test_missing_R_Preserve_object_bug(): rgc = ri.baseenv['gc'] xx = range(100000) x = ri.IntSexpVector(xx) rgc() assert x[0] == 0 def test_invalid_rtype(): with pytest.raises(ValueError): ri.vector([1, ], -1) with pytest.raises(ValueError): ri.vector([1, ], 250) def test_invalid_not_vector_rtype(): with pytest.raises(ValueError): ri.vector([1, ], ri.RTYPES.ENVSXP) _instantiate_without_initr = textwrap.dedent( """ import rpy2.rinterface as rinterface try: tmp = rinterface.vector([1,2], rinterface.RTYPES.INTSXP) res = 'OK' except rinterface.embedded.RNotReadyError as re: res = 'Error: R not ready.' except Exception as e: res = 'Exception: %s' % str(e) print(res) """) def test_instantiate_without_initr(): pycode = (_instantiate_without_initr if os.name == 'nt' else _instantiate_without_initr.encode('ASCII')) output = subprocess.check_output((sys.executable, '-c', pycode)) assert output.rstrip() == b'Error: R not ready.'.rstrip() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8699465 rpy2-3.5.15/rpy2/tests/rlike/0000755000175100001770000000000014543120757015262 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rlike/test_container.py0000644000175100001770000001770314543120705020656 0ustar00runnerdockerimport pytest import pickle from io import BytesIO import rpy2.rlike.container as rlc class TestOrdDict(object): def test_new(self): nl = rlc.OrdDict() x = (('a', 123), ('b', 456), ('c', 789)) nl = rlc.OrdDict(x) def test_new_invalid(self): with pytest.raises(TypeError): rlc.OrdDict({}) def test_notimplemented_operators(self): nl = rlc.OrdDict() nl2 = rlc.OrdDict() assert nl == nl # equivalent to `nl is nl` assert nl != nl2 # equivalent to `nl is not nl2` with pytest.raises(TypeError): nl > nl2 with pytest.raises(NotImplementedError): reversed(nl) with pytest.raises(NotImplementedError): nl.sort() def test_repr(self): x = (('a', 123), ('b', 456), ('c', 789)) nl = rlc.OrdDict(x) assert isinstance(repr(nl), str) def test_iter(self): x = (('a', 123), ('b', 456), ('c', 789)) nl = rlc.OrdDict(x) for a, b in zip(nl, x): assert a == b[0] def test_len(self): x = rlc.OrdDict() assert len(x) == 0 x['a'] = 2 x['b'] = 1 assert len(x) == 2 def test_getsetitem(self): x = rlc.OrdDict() x['a'] = 1 assert len(x) == 1 assert x['a'] == 1 assert x.index('a') == 0 x['a'] = 2 assert len(x) == 1 assert x['a'] == 2 assert x.index('a') == 0 x['b'] = 1 assert len(x) == 2 assert x['b'] == 1 assert x.index('b') == 1 def test_get(self): x = rlc.OrdDict() x['a'] = 1 assert x.get('a') == 1 assert x.get('b') is None assert x.get('b', 2) == 2 def test_keys(self): x = rlc.OrdDict() word = 'abcdef' for i,k in enumerate(word): x[k] = i for i,k in enumerate(x.keys()): assert word[i] == k def test_getsetitemwithnone(self): x = rlc.OrdDict() x['a'] = 1 x[None] = 2 assert len(x) == 2 x['b'] = 5 assert len(x) == 3 assert x['a'] == 1 assert x['b'] == 5 assert x.index('a') == 0 assert x.index('b') == 2 def test_reverse(self): x = rlc.OrdDict() x['a'] = 3 x['b'] = 2 x['c'] = 1 x.reverse() assert x['c'] == 1 assert x.index('c') == 0 assert x['b'] == 2 assert x.index('b') == 1 assert x['a'] == 3 assert x.index('a') == 2 def test_items(self): args = (('a', 5), ('b', 4), ('c', 3), ('d', 2), ('e', 1)) x = rlc.OrdDict(args) it = x.items() for ki, ko in zip(args, it): assert ki[0] == ko[0] assert ki[1] == ko[1] def test_pickling(self): f = BytesIO() pickle.dump(rlc.OrdDict([('a', 1), ('b', 2)]), f) f.seek(0) od = pickle.load(f) assert od['a'] == 1 assert od.index('a') == 0 assert od['b'] == 2 assert od.index('b') == 1 class TestTaggedList(object): def test__add__(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) tl = tl + tl assert len(tl) == 6 assert tl.tags == ('a', 'b', 'c', 'a', 'b', 'c') assert tuple(tl) == (1,2,3,1,2,3) def test__delitem__(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert len(tl) == 3 del tl[1] assert len(tl) == 2 assert tl.tags == ('a', 'c') assert tuple(tl) == (1, 3) def test__delslice__(self): tl = rlc.TaggedList((1,2,3,4), tags=('a', 'b', 'c', 'd')) del tl[1:3] assert len(tl) == 2 assert tl.tags == ('a', 'd') assert tuple(tl) == (1, 4) def test__iadd__(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) tl += tl assert len(tl) == 6 assert tl.tags == ('a', 'b', 'c', 'a', 'b', 'c') assert tuple(tl) == (1,2,3,1,2,3) def test__imul__(self): tl = rlc.TaggedList((1,2), tags=('a', 'b')) tl *= 3 assert len(tl) == 6 assert tl.tags == ('a', 'b', 'a', 'b', 'a', 'b') assert tuple(tl) == (1,2,1,2,1,2) def test__init__(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) with pytest.raises(ValueError): rlc.TaggedList((1,2,3), tags = ('b', 'c')) def test__setslice__(self): tl = rlc.TaggedList((1,2,3,4), tags=('a', 'b', 'c', 'd')) tl[1:3] = [5, 6] assert len(tl) == 4 assert tl.tags == ('a', 'b', 'c', 'd') assert tuple(tl) == (1, 5, 6, 4) def test_append(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert len(tl) == 3 tl.append(4, tag='a') assert len(tl) == 4 assert tl[3] == 4 assert tl.tags == ('a', 'b', 'c', 'a') def test_extend(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) tl.extend([4, 5]) assert tuple(tl.itertags()) == ('a', 'b', 'c', None, None) assert tuple(tl) == (1, 2, 3, 4, 5) def test_insert(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) tl.insert(1, 4, tag = 'd') assert tuple(tl.itertags()) == ('a', 'd', 'b', 'c') assert tuple(tl) == (1, 4, 2, 3) def test_items(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert tuple(tl.items()) == (('a', 1), ('b', 2), ('c', 3)) def test_iterontag(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'a')) assert tuple(tl.iterontag('a')) == (1, 3) def test_itertags(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert tuple(tl.itertags()) == ('a', 'b', 'c') def test_pop(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert len(tl) == 3 elt = tl.pop() assert elt == 3 assert len(tl) == 2 assert tl.tags == ('a', 'b') assert tuple(tl) == (1, 2) elt = tl.pop(0) assert elt == 1 assert len(tl) == 1 assert tl.tags == ('b', ) def test_remove(self): tl = rlc.TaggedList((1,2,3), tags=('a', 'b', 'c')) assert len(tl) == 3 tl.remove(2) assert len(tl) == 2 assert tl.tags == ('a', 'c') assert tuple(tl) == (1, 3) def test_reverse(self): tn = ['a', 'b', 'c'] tv = [1,2,3] tl = rlc.TaggedList(tv, tags = tn) tl.reverse() assert len(tl) == 3 assert tl.tags == ('c', 'b', 'a') assert tuple(tl) == (3, 2, 1) def test_sort(self): tn = ['a', 'c', 'b'] tv = [1,3,2] tl = rlc.TaggedList(tv, tags = tn) tl.sort() assert tl.tags == ('a', 'b', 'c') assert tuple(tl) == (1, 2, 3) def test_tags(self): tn = ['a', 'b', 'c'] tv = [1,2,3] tl = rlc.TaggedList(tv, tags = tn) tags = tl.tags assert isinstance(tags, tuple) is True assert tags == ('a', 'b', 'c') tn = ['d', 'e', 'f'] tl.tags = tn assert isinstance(tags, tuple) is True assert tl.tags == tuple(tn) def test_settag(self): tn = ['a', 'b', 'c'] tv = [1,2,3] tl = rlc.TaggedList(tv, tags = tn) tl.settag(1, 'z') assert tl.tags == ('a', 'z', 'c') def test_from_items(self): od = rlc.OrdDict( (('a', 1), ('b', 2), ('c', 3)) ) tl = rlc.TaggedList.from_items(od) assert tl.tags == ('a', 'b', 'c') assert tuple(tl) == (1, 2, 3) tl = rlc.TaggedList.from_items({'a':1, 'b':2, 'c':3}) assert set(tl.tags) == set(('a', 'b', 'c')) assert set(tuple(tl)) == set((1, 2, 3)) def test_pickle(self): tl = rlc.TaggedList([1, 2, 3]) tl_pickled = pickle.dumps(tl) tl_unpickled = pickle.loads(tl_pickled) assert tuple(tl) == tuple(tl_unpickled) assert tuple(tl.itertags()) == tuple(tl_unpickled.itertags()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rlike/test_functional.py0000644000175100001770000000117614543120705021033 0ustar00runnerdockerimport pytest import rpy2.rlike.functional as rlf def test_tapply_sumbystring(): seq = ( 1, 2, 3, 4, 5, 6) tags = ('a', 'b', 'a', 'c', 'b', 'a') expected = {'a': 1+3+6, 'b': 2+5, 'c': 4} res = rlf.tapply(seq, tags, sum) for k, v in res: assert expected[k] == v @pytest.mark.parametrize('subject_fun', [rlf.iterify, rlf.listify]) def test_simplefunction(subject_fun): def f(x): return x ** 2 f_iter = subject_fun(f) seq = (1, 2, 3) res = f_iter(seq) for va, vb in zip(seq, res): assert va ** 2 == vb ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/rlike/test_indexing.py0000644000175100001770000000033214543120705020467 0ustar00runnerdockerimport pytest import rpy2.rlike.indexing as rfi def test_order(): seq = (2, 1, 5, 3, 4) expected = (1, 2, 3, 4, 5) res = rfi.order(seq) for va, vb in zip(expected, res): assert seq[vb] == va ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8739467 rpy2-3.5.15/rpy2/tests/robjects/0000755000175100001770000000000014543120757015767 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/__init__.py0000644000175100001770000000000014543120705020057 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8739467 rpy2-3.5.15/rpy2/tests/robjects/lib/0000755000175100001770000000000014543120757016535 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/__init__.py0000644000175100001770000000000014543120705020625 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/test_dbplyr.py0000644000175100001770000000074714543120705021443 0ustar00runnerdockerimport pytest from rpy2.robjects import packages has_dbplyr = None try: from rpy2.robjects.lib import dbplyr has_dbplyr = True msg = '' except packages.PackageNotInstalledError as error: has_dbplyr = False msg = str(error) @pytest.mark.skipif(not has_dbplyr, reason=msg) class TestDplyr(object): def test_sql(self): sql = dbplyr.sql('count(*)') # FIXME: no testing much at the moment... assert 'sql' in sql.rclass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/test_dplyr.py0000644000175100001770000001103414543120705021270 0ustar00runnerdockerimport pytest from rpy2.robjects import packages from rpy2.robjects import rl from rpy2.robjects.vectors import StrVector try: from rpy2.robjects.lib import dplyr has_dplyr = True msg = '' except packages.PackageNotInstalledError as error: has_dplyr = False msg = str(error) datasets = packages.importr('datasets') mtcars = packages.data(datasets).fetch('mtcars')['mtcars'] @pytest.mark.skipif(not has_dplyr, reason=msg) class TestDplyr(object): def test_dataframe(self): dataf = dplyr.DataFrame(mtcars) # FIXME: no testing much at the moment... assert isinstance(dataf, dplyr.DataFrame) def test_filter_nofilter_method(self): dataf = dplyr.DataFrame(mtcars) dataf_filter = dataf.filter() assert dataf.nrow == dataf_filter.nrow def test_filter_nofilter_function(self): dataf = dplyr.DataFrame(mtcars) dataf_filter = dplyr.filter(dataf) assert dataf.nrow == dataf_filter.nrow def test_filter_onefilter_method(self): dataf = dplyr.DataFrame(mtcars) ngear_gt_3 = len(tuple(x for x in dataf.rx2('gear') if x > 3)) dataf_filter = dataf.filter(rl('gear > 3')) assert ngear_gt_3 == dataf_filter.nrow def test_filter_onefilter_function(self): dataf = dplyr.DataFrame(mtcars) ngear_gt_3 = len(tuple(x for x in dataf.rx2('gear') if x > 3)) dataf_filter = dplyr.filter(dataf, rl('gear > 3')) assert ngear_gt_3 == dataf_filter.nrow def test_count(self): dataf_a = dplyr.DataFrame(mtcars) res = dataf_a.count() assert tuple(res.rx2('n')) == (dataf_a.nrow, ) def test_distinct(self): dataf_a = dplyr.DataFrame(mtcars) res = dataf_a.distinct() assert res.nrow == dataf_a.nrow def test_sample_n(self): dataf_a = dplyr.DataFrame(mtcars) res = dataf_a.sample_n(5) assert res.nrow == 5 def test_sample_frac(self): dataf_a = dplyr.DataFrame(mtcars) res = dataf_a.sample_frac(.5) assert res.nrow == int(dataf_a.nrow / 2) def test_sample_select(self): dataf_a = dplyr.DataFrame(mtcars) res = dataf_a.select('gear') assert res.nrow == dataf_a.nrow def test_group_by(self): dataf_a = dplyr.DataFrame(mtcars) dataf_g = dataf_a.group_by(rl('gear')) assert dataf_g.is_grouped_df assert not dataf_g.ungroup().is_grouped_df assert dataf_g.is_grouped_df def test_splitmerge_function(self): dataf = dplyr.DataFrame(mtcars) dataf_by_gear = dataf.group_by(rl('gear')) dataf_avg_mpg = dataf_by_gear.summarize(foo=rl('mean(mpg)')) assert isinstance(dataf_avg_mpg, dplyr.DataFrame) def test_mutate(self): dataf_a = dplyr.DataFrame(mtcars) dataf_b = dataf_a.mutate(foo=1, bar=rl('gear+1')) assert type(dataf_b) is dplyr.DataFrame assert all(a == b for a, b in zip(dataf_a.rx2('gear'), dataf_b.rx2('gear'))) assert all(a+1 == b for a, b in zip(dataf_a.rx2('gear'), dataf_b.rx2('bar'))) def test_mutate_at(self): dataf_a = dplyr.DataFrame(mtcars) dataf_b = dataf_a.mutate_at(StrVector(["gear"]), rl('sqrt')) assert type(dataf_b) is dplyr.DataFrame def test_mutate_all(self): dataf_a = dplyr.DataFrame(mtcars) dataf_b = dataf_a.mutate_all(rl('sqrt')) assert type(dataf_b) is dplyr.DataFrame @pytest.mark.parametrize('join_method', ('inner_join', 'left_join', 'right_join', 'full_join')) def test_join(self, join_method): dataf_a = dplyr.DataFrame(mtcars) dataf_b = dataf_a.mutate(foo=1) dataf_c = getattr(dataf_a, join_method)(dataf_b, by=dataf_a.colnames) all_names = list(dataf_a.colnames) all_names.append('foo') assert sorted(list(all_names)) == sorted(list(dataf_c.colnames)) assert tuple(all_names) == tuple(dataf_c.colnames) def test_collect(self): dataf = dplyr.DataFrame(mtcars) dataf_collected = dataf.collect() # FIXME: no real test here. Just ensuring that it is returning # without error assert type(dataf_collected) is dplyr.DataFrame def test_arrange(self): dataf = dplyr.DataFrame(mtcars) dataf_arrange = dataf.arrange(rl('mpg')) assert tuple(sorted(dataf.collect().rx2('mpg'))) == \ tuple(dataf_arrange.collect().rx2('mpg')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/test_ggplot2.py0000644000175100001770000000615614543120705021525 0ustar00runnerdockerimport pytest from rpy2.robjects import packages from rpy2.robjects import rl try: from rpy2.robjects.lib import ggplot2 has_ggplot = True msg = '' except packages.PackageNotInstalledError as error: has_ggplot = False msg = str(error) datasets = packages.importr('datasets') mtcars = datasets.__rdata__.fetch('mtcars')['mtcars'] @pytest.mark.skipif(not has_ggplot, reason=msg) class TestGGplot(object): def test_gglot(self): gp = ggplot2.ggplot(mtcars) assert isinstance(gp, ggplot2.GGPlot) def test_gglot_mapping(self): gp = ggplot2.ggplot(mtcars, ggplot2.aes_string(x='gear')) assert isinstance(gp, ggplot2.GGPlot) def test_element_text(self): et = ggplot2.element_text() assert isinstance(et, ggplot2.ElementText) def test_element_text_repr(self): et = ggplot2.element_text() assert repr(et).startswith(' 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/test_grid.py0000644000175100001770000000257714543120705021077 0ustar00runnerdockerimport pytest import contextlib import os import tempfile from rpy2.robjects.conversion import localconverter import rpy2.robjects as robjects from rpy2.robjects.lib import grid from rpy2.robjects.vectors import FloatVector, StrVector @pytest.mark.parametrize( 'constructor,args,kwargs,res_cls', ((grid.unit, (1, 'cm'), {}, grid.Unit), (grid.unit, (), {'x': 1, 'units': 'cm'}, grid.Unit), (grid.gpar, (), {'col': 'blue'}, grid.Gpar), (grid.grob, (), {'name': 'foo'}, grid.Grob), (grid.rect, (), {'x': grid.unit(0, 'cm')}, grid.Rect), (grid.lines, (), {'x': grid.unit(FloatVector([0, 1]), 'cm')}, grid.Lines), (grid.circle, (), {'x': 0}, grid.Circle), (grid.points, (), {}, grid.Points), (grid.text, ('t0', ), {}, grid.Text), (grid.GTree.gtree, (), {}, grid.GTree), (grid.GTree.grobtree, (), {}, grid.GTree), (grid.XAxis.xaxis, (), {}, grid.XAxis), (grid.YAxis.yaxis, (), {}, grid.YAxis), (grid.XAxis.xaxisgrob, (), {}, grid.XAxis), (grid.YAxis.yaxisgrob, (), {}, grid.YAxis), ) ) def test_constructor_and_rpy2py(constructor, args, kwargs, res_cls): r_obj = constructor(*args, **kwargs) with localconverter(robjects.default_converter + grid.converter) as cv: rp_obj = cv.rpy2py(r_obj) assert isinstance(rp_obj, res_cls) # TODO: is there anything to test ? def test_viewport(): v = grid.viewport() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/lib/test_tidyr.py0000644000175100001770000000257214543120705021300 0ustar00runnerdockerimport pytest from rpy2.robjects import packages try: from rpy2.robjects.lib import tidyr has_tidyr = True msg = '' except packages.PackageNotInstalledError as error: has_tidyr = False msg = str(error) from rpy2 import rinterface from rpy2.robjects import vectors datasets = packages.importr('datasets') mtcars = packages.data(datasets).fetch('mtcars')['mtcars'] @pytest.mark.skipif(not has_tidyr, reason=msg) class TestTidyr(object): def test_dataframe(self): dataf = tidyr.DataFrame( {'x': vectors.IntVector((1,2,3,4,5)), 'labels': vectors.StrVector(('a','b','b','b','a'))}) assert isinstance(dataf, tidyr.DataFrame) assert sorted(['x', 'labels']) == sorted(list(dataf.colnames)) def test_spread(self): labels = ('a','b','c','d','e') dataf = tidyr.DataFrame( {'x': vectors.IntVector((1,2,3,4,5)), 'labels': vectors.StrVector(labels)}) dataf_spread = dataf.spread('labels', 'x') assert sorted(list(labels)) == sorted(list(dataf_spread.colnames)) def test_gather(self): dataf = tidyr.DataFrame({'a': 1.0, 'b': 2.0}) dataf_gathered = dataf.gather('label', 'x') assert sorted(['label', 'x']) == sorted(list(dataf_gathered.colnames)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_array.py0000644000175100001770000000775714543120705020527 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects rinterface = robjects.rinterface import array # TODO: move to common module for testing def almost_equal(x, y, epsilon = 0.00001): return abs(y - x) <= epsilon def test_init_invalid(): with pytest.raises(TypeError): robjects.vectors.IntArray(3) def test_init(): m = rinterface.globalenv.find('matrix')(1, nrow=5, ncol=3) a = robjects.vectors.FloatArray(m) if int(rinterface.sexp.RVersion()['major']) >= 4: expected = ('matrix', 'array') else: expected = ('matrix', ) assert tuple(a.rclass) == expected def test_dim(): m = robjects.r.matrix(1, nrow=5, ncol=3) a = robjects.vectors.FloatArray(m) d = a.dim assert len(d) == 2 assert d[0] == 5 assert d[1] == 3 def test_names_get(): dimnames = robjects.r.list(robjects.StrVector(['a', 'b', 'c']), robjects.StrVector(['d', 'e'])) m = robjects.r.matrix(1, nrow=3, ncol=2, dimnames = dimnames) a = robjects.vectors.FloatArray(m) res = a.names r_identical = robjects.r.identical assert r_identical(dimnames[0], res[0])[0] assert r_identical(dimnames[1], res[1])[0] def test_names_set(): dimnames = robjects.r.list(robjects.StrVector(['a', 'b', 'c']), robjects.StrVector(['d', 'e'])) m = robjects.r.matrix(1, nrow=3, ncol=2) a = robjects.vectors.FloatArray(m) a.names = dimnames res = a.names r_identical = robjects.r.identical assert r_identical(dimnames[0], res[0])[0] assert r_identical(dimnames[1], res[1])[0] # Test Matrix def test_nrow_get(): m = robjects.r.matrix(robjects.IntVector(range(6)), nrow=3, ncol=2) assert m.nrow == 3 def test_ncol_get(): m = robjects.r.matrix(robjects.IntVector(range(6)), nrow=3, ncol=2) assert m.ncol == 2 def test_transpose(): m = robjects.r.matrix(robjects.IntVector(range(6)), nrow=3, ncol=2) mt = m.transpose() for i,val in enumerate((0,1,2,3,4,5,)): assert m[i] == val for i,val in enumerate((0,3,1,4,2,5)): assert mt[i] == val def test_matmul(): # 1 3 # 2 4 m = robjects.r.matrix(robjects.IntVector(range(1, 5)), nrow=2) # 1*1+3*2 1*3+3*4 # 2*1+4*2 2*3+4*4 m2 = m @ m for i,val in enumerate((7.0, 10.0, 15.0, 22.0,)): assert m2[i] == val def test_crossprod(): m = robjects.r.matrix(robjects.IntVector(range(4)), nrow=2) mcp = m.crossprod(m) for i,val in enumerate((1.0,3.0,3.0,13.0,)): assert mcp[i] == val def test_tcrossprod(): m = robjects.r.matrix(robjects.IntVector(range(4)), nrow=2) mtcp = m.tcrossprod(m) for i,val in enumerate((4,6,6,10,)): assert mtcp[i] == val def test_svd(): m = robjects.r.matrix(robjects.IntVector((1, -1, -1, 1)), nrow=2) res = m.svd() for i,val in enumerate(res.rx2("d")): assert almost_equal((2, 0)[i], val) def test_eigen(): m = robjects.r.matrix(robjects.IntVector((1, -1, -1, 1)), nrow=2) res = m.eigen() for i, val in enumerate(res.rx2("values")): assert (2, 0)[i] == val def test_dot(): m = robjects.r.matrix(robjects.IntVector(range(4)), nrow=2, ncol=2) m2 = m.dot(m) assert tuple(m2) == (2,3,6,11) def test_colnames(): m = robjects.r.matrix(robjects.IntVector(range(4)), nrow=2, ncol=2) assert m.colnames == rinterface.NULL m.colnames = robjects.StrVector(('a', 'b')) assert len(m.colnames) == 2 assert m.colnames[0] == 'a' assert m.colnames[1] == 'b' with pytest.raises(ValueError): m.colnames = robjects.StrVector(('a', 'b', 'c')) def test_rownames(): m = robjects.r.matrix(robjects.IntVector(range(4)), nrow=2, ncol=2) assert m.rownames == rinterface.NULL m.rownames = robjects.StrVector(('c', 'd')) assert len(m.rownames) == 2 assert m.rownames[0] == 'c' assert m.rownames[1] == 'd' with pytest.raises(ValueError): m.rownames = robjects.StrVector(('a', 'b', 'c')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_conversion.py0000644000175100001770000001450514543120705021563 0ustar00runnerdockerimport array import pytest import rpy2.rinterface_lib.sexp from rpy2 import rinterface from rpy2 import robjects from rpy2.robjects import conversion @pytest.mark.parametrize( 'cls, values, expected_cls', [(rinterface.IntSexpVector, (1, 2), robjects.vectors.IntVector), (rinterface.FloatSexpVector, (1.1, 2.2), robjects.vectors.FloatVector), (rinterface.StrSexpVector, ('ab', 'cd'), robjects.vectors.StrVector), (rinterface.BoolSexpVector, (True, False), robjects.vectors.BoolVector), (rinterface.ByteSexpVector, b'ab', robjects.vectors.ByteVector), (lambda x: rinterface.evalr(x), 'y ~ x', robjects.Formula)]) def test_sexpvector_to_ro(cls, values, expected_cls): v_ri = cls(values) v_ro = robjects.default_converter.rpy2py(v_ri) assert isinstance(v_ro, expected_cls) def test_mapperR2Python_function(): sexp = rinterface.globalenv.find('plot') ob = robjects.default_converter.rpy2py(sexp) assert isinstance(ob, robjects.Function) def test_mapperR2Python_environment(): sexp = rinterface.globalenv.find('.GlobalEnv') assert isinstance(robjects.default_converter.rpy2py(sexp), robjects.Environment) def test_mapperR2Python_lang(): sexp = rinterface.baseenv['str2lang']('1+2') ob = robjects.default_converter.rpy2py(sexp) assert isinstance(ob, robjects.language.LangVector) def test_mapperR2Python_Date(): sexp = rinterface.baseenv.find('as.Date')('2020-01-01') assert isinstance(robjects.default_converter.rpy2py(sexp), robjects.vectors.DateVector) def test_NameClassMap(): ncm = conversion.NameClassMap(object) classnames = ('A', 'B') assert ncm.find_key(classnames) is None assert ncm.find(classnames) is object ncm['B'] = list assert ncm.find_key(classnames) == 'B' assert ncm.find(classnames) is list ncm['A'] = tuple assert ncm.find_key(classnames) == 'A' assert ncm.find(classnames) is tuple def test_NameClassMapContext(): ncm = conversion.NameClassMap(object) assert not len(ncm._map) with conversion.NameClassMapContext(ncm, {}): assert not len(ncm._map) assert not len(ncm._map) with conversion.NameClassMapContext(ncm, {'A': list}): assert set(ncm._map.keys()) == set('A') assert not len(ncm._map) ncm['B'] = tuple with conversion.NameClassMapContext(ncm, {'A': list}): assert set(ncm._map.keys()) == set('AB') assert set(ncm._map.keys()) == set('B') with conversion.NameClassMapContext(ncm, {'B': list}): assert set(ncm._map.keys()) == set('B') assert set(ncm._map.keys()) == set('B') assert ncm['B'] is tuple def test_Converter_rclass_map_context(): converter = robjects.default_converter class FooEnv(robjects.Environment): pass ncm = converter.rpy2py_nc_name[rinterface.SexpEnvironment] with robjects.default_converter.rclass_map_context( rinterface.SexpEnvironment, {'A': FooEnv} ): assert set(ncm._map.keys()) == set('A') assert not len(ncm._map) @pytest.fixture(scope='module') def _set_class_AB(): robjects.r('A <- methods::setClass("A", representation(x="integer"))') robjects.r('B <- methods::setClass("B", contains="A")') yield robjects.r('methods::removeClass("B")') robjects.r('methods::removeClass("A")') def test_mapperR2Python_s4(_set_class_AB): classname = rinterface.StrSexpVector(['A', ]) one = rinterface.IntSexpVector([1, ]) sexp = rinterface.globalenv['A'](x=one) assert isinstance(robjects.default_converter.rpy2py(sexp), robjects.RS4) def test_mapperR2Python_s4custom(_set_class_AB): class A(robjects.RS4): pass sexp_a = rinterface.globalenv['A']( x=rinterface.IntSexpVector([1, ]) ) sexp_b = rinterface.globalenv['B']( x=rinterface.IntSexpVector([2, ]) ) rs4_map = (conversion.get_conversion() .rpy2py_nc_name[rinterface.SexpS4]) with conversion.NameClassMapContext( rs4_map, {'A': A} ): assert rs4_map.find_key(('A', )) == 'A' assert isinstance( robjects.default_converter.rpy2py(sexp_a), A) assert rs4_map.find_key(('B', 'A')) == 'A' assert isinstance( robjects.default_converter.rpy2py(sexp_b), A) @pytest.mark.parametrize('value,cls', [ (1, int), (True, bool), (b'houba', bytes), (1.0, float), (1.0 + 2j, complex) ]) def test_py2ro_mappedtype(value, cls): pyobj = value assert isinstance(pyobj, cls) rob = robjects.default_converter.py2rpy(pyobj) assert isinstance(rob, cls) def test_mapperPy2R_taggedlist(): py = robjects.rlc.TaggedList(('a', 'b'), tags=('foo', 'bar')) robj = robjects.default_converter.py2rpy(py) assert isinstance(robj, robjects.Vector) assert len(robj) == 2 assert tuple(robj.names) == ('foo', 'bar') def test_mapperPy2R_function(): func = lambda x: x rob = robjects.default_converter.py2rpy(func) assert isinstance(rob, robjects.SignatureTranslatedFunction) assert rob.typeof == rinterface.RTYPES.CLOSXP @pytest.mark.parametrize('ctype', ['h', 'H', 'i', 'I']) def test_mapperpy2rpy_int_array(ctype): a = array.array(ctype, range(10)) rob = robjects.default_converter.py2rpy(a) assert isinstance(rob, robjects.vectors.IntSexpVector) assert isinstance(rob, robjects.vectors.IntVector) assert rob.typeof == rinterface.RTYPES.INTSXP @pytest.mark.parametrize('ctype', ['d', 'f']) def test_mapperpy2rpy_float_array(ctype): a = array.array(ctype, (1.1, 2.2, 3.3)) rob = robjects.default_converter.py2rpy(a) assert isinstance(rob, robjects.vectors.FloatSexpVector) assert isinstance(rob, robjects.vectors.FloatVector) assert rob.typeof == rinterface.RTYPES.REALSXP def test_missingconversion(): with conversion.localconverter(conversion.missingconverter) as cv: with pytest.raises(NotImplementedError): cv.py2rpy(1) with pytest.raises(NotImplementedError): cv.rpy2py(robjects.globalenv) def noconversion(): robj_res = robjects.baseenv['pi'] assert isinstance(robj_res, robjects.RObject) rint_res = robject.conversion.noconversion(robj_res) assert isinstance(rint_res, rpy2.rinterface_lib.sexp.Sexp) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_conversion_numpy.py0000644000175100001770000002504414543120705023013 0ustar00runnerdockerimport pytest import sys from rpy2 import robjects from rpy2 import rinterface import rpy2.rlike.container import rpy2.robjects.conversion as conversion r = robjects.r class DummyNamespace(object): def __getattr__(self, name): return None has_numpy = False try: import numpy has_numpy = True import rpy2.robjects.numpy2ri as rpyn except: numpy = DummyNamespace() @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') class TestNumpyConversions(object): def check_homogeneous(self, obj, mode, storage_mode): with (robjects.default_converter + rpyn.converter).context() as cv: converted = cv.py2rpy(obj) assert r["mode"](converted)[0] == mode assert r["storage.mode"](converted)[0] == storage_mode assert list(obj) == list(converted) assert r["is.array"](converted)[0] is True return converted def test_vector_boolean(self): l = [True, False, True] b = numpy.array(l, dtype=numpy.bool_) b_r = self.check_homogeneous(b, "logical", "logical") assert tuple(l) == tuple(b_r) def test_vector_integer_py2rpy(self): l = [1, 2, 3] i = numpy.array(l, dtype="i") i_r = self.check_homogeneous(i, "numeric", "integer") assert tuple(l) == tuple(i_r) def test_vector_integer_rpy2py(self): l = [1, 2, 3] i = rinterface.IntSexpVector(l) with (robjects.default_converter + rpyn.converter).context() as cv: converted = cv.rpy2py(i) assert isinstance(converted, numpy.ndarray) assert tuple(l) == tuple(converted) def test_factor_integer_rpy2py(self): l = ['a', 'b', 'a'] f = robjects.FactorVector(l) with (robjects.default_converter + rpyn.converter).context() as cv: converted = cv.rpy2py(f) assert isinstance(converted, numpy.ndarray) assert tuple(l) == tuple(converted) def test_vector_float(self): l = [1.0, 2.0, 3.0] f = numpy.array(l, dtype="f") f_r = self.check_homogeneous(f, "numeric", "double") for orig, conv in zip(l, f_r): assert abs(orig-conv) < 0.000001 def test_vector_complex(self): l = [1j, 2j, 3j] c = numpy.array(l, dtype=numpy.complex_) c_r = self.check_homogeneous(c, "complex", "complex") for orig, conv in zip(l, c_r): assert abs(orig.real-conv.real) < 0.000001 assert abs(orig.imag-conv.imag) < 0.000001 def test_vector_unicode_character(self): l = [u"a", u"c", u"e"] u = numpy.array(l, dtype="U") u_r = self.check_homogeneous(u, "character", "character") assert tuple(l) == tuple(u_r) def test_vector_bytes(self): l = [b'a', b'b', b'c'] s = numpy.array(l, dtype = '|S1') with (robjects.default_converter + rpyn.converter).context() as cv: converted = cv.py2rpy(s) assert r["mode"](converted)[0] == 'raw' assert r["storage.mode"](converted)[0] == 'raw' assert bytearray(b''.join(l)) == bytearray(converted) def test_array(self): i2d = numpy.array([[1, 2, 3], [4, 5, 6]], dtype='i') with (robjects.default_converter + rpyn.converter).context() as cv: i2d_r = cv.py2rpy(i2d) assert r['storage.mode'](i2d_r)[0] == 'integer' assert tuple(r['dim'](i2d_r)) == (2, 3) # Make sure we got the row/column swap right: assert r['['](i2d_r, 1, 2)[0] == i2d[0, 1] f3d = numpy.arange(24, dtype='f').reshape((2, 3, 4)) with (robjects.default_converter + rpyn.converter).context() as cv: f3d_r = cv.py2rpy(f3d) assert r['storage.mode'](f3d_r)[0] == 'double' assert tuple(r['dim'](f3d_r)) == (2, 3, 4) # Make sure we got the row/column swap right: #assert r['['](f3d_r, 1, 2, 3)[0] == f3d[0, 1, 2] @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') @pytest.mark.parametrize( 'constructor', (numpy.int32, numpy.int64, numpy.uint32, numpy.uint64) ) def test_scalar_int(self, constructor): np_value = constructor(100) with (robjects.default_converter + rpyn.converter).context() as cv: r_vec = cv.py2rpy(np_value) r_scalar = numpy.array(r_vec)[0] assert np_value == r_scalar @pytest.mark.skipif(not (has_numpy and hasattr(numpy, 'float128')), reason='numpy.float128 not available on this system') def test_scalar_f128(self): f128 = numpy.float128(100.000000003) with (robjects.default_converter + rpyn.converter).context() as cv: f128_r = cv.py2rpy(f128) f128_test = numpy.array(f128_r)[0] assert f128 == f128_test def test_object_array(self): o = numpy.array([1, "a", 3.2], dtype=numpy.object_) with (robjects.default_converter + rpyn.converter).context() as cv: o_r = cv.py2rpy(o) assert r['mode'](o_r)[0] == 'list' assert r['[['](o_r, 1)[0] == 1 assert r['[['](o_r, 2)[0] == 'a' assert r['[['](o_r, 3)[0] == 3.2 def test_record_array(self): rec = numpy.array([(1, 2.3), (2, -0.7), (3, 12.1)], dtype=[("count", "i"), ("value", numpy.double)]) with (robjects.default_converter + rpyn.converter).context() as cv: rec_r = cv.py2rpy(rec) assert r["is.data.frame"](rec_r)[0] is True assert tuple(r["names"](rec_r)) == ("count", "value") count_r = rec_r[rec_r.names.index('count')] value_r = rec_r[rec_r.names.index('value')] assert r["storage.mode"](count_r)[0] == 'integer' assert r["storage.mode"](value_r)[0] == 'double' assert count_r[1] == 2 assert value_r[2] == 12.1 def test_bad_array(self): u = numpy.array([1, 2, 3], dtype=numpy.uint32) with pytest.raises(ValueError): with (robjects.default_converter + rpyn.converter).context() as cv: cv.py2rpy(u) def test_assign_numpy_object(self): x = numpy.arange(-10., 10., 1) env = robjects.Environment() with (robjects.default_converter + rpyn.converter).context() as cv: env['x'] = x assert len(env) == 1 # do have an R object of the right type ? with robjects.default_converter.context() as lc: x_r = env['x'] assert robjects.rinterface.RTYPES.REALSXP == x_r.typeof # assert tuple(x_r.dim) == (20,) def test_dataframe_to_numpy(self): df = robjects.vectors.DataFrame( {'a': 1, 'b': 2, 'c': robjects.vectors.FactorVector('e'), 'd': robjects.vectors.StrVector(['xyz'])}) with conversion.localconverter(robjects.default_converter + rpyn.converter) as cv: rec = cv.rpy2py(df) assert numpy.recarray == type(rec) assert rec.a[0] == 1 assert rec.b[0] == 2 assert rec.c[0] == 'e' assert rec.d[0] == 'xyz' @pytest.mark.parametrize( 'cls', (robjects.ListVector, rinterface.ListSexpVector) ) def test_rlist_to_numpy(self, cls): df = cls( robjects.ListVector( {'a': 1, 'b': 2, 'c': robjects.vectors.FactorVector('e')} ) ) with (robjects.default_converter + rpyn.converter).context() as cv: rec = cv.rpy2py(df) assert rpy2.rlike.container.OrdDict == type(rec) assert rec['a'][0] == 1 assert rec['b'][0] == 2 assert rec['c'][0] == 'e' # not 1. def test_atomic_vector_to_numpy(self): v = robjects.vectors.IntVector((1,2,3)) with rpyn.converter.context() as cv: a = cv.rpy2py(v) assert isinstance(a, numpy.ndarray) assert v[0] == 1 def test_rx2(self): df = robjects.vectors.DataFrame({ "A": robjects.vectors.IntVector([1,2,3]), "B": robjects.vectors.IntVector([1,2,3])}) with (robjects.default_converter + rpyn.converter).context() as cv: b = df.rx2('B') assert tuple((1,2,3)) == tuple(b) def test_rint_to_numpy(self): with (robjects.default_converter + rpyn.converter).context() as cv: a = robjects.r('c(1:4)') assert isinstance(a, numpy.ndarray) def test_rfloat_to_numpy(self): with (robjects.default_converter + rpyn.converter).context() as cv: a = robjects.r('c(1.0, 2.0, 3.0)') assert isinstance(a, numpy.ndarray) @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') @pytest.mark.parametrize('dtype', ('uint32', 'uint64')) def test_unsignednumpyint_to_rint_error(dtype): values = (1,2,3) a = numpy.array(values, dtype=dtype) with pytest.raises(ValueError): rpyn.unsignednumpyint_to_rint(a) @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') @pytest.mark.parametrize('dtype', ('uint8', 'uint16')) def test_unsignednumpyint_to_rint(dtype): values = (1,2,3) a = numpy.array(values, dtype=dtype) v = rpyn.unsignednumpyint_to_rint(a) assert values == tuple(v) @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') @pytest.mark.parametrize('values,expected_cls', ((['a', 1, 2], robjects.vectors.ListVector), (['a', 'b', 'c'], rinterface.StrSexpVector), ([b'a', b'b', b'c'], rinterface.ByteSexpVector))) def test_numpy_O_py2rpy(values, expected_cls): a = numpy.array(values, dtype='O') v = rpyn.numpy_O_py2rpy(a) assert isinstance(v, expected_cls) @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') @pytest.mark.parametrize( 'rcode,expected_values', (('c(TRUE, FALSE)', (True, False, )), ('c(1, 2, 3)', (1, 2, 3)), ('c(1.0, 2.0, 3.0)', (1.0, 2.0, 3.0)), ('c("ab", "cd", NA_character_)', ('ab', 'cd', None))) ) def test_rpy2py(rcode, expected_values): with (robjects.default_converter + rpyn.converter).context(): values = robjects.r(rcode) assert tuple(values) == expected_values @pytest.mark.skipif(not has_numpy, reason='package numpy cannot be imported') def test_NA_CharSexp(): with (robjects.default_converter + rpyn.converter).context(): values = robjects.r('c(NA_character_)') assert values[0] is None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_dataframe.py0000644000175100001770000001127414543120705021322 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects rinterface = robjects.rinterface import rpy2.rlike.container as rlc import array import csv import os import tempfile def test_init_from_taggedlist(): letters = robjects.r.letters numbers = robjects.r('1:26') df = robjects.DataFrame(rlc.TaggedList((letters, numbers), tags = ('letters', 'numbers'))) assert df.rclass[0] == 'data.frame' def test_init_from_RObject(): numbers = robjects.r('1:5') dataf = robjects.DataFrame(numbers) assert len(dataf) == 5 assert all(len(x) == 1 for x in dataf) rfunc = robjects.r('sum') with pytest.raises(ValueError): robjects.DataFrame(rfunc) rdataf = robjects.r('data.frame(a=1:2, b=c("a", "b"))') dataf = robjects.DataFrame(rdataf) # TODO: test ? def test_init_from_OrdDict(): od = rlc.OrdDict(c=(('a', robjects.IntVector((1,2))), ('b', robjects.StrVector(('c', 'd'))) )) dataf = robjects.DataFrame(od) assert dataf.rx2('a')[0] == 1 def test_init_from_dict(): od = {'a': robjects.IntVector((1,2)), 'b': robjects.StrVector(('c', 'd'))} dataf = robjects.DataFrame(od) assert dataf.rx2('a')[0] == 1 def test_init_stringsasfactors(): od = {'a': robjects.IntVector((1,2)), 'b': robjects.StrVector(('c', 'd'))} dataf = robjects.DataFrame(od, stringsasfactor=True) assert isinstance(dataf.rx2('b'), robjects.FactorVector) dataf = robjects.DataFrame(od, stringsasfactor=False) assert isinstance(dataf.rx2('b'), robjects.StrVector) def test_dim(): letters = robjects.r.letters numbers = robjects.r('1:26') df = robjects.DataFrame(rlc.TaggedList((letters, numbers), tags = ('letters', 'numbers'))) assert df.nrow == 26 assert df.ncol == 2 def test_from_csvfile(): column_names = ('letter', 'value') data = (column_names, ('a', 1), ('b', 2), ('c', 3)) fh = tempfile.NamedTemporaryFile(mode = "w", delete = False) csv_w = csv.writer(fh) csv_w.writerows(data) fh.close() dataf = robjects.DataFrame.from_csvfile(fh.name) assert isinstance(dataf, robjects.DataFrame) assert column_names == tuple(dataf.names) assert dataf.nrow == 3 assert dataf.ncol == 2 def test_to_csvfile(): fh = tempfile.NamedTemporaryFile(mode = "w", delete = False) fh.close() d = {'letter': robjects.StrVector('abc'), 'value' : robjects.IntVector((1, 2, 3))} dataf = robjects.DataFrame(d) dataf.to_csvfile(fh.name) dataf = robjects.DataFrame.from_csvfile(fh.name) assert dataf.nrow == 3 assert dataf.ncol == 2 def test_iter_col(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') col_types = [x.typeof for x in dataf.iter_column()] assert rinterface.RTYPES.INTSXP == col_types[0] assert rinterface.RTYPES.STRSXP == col_types[1] def test_iter_row(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') rows = [x for x in dataf.iter_row()] assert rows[0][0][0] == 1 assert rows[1][1][0] == 'b' def test_colnames(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') assert dataf.rownames[0] == '1' assert dataf.rownames[1] == '2' def test_colnames_set(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') dataf.colnames = robjects.StrVector('de') assert tuple(dataf.colnames) == ('d', 'e') def test_rownames(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') assert tuple(dataf.colnames) == ('a', 'b') def test_rownames_set(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') dataf.rownames = robjects.StrVector('de') assert tuple(dataf.rownames) == ('d', 'e') def test_cbind(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') dataf = dataf.cbind(robjects.r('data.frame(a=1:2, b=I(c("a", "b")))')) assert dataf.ncol == 4 assert len([x for x in dataf.colnames if x == 'a']) == 2 dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') dataf = dataf.cbind(a = robjects.StrVector(("c", "d"))) assert dataf.ncol == 3 assert len([x for x in dataf.colnames if x == 'a']) == 2 def test_rbind(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') dataf = dataf.rbind(dataf) assert dataf.ncol == 2 assert dataf.nrow == 4 def test_head(): dataf = robjects.r('data.frame(a=1:26, b=I(letters))') assert dataf.head(5).nrow == 5 assert dataf.head(5).ncol == 2 def test_repr(): dataf = robjects.r('data.frame(a=1:2, b=I(c("a", "b")))') s = repr(dataf) assert 'data.frame' in s.split(os.linesep)[1] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_environment.py0000644000175100001770000000562414543120705021744 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects rinterface = robjects.rinterface import array def test_init_empty(): env = robjects.Environment() assert env.typeof == rinterface.RTYPES.ENVSXP def test_init_invalid(): with pytest.raises(ValueError): robjects.Environment('a') def test_getsetitem(): env = robjects.Environment() env['a'] = 123 assert 'a' in env a = env['a'] assert len(a) == 1 assert a[0] == 123 def test_keys(): env = robjects.Environment() env['a'] = 123 env['b'] = 234 keys = list(env.keys()) assert len(keys) == 2 keys.sort() for it_a, it_b in zip(keys, ('a', 'b')): assert it_a == it_b def test_values(): env = robjects.Environment() env['a'] = 123 env['b'] = 234 values = list(env.values()) assert len(values) == 2 values.sort(key=lambda x: x[0]) for it_a, it_b in zip(values, (123, 234)): assert len(it_a) == 1 assert it_a[0] == it_b def test_items(): env = robjects.Environment() env['a'] = 123 env['b'] = 234 items = list(env.items()) assert len(items) == 2 items.sort(key=lambda x: x[0]) for it_a, it_b in zip(items, (('a', 123), ('b', 234))): assert it_a[0] == it_b[0] assert it_a[1][0] == it_b[1] def test_pop_key(): env = robjects.Environment() env['a'] = 123 env['b'] = 456 robjs = [] assert len(env) == 2 robjs.append(env.pop('a')) assert len(env) == 1 robjs.append(env.pop('b')) assert len(env) == 0 assert [x[0] for x in robjs] == [123, 456] with pytest.raises(KeyError): env.pop('c') assert env.pop('c', 789) == 789 with pytest.raises(ValueError): env.pop('c', 1, 2) def test_popitem(): env = robjects.Environment() env['a'] = 123 env['b'] = 456 robjs = [] assert len(env) == 2 robjs.append(env.popitem()) assert len(env) == 1 robjs.append(env.popitem()) assert len(env) == 0 assert sorted([(k, v[0]) for k, v in robjs]) == [('a', 123), ('b', 456)] with pytest.raises(KeyError): robjs.append(env.popitem()) def test_clear(): env = robjects.Environment() env['a'] = 123 env['b'] = 234 assert len(env) == 2 env.clear() assert len(env) == 0 @pytest.mark.parametrize('use_rlock', (True, False)) def test_call_in_context_nested(use_rlock): ls = robjects.baseenv['ls'] get = robjects.baseenv['get'] assert 'foo' not in ls() with robjects.environments.local_context() as lc_a: lc_a['foo'] = 123 assert tuple(get('foo')) == (123, ) with robjects.environments.local_context(use_rlock=use_rlock) as lc_b: lc_b['foo'] = 456 assert tuple(get('foo')) == (456, ) assert tuple(get('foo')) == (123, ) assert 'foo' not in ls() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_formula.py0000644000175100001770000000133214543120705021035 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects rinterface = robjects.rinterface def test_init(): fml = robjects.Formula('y ~ x') assert fml.rclass[0] == 'formula' def test_getenvironment(): fml = robjects.Formula('y ~ x') env = fml.getenvironment() assert env.rclass[0] == 'environment' def test_setenvironment(): fml = robjects.Formula('y ~ x') newenv = robjects.baseenv['new.env']() env = fml.getenvironment() assert not newenv.rsame(env) fml.setenvironment(newenv) env = fml.getenvironment() assert newenv.rsame(env) def test_setenvironment_error(): fml = robjects.Formula('y ~ x') with pytest.raises(TypeError): fml.setenvironment(rinterface.NA_Logical) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_function.py0000644000175100001770000001163514543120705021224 0ustar00runnerdockerimport pytest import inspect import rpy2.robjects as robjects rinterface = robjects.rinterface import array identical = rinterface.baseenv['identical'] Function = robjects.functions.Function def test_init_invalid(): with pytest.raises(ValueError): Function('a') def test_init_from_existing(): ri_f = rinterface.baseenv.find('sum') ro_f = Function(ri_f) assert identical(ri_f, ro_f)[0] == True def test_call_with_sexp(): ri_f = rinterface.baseenv.find('sum') ro_f = Function(ri_f) ro_v = robjects.IntVector(array.array('i', [1,2,3])) s = ro_f(ro_v) assert s[0] == 6 @pytest.mark.parametrize( 'rcode,funcnames', (('function(x, y) TRUE', ('x', 'y')), ('.C', None), ('`if`', None)) ) def test_formals(rcode, funcnames): ri_f = robjects.r(rcode) res = ri_f.formals() #FIXME: no need for as.list when paired list are handled if funcnames: res = robjects.r['as.list'](res) assert len(res) == len(funcnames) assert funcnames == tuple(res.names) else: res is None @pytest.mark.parametrize('rcode', ('function(x, y) TRUE', 'function() TRUE')) def test_function(rcode): r_func = robjects.functions.Function(robjects.r(rcode)) assert isinstance(r_func.__doc__, str) @pytest.mark.parametrize('rcode', ('function(x, y) TRUE', 'function() TRUE')) def test_signaturestranslatedfunction(rcode): r_func = robjects.r(rcode) stf = robjects.functions.SignatureTranslatedFunction(r_func) assert isinstance(r_func.__doc__, str) @pytest.mark.parametrize('name', ('sum', 'Sys.Date', 'append')) def test_documentedstfunction(name): dstf = robjects.functions.DocumentedSTFunction(robjects.baseenv[name], packagename='base') assert isinstance(dstf.__doc__, str) @pytest.mark.parametrize( 'value, expected', ((robjects.r('TRUE'), True), (robjects.r('1'), 1), (robjects.r('"abc"'), 'abc')) ) def test_map_default_values(value, expected): assert robjects.functions._map_default_value(value) == expected @pytest.mark.parametrize( 'r_code,parameter_names,r_ellipsis', ( ('function(x, y=FALSE, z="abc") TRUE', ('x', 'y', 'z'), None), ('function(x, y=FALSE, z="abc") {TRUE}', ('x', 'y', 'z'), None), ('function(x, ..., y=FALSE, z="abc") TRUE', ('x', '___', 'y', 'z'), 1), ) ) def test_map_signature(r_code, parameter_names, r_ellipsis): r_func = robjects.r(r_code) stf = robjects.functions.SignatureTranslatedFunction(r_func) signature, r_ellipsis = robjects.functions.map_signature(r_func) assert tuple(signature.parameters.keys()) == parameter_names @pytest.mark.parametrize( 'r_code,parameter_names', ( ('function(x, y=FALSE, z, ...) TRUE', ('x', 'y', 'z', '___')), ) ) def test_map_signature_invalid(r_code, parameter_names): r_func = robjects.r(r_code) stf = robjects.functions.SignatureTranslatedFunction(r_func) with pytest.raises(ValueError): signature, r_ellipsis = robjects.functions.map_signature(stf) @pytest.mark.parametrize( 'r_code,args,kwargs,expected', ( ('function(x, y=1, z=2) {sum(x, y, z)}', (3, ), {}, 6), ('function(x, y=1, z=2) {sum(x, y, z)}', (3, 4), {}, 9), ('function(...) {sum(...)}', (3, 2, 4), {}, 9), ('function(x, ...) {sum(x, ...)}', (3, 2, 4), {}, 9), ('function(x, ..., z=1) {sum(x, ..., z)}', (3, 2, 4), {}, 10), ('function(x, ..., z=1) {sum(x, ..., z)}', (3, 2, 4), {'z': 2}, 11), ) ) def test_wrap_r_function_args(r_code, args, kwargs, expected): r_func = robjects.r(r_code) stf = robjects.functions.SignatureTranslatedFunction(r_func) w_func = robjects.functions.wrap_r_function(stf, 'foo') res = w_func(*args, **kwargs) assert tuple(res) == (expected, ) @pytest.mark.parametrize('is_method', (True, False)) def test_wrap_r_function(is_method): r_code = 'function(x, y=FALSE, z="abc") TRUE' parameter_names = ('self', 'x', 'y', 'z') if is_method else ('x', 'y', 'z') r_func = robjects.r(r_code) foo = robjects.functions.wrap_r_function(r_func, 'foo', is_method=is_method) assert inspect.getclosurevars(foo).nonlocals['r_func'].rid == r_func.rid assert tuple(foo.__signature__.parameters.keys()) == parameter_names if not is_method: res = foo(1) assert res[0] is True @pytest.mark.parametrize('wrap_docstring', (None, robjects.functions.wrap_docstring_default)) def test_wrap_r_function_docstrings(wrap_docstring): r_code = 'function(x, y=FALSE, z="abc") TRUE' r_func = robjects.r(r_code) foo = robjects.functions.wrap_r_function(r_func, 'foo', wrap_docstring=wrap_docstring) # TODO: only an integration test ? Nothing is tested. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_help.py0000644000175100001770000000457214543120705020331 0ustar00runnerdockerimport os import pytest import rpy2.robjects as robjects import rpy2.robjects.help as rh rinterface = robjects.rinterface class TestPackage(object): def test_init(self): base_help = rh.Package('base') assert base_help.name == 'base' def test_repr(self): base_help = rh.Package('base') assert isinstance(repr(base_help), str) class TestPage(object): def test_init(self): base_help = rh.Package('base') p = base_help.fetch('print') assert tuple(p.sections.keys())[0] == '\\title' def test_fetch(self): base_help = rh.Package('base') f = base_help.fetch('print') assert '\\title' in f.sections.keys() def test_to_docstring(self): base_help = rh.Package('base') p = base_help.fetch('print') ds = p.to_docstring() assert ds[:5] == 'title' def test_title(self): base_help = rh.Package('base') p = base_help.fetch('print') d = p.title() assert all(isinstance(x, str) for x in d) assert len(d) > 0 def test_description(self): base_help = rh.Package('base') p = base_help.fetch('print') d = p.description() assert all(isinstance(x, str) for x in d) assert len(d) > 0 def test_seealso(self): base_help = rh.Package('base') p = base_help.fetch('print') d = p.seealso() assert all(isinstance(x, str) for x in d) assert len(d) > 0 def test_usage(self): base_help = rh.Package('base') p = base_help.fetch('print') d = p.usage() assert all(isinstance(x, str) for x in d) assert len(d) > 0 def test_iteritems(self): base_help = rh.Package('base') p = base_help.fetch('print') with pytest.deprecated_call(): res = tuple(p.iteritems()) # TODO: test result more in depth. assert len(res) > 0 def test_iteritems(self): base_help = rh.Package('base') p = base_help.fetch('print') res = tuple(p.items()) # TODO: test result more in depth. assert len(res) > 0 @pytest.mark.xfail( os.name == 'nt', reason='Windows is missing library/translations/Meta/Rd.rds file') def test_pages(): pages = rh.pages('plot') assert isinstance(pages, tuple) assert all(isinstance(elt, rh.Page) for elt in pages) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_language.py0000644000175100001770000000240114543120705021151 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects import rpy2.robjects.language as lg from rpy2 import rinterface from rpy2.rinterface_lib import embedded @pytest.fixture(scope='module') def clean_globalenv(): yield for name in robjects.globalenv.keys(): del robjects.globalenv[name] def test_eval(clean_globalenv): code = """ x <- 1+2 y <- (x+1) / 2 """ res = lg.eval(code) assert 'x' in robjects.globalenv.keys() assert robjects.globalenv['x'][0] == 3 assert 'y' in robjects.globalenv.keys() assert robjects.globalenv['y'][0] == 2 def testeval_in_environment(clean_globalenv): code = """ x <- 1+2 y <- (x+1) / 2 """ env = robjects.Environment() res = lg.eval(code, envir=env) assert 'x' in env.keys() assert env['x'][0] == 3 assert 'y' in env.keys() assert env['y'][0] == 2 def test_LangVector_from_string(): lang_obj = lg.LangVector.from_string('1+2') assert lang_obj.typeof == rinterface.RTYPES.LANGSXP def test_LangVector_repr(): lang_obj = lg.LangVector.from_string('1+2') assert repr(lang_obj) == 'Rlang( 1 + 2 )' def test_LangVector_from_string_invalid(): with pytest.raises(embedded.RRuntimeError): lang_obj = lg.LangVector.from_string(1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_methods.py0000644000175100001770000000567614543120705021052 0ustar00runnerdockerimport pytest import sys import textwrap import rpy2.robjects as robjects import rpy2.robjects.methods as methods rinterface = robjects.rinterface def test_set_accessors(): robjects.r['setClass']("A", robjects.r('list(foo="numeric")')) robjects.r['setMethod']("length", signature="A", definition = robjects.r("function(x) 123")) class A(methods.RS4): def __init__(self): obj = robjects.r['new']('A') super().__init__(obj) acs = (('length', None, True, None), ) methods.set_accessors(A, "A", None, acs) a = A() assert a.length[0] == 123 def test_RS4Type_noaccessors(): robjects.r['setClass']("Foo", robjects.r('list(foo="numeric")')) classdef = """ from rpy2 import robjects from rpy2.robjects import methods class Foo(methods.RS4, metaclass=methods.RS4_Type): def __init__(self): obj = robjects.r['new']('Foo') super().__init__(obj) """ code = compile(textwrap.dedent(classdef), '', 'exec') ns = dict() exec(code, ns) f = ns['Foo']() # TODO: test ? def test_RS4_factory(): rclassname = 'Foo' robjects.r['setClass'](rclassname, robjects.r('list(bar="numeric")')) obj = robjects.r['new'](rclassname) f_rs4i = methods.rs4instance_factory(obj) assert rclassname == type(f_rs4i).__name__ def test_RS4Type_accessors(): robjects.r['setClass']("R_A", robjects.r('list(foo="numeric")')) robjects.r['setMethod']("length", signature="R_A", definition = robjects.r("function(x) 123")) classdef = """ from rpy2 import robjects from rpy2.robjects import methods class R_A(methods.RS4, metaclass=methods.RS4_Type): __accessors__ = ( ('length', None, 'get_length', False, 'get the length'), ('length', None, None, True, 'length')) def __init__(self): obj = robjects.r['new']('R_A') super().__init__(obj) """ code = compile(textwrap.dedent(classdef), '', 'exec') ns = dict() exec(code, ns) R_A = ns['R_A'] class A(R_A): __rname__ = 'R_A' ra = R_A() assert ra.get_length()[0] == 123 assert ra.length[0] == 123 a = A() assert a.get_length()[0] == 123 assert a.length[0] == 123 def test_getclassdef(): robjects.r('library(stats4)') cr = methods.getclassdef('mle', packagename='stats4') assert not cr.virtual def test_RS4Auto_Type(): robjects.r('library(stats4)') class MLE(object, metaclass=robjects.methods.RS4Auto_Type): __rname__ = 'mle' __rpackagename__ = 'stats4' # TODO: test ? def test_RS4Auto_Type_nopackname(): robjects.r('library(stats4)') class MLE(object, metaclass=robjects.methods.RS4Auto_Type): __rname__ = 'mle' # TODO: test ? ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_packages.py0000644000175100001770000001532714543120705021157 0ustar00runnerdockerimport io import os import pytest import sys import rpy2.robjects as robjects import rpy2.robjects.help import rpy2.robjects.packages as packages import rpy2.robjects.packages_utils from rpy2.rinterface_lib.embedded import RRuntimeError rinterface = robjects.rinterface class TestPackage(object): def tests_package_from_env(self): env = robjects.Environment() env['a'] = robjects.StrVector('abcd') env['b'] = robjects.IntVector((1,2,3)) env['c'] = robjects.r(''' function(x) x^2''') pck = robjects.packages.Package(env, "dummy_package") assert isinstance(pck.a, robjects.Vector) assert isinstance(pck.b, robjects.Vector) assert isinstance(pck.c, robjects.Function) def test_new_with_dot(self): env = robjects.Environment() env['a.a'] = robjects.StrVector('abcd') env['b'] = robjects.IntVector((1,2,3)) env['c'] = robjects.r(''' function(x) x^2''') pck = robjects.packages.Package(env, "dummy_package") assert isinstance(pck.a_a, robjects.Vector) assert isinstance(pck.b, robjects.Vector) assert isinstance(pck.c, robjects.Function) def test_new_with_dot_conflict(self): env = robjects.Environment() env['a.a_a'] = robjects.StrVector('abcd') env['a_a.a'] = robjects.IntVector((1,2,3)) env['c'] = robjects.r(''' function(x) x^2''') with pytest.raises(packages.LibraryError): robjects.packages.Package(env, "dummy_package") def test_new_with_dot_conflict2(self): env = robjects.Environment() name_in_use = dir(packages.Package(env, "foo"))[0] env[name_in_use] = robjects.StrVector('abcd') with pytest.raises(packages.LibraryError): robjects.packages.Package(env, "dummy_package") def tests_package_repr(self): env = robjects.Environment() pck = robjects.packages.Package(env, "dummy_package") assert isinstance(repr(pck), str) def test_signaturetranslatedanonymouspackage(): rcode = """ square <- function(x) { return(x^2) } cube <- function(x) { return(x^3) } """ powerpack = packages.STAP(rcode, "powerpack") assert hasattr(powerpack, 'square') assert hasattr(powerpack, 'cube') def test_installedstpackage_docstring(): stats = robjects.packages.importr('stats', on_conflict='warn') assert stats.__doc__.startswith('Python representation of an R package.') def test_installedstpackage_docstring_no_rname(): stats = robjects.packages.importr('stats', on_conflict='warn') stats.__rname__ = None assert stats.__doc__.startswith( 'Python representation of an R package.%s' '' % os.linesep) class TestImportr(object): def test_importr_stats(self): stats = robjects.packages.importr('stats', on_conflict='warn') assert isinstance(stats, robjects.packages.Package) def test_import_stats_with_libloc(self): path = os.path.dirname( robjects.packages_utils.get_packagepath('stats') ) stats = robjects.packages.importr('stats', on_conflict='warn', lib_loc = path) assert isinstance(stats, robjects.packages.Package) def test_import_stats_with_libloc_and_suppressmessages(self): path = os.path.dirname( robjects.packages_utils.get_packagepath('stats') ) stats = robjects.packages.importr('stats', lib_loc=path, on_conflict='warn', suppress_messages=False) assert isinstance(stats, robjects.packages.Package) def test_import_stats_with_libloc_with_quote(self): path = 'coin"coin' with pytest.raises(robjects.packages.PackageNotInstalledError), \ pytest.warns(UserWarning): Tmp_File = io.StringIO tmp_file = Tmp_File() try: stdout = sys.stdout sys.stdout = tmp_file robjects.packages.importr('dummy_inexistant', lib_loc=path) finally: sys.stdout = stdout tmp_file.close() def test_import_datasets(self): datasets = robjects.packages.importr('datasets') assert isinstance(datasets, robjects.packages.Package) assert isinstance(datasets.__rdata__, robjects.packages.PackageData) assert isinstance(robjects.packages.data(datasets), robjects.packages.PackageData) def test_datatsets_names(self): datasets = robjects.packages.importr('datasets') datasets_data = robjects.packages.data(datasets) datasets_names = tuple(datasets_data.names()) assert len(datasets_names) > 0 assert all(isinstance(x, str) for x in datasets_names) def test_datatsets_fetch(self): datasets = robjects.packages.importr('datasets') datasets_data = robjects.packages.data(datasets) datasets_names = tuple(datasets_data.names()) assert isinstance(datasets_data.fetch(datasets_names[0]), robjects.Environment) with pytest.raises(KeyError): datasets_data.fetch('foo_%s' % datasets_names[0]) def test_wherefrom(): stats = robjects.packages.importr('stats', on_conflict='warn') rnorm_pack = robjects.packages.wherefrom('rnorm') assert rnorm_pack.do_slot('name')[0] == 'package:stats' def test_installedpackages(): instapacks = robjects.packages.InstalledPackages() res = instapacks.isinstalled('foo') assert res is False ncols = len(instapacks.colnames) for row in instapacks: assert ncols == len(row) # TODO: Not really a unit test. The design of the code being # tested (or not tested) probably need to be re-thought. def test_parsedcode(): rcode = '1+2' expression = rinterface.parse(rcode) pc = robjects.packages.ParsedCode(expression) assert isinstance(pc, type(expression)) def test_sourcecode(): rcode = '1+2' expression = rinterface.parse(rcode) sc = robjects.packages.SourceCode(rcode) assert isinstance(sc.parse(), type(expression)) def test_sourcecode_as_namespace(): rcode = '\n'.join( ('x <- 1+2', 'f <- function(x) x+1') ) sc = robjects.packages.SourceCode(rcode) foo_ns = sc.as_namespace('foo_ns') assert hasattr(foo_ns, 'x') assert hasattr(foo_ns, 'f') assert foo_ns.x[0] == 3 assert isinstance(foo_ns.f, rinterface.SexpClosure) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_packages_utils.py0000644000175100001770000000506614543120705022376 0ustar00runnerdockerimport pytest import rpy2.robjects.packages_utils as p_u def test_default_symbol_r2python(): test_values = ( ('foo', 'foo'), ('foo.bar', 'foo_bar'), ('foo_bar', 'foo_bar') ) for provided, expected in test_values: assert expected == p_u.default_symbol_r2python(provided) @pytest.mark.parametrize( 'symbol_mapping,expected_conflict,expected_resolution', [({'foo_bar': ['foo.bar'], 'foo': ['foo']}, {}, {}), ({'foo_bar': ['foo.bar', 'foo_bar'], 'foo': ['foo']}, {}, {'foo_bar': ['foo_bar'], 'foo_bar_': ['foo.bar']}), ({'foo_bar': ['foo.bar', 'foo_bar', 'foo_bar_'], 'foo': ['foo']}, {'foo_bar': ['foo.bar', 'foo_bar', 'foo_bar_']}, {}), ], ) def test_default_symbol_resolve_noconflicts(symbol_mapping, expected_conflict, expected_resolution): conflicts, resolved_mapping = p_u.default_symbol_resolve(symbol_mapping) assert conflicts == expected_conflict assert resolved_mapping == expected_resolution def test___map_symbols(): rnames = ('foo.bar', 'foo_bar', 'foo') translations = {} (symbol_mapping, conflicts, resolutions) = p_u._map_symbols(rnames, translations) expected_symbol_mapping = { 'foo_bar': ['foo.bar', 'foo_bar'], 'foo': ['foo'] } for new_symbol, old_symbols in expected_symbol_mapping.items(): assert symbol_mapping[new_symbol] == old_symbols translations = {'foo.bar': 'foo_dot_bar'} (symbol_mapping, conflicts, resolutions) = p_u._map_symbols(rnames, translations) def test__fix_map_symbols_invalidonconflict(): msg_prefix = '' exception = ValueError symbol_mappings = {'foo': 'foo'} conflicts = {'foo_bar': ['foo.bar', 'foo_bar']} on_conflict = 'foo' with pytest.raises(ValueError): p_u._fix_map_symbols(symbol_mappings, conflicts, on_conflict, msg_prefix, exception) def test__fix_map_symbols_conflictwarn(): msg_prefix = '' exception = ValueError symbol_mappings = {'foo': 'foo'} conflicts = {'foo_bar': ['foo.bar', 'foo_bar']} on_conflict = 'warn' with pytest.warns(UserWarning): p_u._fix_map_symbols(symbol_mappings, conflicts, on_conflict, msg_prefix, exception) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_pandas_conversions.py0000644000175100001770000005305014543120705023272 0ustar00runnerdockerimport math from collections import OrderedDict from datetime import datetime import pytest from rpy2 import rinterface from rpy2 import robjects from rpy2.robjects import vectors from rpy2.robjects import conversion class MockNamespace(object): def __getattr__(self, name): return None has_pandas = False try: import pandas has_pandas = True except: pandas = MockNamespace() has_numpy = False try: import numpy has_numpy = True except: numpy = MockNamespace() if has_pandas: import rpy2.robjects.pandas2ri as rpyp from rpy2.robjects import default_converter from rpy2.robjects.conversion import localconverter @pytest.mark.skipif(not has_pandas, reason='Package pandas is not installed.') class TestPandasConversions(object): def testActivate(self): #FIXME: is the following still making sense ? assert rpyp.py2rpy != robjects.conversion.get_conversion().py2rpy l = len(robjects.conversion.converter_ctx.get().py2rpy.registry) k = set(robjects.conversion.converter_ctx.get().py2rpy.registry.keys()) rpyp.activate() assert len(conversion.converter_ctx.get().py2rpy.registry) > l rpyp.deactivate() assert len(conversion.converter_ctx.get().py2rpy.registry) == l assert set(conversion.converter_ctx.get().py2rpy.registry.keys()) == k def testActivateTwice(self): #FIXME: is the following still making sense ? assert rpyp.py2rpy != robjects.conversion.converter_ctx.get().py2rpy l = len(robjects.conversion.converter_ctx.get().py2rpy.registry) k = set(robjects.conversion.converter_ctx.get().py2rpy.registry.keys()) rpyp.activate() rpyp.deactivate() rpyp.activate() assert len(conversion.converter_ctx.get().py2rpy.registry) > l rpyp.deactivate() assert len(conversion.converter_ctx.get().py2rpy.registry) == l assert set(conversion.converter_ctx.get().py2rpy.registry.keys()) == k @pytest.mark.parametrize( 'cls', (robjects.ListVector, rinterface.ListSexpVector) ) def test_list(self, cls): rlist = cls(robjects.ListVector({'a': 1, 'b': 'c'})) with localconverter(default_converter + rpyp.converter) as cv: pylist = cv.rpy2py(rlist) assert len(pylist) == 2 assert set(pylist.keys()) == set(rlist.names) def test_dataframe(self): # Content for test data frame l = ( ('b', numpy.array([True, False, True], dtype=numpy.bool_)), ('i', numpy.array([1, 2, 3], dtype='i')), ('f', numpy.array([1, 2, 3], dtype='f')), # ('s', numpy.array([b'b', b'c', b'd'], dtype='S1')), ('u', numpy.array([u'a', u'b', u'c'], dtype='U')), ('dates', [datetime(2012, 5, 2), datetime(2012, 6, 3), datetime(2012, 7, 1)]) ) od = OrderedDict(l) # Pandas data frame pd_df = pandas.core.frame.DataFrame(od) # Convert to R with localconverter(default_converter + rpyp.converter) as cv: rp_df = robjects.conversion.converter_ctx.get().py2rpy(pd_df) assert pd_df.shape[0] == rp_df.nrow assert pd_df.shape[1] == rp_df.ncol # assert tuple(rp_df.rx2('s')) == (b'b', b'c', b'd') assert tuple(rp_df.rx2('u')) == ('a', 'b', 'c') def test_dataframe_columnnames(self): pd_df = pandas.DataFrame({'the one': [1, 2], 'the other': [3, 4]}) # Convert to R with localconverter(default_converter + rpyp.converter) as cv: rp_df = robjects.conversion.converter_ctx.get().py2rpy(pd_df) assert tuple(rp_df.names) == ('the one', 'the other') def test_dataframe_warns_duplicated_index(self): pd_df = pandas.DataFrame({'x': [1, 2]}, index=['a', 'a']) with pytest.warns( UserWarning, match='DataFrame contains duplicated elements in the index' ) as record: with localconverter(default_converter + rpyp.converter) as cv: robjects.conversion.converter_ctx.get().py2rpy(pd_df) assert len(record) == 1 def test_series(self): Series = pandas.core.series.Series s = Series(numpy.random.randn(5), index=['a', 'b', 'c', 'd', 'e']) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) assert isinstance(rp_s, rinterface.FloatSexpVector) @pytest.mark.parametrize('dtype', ('i', numpy.int32 if has_pandas else None, numpy.int8 if has_pandas else None, numpy.int16 if has_pandas else None, numpy.int32 if has_pandas else None, numpy.int64 if has_pandas else None, numpy.uint8 if has_pandas else None, numpy.uint16 if has_pandas else None, pandas.Int32Dtype if has_pandas else None, pandas.Int64Dtype if has_pandas else None)) def test_series_int(self, dtype): Series = pandas.core.series.Series s = Series(range(5), index=['a', 'b', 'c', 'd', 'e'], dtype=dtype) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.get_conversion().py2rpy(s) assert isinstance(rp_s, rinterface.IntSexpVector) @pytest.mark.parametrize('dtype', (pandas.Int32Dtype() if has_pandas else None, pandas.Int64Dtype() if has_pandas else None)) def test_dataframe_int_nan(self, dtype): a = pandas.DataFrame([(numpy.NaN,)], dtype=dtype, columns=['z']) with localconverter(default_converter + rpyp.converter) as cv: b = robjects.conversion.get_conversion().py2rpy(a) assert b[0][0] is rinterface.na_values.NA_Integer with localconverter(default_converter + rpyp.converter) as cv: c = robjects.conversion.get_conversion().rpy2py(b) @pytest.mark.parametrize('dtype', (pandas.Int32Dtype() if has_pandas else None, pandas.Int64Dtype() if has_pandas else None)) def test_series_int_nan(self, dtype): a = pandas.Series((numpy.NaN,), dtype=dtype, index=['z']) with localconverter(default_converter + rpyp.converter) as _: b = robjects.conversion.converter_ctx.get().py2rpy(a) assert b[0] is rinterface.na_values.NA_Integer with localconverter(default_converter + rpyp.converter) as _: c = robjects.conversion.converter_ctx.get().rpy2py(b) @pytest.mark.skipif(not (has_numpy and has_pandas), reason='Packages numpy and pandas must be installed.') @pytest.mark.parametrize( 'data', (['x', 'y', 'z'], ['x', 'y', None], ['x', 'y', numpy.nan], ['x', 'y', pandas.NA]) ) @pytest.mark.parametrize( 'dtype', ['O', pandas.StringDtype() if has_pandas else None] ) def test_series_obj_str(self, data, dtype): Series = pandas.core.series.Series s = Series(data, index=['a', 'b', 'c'], dtype=dtype) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) assert isinstance(rp_s, rinterface.StrSexpVector) def test_series_obj_mixed(self): Series = pandas.core.series.Series s = Series(['x', 1, False], index=['a', 'b', 'c']) with localconverter(default_converter + rpyp.converter) as cv: with pytest.raises(ValueError): rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) s = Series(['x', 1, None], index=['a', 'b', 'c']) with localconverter(default_converter + rpyp.converter) as cv: with pytest.raises(ValueError): rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) @pytest.mark.skipif(not has_pandas, reason='pandas must be installed.') @pytest.mark.parametrize( 'data', ([True, False, True], [True, False, None]) ) @pytest.mark.parametrize( 'dtype', [bool, pandas.BooleanDtype() if has_pandas else None] ) @pytest.mark.parametrize( 'constructor,wrapcheck', [(pandas.Series, lambda x: x), (pandas.DataFrame, lambda x: x[0])] ) def test_series_obj_bool(self, data, dtype, constructor, wrapcheck): s = constructor(data, index=['a', 'b', 'c'], dtype=dtype) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) assert isinstance(wrapcheck(rp_s), rinterface.BoolSexpVector) @pytest.mark.parametrize( 'data', ([1.1, 2.2, 3.3], [1.1, 2.2, None]) ) @pytest.mark.parametrize( 'dtype', [float, pandas.Float64Dtype() if has_pandas else None] ) def test_series_obj_float(self, data, dtype): Series = pandas.core.series.Series s = Series(data, index=['a', 'b', 'c'], dtype=dtype) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) assert isinstance(rp_s, rinterface.FloatSexpVector) def test_series_obj_allnone(self): Series = pandas.core.series.Series s = Series([None, None, None], index=['a', 'b', 'c']) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) assert isinstance(rp_s, rinterface.BoolSexpVector) def test_series_issue264(self): Series = pandas.core.series.Series s = Series(('a', 'b', 'c', 'd', 'e'), index=pandas.Index([0,1,2,3,4], dtype='int64')) with localconverter(default_converter + rpyp.converter) as cv: rp_s = robjects.conversion.converter_ctx.get().py2rpy(s) # segfault before the fix str(rp_s) assert isinstance(rp_s, rinterface.StrSexpVector) def test_object2String(self): series = pandas.Series(['a', 'b', 'c', 'a'], dtype='O') with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(series) assert isinstance(rp_c, rinterface.StrSexpVector) def test_object2String_with_None(self): series = pandas.Series([None, 'a', 'b', 'c', 'a'], dtype='O') with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(series) assert isinstance(rp_c, rinterface.StrSexpVector) def test_factor2Category(self): factor = robjects.vectors.FactorVector(('a', 'b', 'a')) with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().rpy2py(factor) assert isinstance(rp_c, pandas.Categorical) def test_factorwithNA2Category(self): factor = robjects.vectors.FactorVector(('a', 'b', 'a', None)) assert factor[3] is rinterface.na_values.NA_Integer with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().rpy2py(factor) assert isinstance(rp_c, pandas.Categorical) assert math.isnan(rp_c[3]) def test_orderedFactor2Category(self): factor = robjects.vectors.FactorVector(('a', 'b', 'a'), ordered=True) with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().rpy2py(factor) assert isinstance(rp_c, pandas.Categorical) def test_category2Factor(self): category = pandas.Series(["a","b","c","a"], dtype="category") with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(category) assert isinstance(rp_c, robjects.vectors.FactorVector) def test_categorywithNA2Factor(self): category = pandas.Series(['a', 'b', 'c', numpy.nan], dtype='category') with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(category) assert isinstance(rp_c, robjects.vectors.FactorVector) assert rp_c[3] == rinterface.NA_Integer def test_orderedCategory2Factor(self): category = pandas.Series(pandas.Categorical(['a','b','c','a'], categories=['a','b','c'], ordered=True)) with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(category) assert isinstance(rp_c, robjects.vectors.FactorVector) def test_datetime2posixct(self): datetime = pandas.Series( pandas.date_range('2017-01-01 00:00:00.234', periods=20, freq='ms', tz='UTC') ) with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(datetime) assert isinstance(rp_c, robjects.vectors.POSIXct) assert int(rp_c[0]) == 1483228800 assert int(rp_c[1]) == 1483228800 assert rp_c[0] != rp_c[1] def test_datetime2posixct_withNA(self): datetime = pandas.Series( pandas.date_range('2017-01-01 00:00:00.234', periods=20, freq='ms', tz='UTC') ) datetime[1] = pandas.NaT with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(datetime) assert isinstance(rp_c, robjects.vectors.POSIXct) assert int(rp_c[0]) == 1483228800 assert math.isnan(rp_c[1]) def test_date2posixct(self): today = datetime.now().date() date = pandas.Series([today]) with localconverter(default_converter + rpyp.converter) as cv: rp_c = robjects.conversion.converter_ctx.get().py2rpy(date) assert isinstance(rp_c, robjects.vectors.FloatSexpVector) assert tuple(int(x) for x in rp_c) == (today.toordinal(), ) def test_timeR2Pandas(self): tzone = robjects.vectors.get_timezone() dt = [datetime(1960, 5, 2), datetime(1970, 6, 3), datetime(2012, 7, 1)] dt = [x.replace(tzinfo=tzone) for x in dt] # fix the time ts = [x.timestamp() for x in dt] # Create an R POSIXct vector. r_time = robjects.baseenv['as.POSIXct']( rinterface.FloatSexpVector(ts), origin=rinterface.StrSexpVector(('1970-01-01',)) ) # Convert R POSIXct vector to pandas-compatible vector with localconverter(default_converter + rpyp.converter) as cv: py_time = robjects.conversion.converter_ctx.get().rpy2py(r_time) # Check that the round trip did not introduce changes for expected, obtained in zip(dt, py_time): assert expected == obtained.to_pydatetime() # Try with NA. r_time[1] = rinterface.na_values.NA_Real # Convert R POSIXct vector to pandas-compatible vector with localconverter(default_converter + rpyp.converter) as cv: py_time = robjects.conversion.converter_ctx.get().rpy2py(r_time) assert py_time[1] is pandas.NaT def test_posixct_in_dataframe_to_pandas(self): tzone = robjects.vectors.get_timezone() dt = [datetime(1960, 5, 2), datetime(1970, 6, 3), datetime(2012, 7, 1)] dt = [x.replace(tzinfo=tzone) for x in dt] # fix the time ts = [x.timestamp() for x in dt] # Create an R data.frame with a posixct_vector. r_dataf = robjects.vectors.DataFrame({ 'mydate': robjects.baseenv['as.POSIXct']( rinterface.FloatSexpVector(ts), origin=rinterface.StrSexpVector(('1970-01-01',)) )}) # Convert R POSIXct vector to pandas-compatible vector with localconverter(default_converter + rpyp.converter): py_dataf = robjects.conversion.converter_ctx.get().rpy2py(r_dataf) assert pandas.core.dtypes.common.is_datetime64_any_dtype(py_dataf['mydate']) def test_repr(self): # this should go to testVector, with other tests for repr() l = (('b', numpy.array([True, False, True], dtype=numpy.bool_)), ('i', numpy.array([1, 2, 3], dtype="i")), ('f', numpy.array([1, 2, 3], dtype="f")), ('s', numpy.array(["a", "b", "c"], dtype="S")), ('u', numpy.array([u"a", u"b", u"c"], dtype="U"))) od = OrderedDict(l) pd_df = pandas.core.frame.DataFrame(od) with localconverter(default_converter + rpyp.converter) as cv: rp_df = robjects.conversion.converter_ctx.get().py2rpy(pd_df) s = repr(rp_df) # used to fail with a TypeError. s = s.split('\n') repr_str = ('[BoolSex..., IntSexp..., FloatSe..., ' 'ByteSex..., StrSexp...]') assert repr_str == s[2].strip() # Try again with the conversion still active. with localconverter(default_converter + rpyp.converter) as cv: rp_df = robjects.conversion.converter_ctx.get().py2rpy(pd_df) s = repr(rp_df) # used to fail with a TypeError. s = s.split('\n') assert repr_str == s[2].strip() @pytest.mark.parametrize( 'colnames', (('w', 'x', 'y', 'z'), ('w', 'x', 'y', 'x')) ) @pytest.mark.parametrize( 'r_rownames,py_rownames', (('NULL', ('1', '2')), ('c("a", "b")',('a', 'b'))) ) def test_ri2pandas(self, r_rownames, py_rownames, colnames): rdataf = robjects.r( f'data.frame({colnames[0]}=1:2, ' f' {colnames[1]}=1:2, ' f' {colnames[2]}=I(c("a", "b")), ' f' {colnames[3]}=factor(c("a", "b")), ' f' row.names={r_rownames}, ' ' check.names=FALSE)') with localconverter(default_converter + rpyp.converter) as cv: pandas_df = robjects.conversion.converter_ctx.get().rpy2py(rdataf) assert isinstance(pandas_df, pandas.DataFrame) assert colnames == tuple(pandas_df.keys()) assert pandas_df['w'].dtype in (numpy.dtype('int32'), numpy.dtype('int64')) assert pandas_df['y'].dtype == numpy.dtype('O') if 'z' in colnames: assert isinstance(pandas_df['z'].dtype, pandas.api.types.CategoricalDtype) assert tuple(pandas_df.index) == py_rownames @pytest.mark.parametrize( 'rcode,names,values', ( ('list(list(x=1, y=3), list(x=2, y=4))', ('x', 'y'), ((1, 2), (3, 4))), ('list(list(x=1), list(x=2, y=4))', ('x', 'y'), ((1, 2), (None, 4))) ) ) def test__records_to_columns(self, rcode, names, values): rlist = robjects.r(rcode) columns = rpyp._records_to_columns(rlist) assert tuple(columns.keys()) == names for n, v in zip(names, values): assert tuple(columns[n]) == v @pytest.mark.parametrize( 'rcode,names,values', ( ('data.frame(' ' a = c("a", "b"),' ' b = c(1, 2)' ')', ('a', 'b'), (["a", "b"], [1, 2])), ('data.frame(' ' a = c("a", "b"),' ' b = I(list(list(x=1, y=3), list(x=2, y=4)))' ')', ('a', ('b', 'x'), ('b', 'y')), (["a", "b"], [1, 2], [3, 4])) ) ) def test__flatten_dataframe(self, rcode, names, values): rdataf = robjects.r(rcode) colnames_lst = [] columns = tuple(rpyp._flatten_dataframe(rdataf, colnames_lst)) assert tuple(colnames_lst) == names @pytest.mark.parametrize( 'rcode,names,values', ( ('data.frame(' ' a = c("a", "b"),' ' b = I(list(list(x=1, y=3), list(x=2, y=4)))' ')', ('a', ('b', 'x'), ('b', 'y')), (["a", "b"], [1, 2], [3, 4])), ('data.frame(' ' b = I(list(list(x=1, y=3), list(x=2, y=4)))' ')', (('b', 'x'), ('b', 'y')), ([1, 2], [3, 4])), ('aggregate(gear ~ am, mtcars, ' 'function(x) c(mean = mean(x), sd = sd(x)), ' 'simplify=FALSE)', ('am', ('gear', 'mean'), ('gear', 'sd')), ([0.0, 1.0], [3.210526, 4.384615], [0.418854, 0.506370])) ) ) def test_dataframe_with_list(self, rcode, names, values): rdataf = robjects.r(rcode) with localconverter(default_converter + rpyp.converter) as cv: pandas_df = robjects.conversion.converter_ctx.get().rpy2py(rdataf) assert tuple(pandas_df.columns) == names def test_ri2pandas_issue207(self): d = robjects.DataFrame({'x': 1}) with localconverter(default_converter + rpyp.converter) as cv: try: ok = True robjects.globalenv['d'] = d except ValueError: ok = False finally: if 'd' in robjects.globalenv: del(robjects.globalenv['d']) assert ok ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_robjects.py0000644000175100001770000000576614543120705021222 0ustar00runnerdockerimport array import pytest import rpy2.robjects as robjects rinterface = robjects.rinterface # TODO: what is this ? # def tearDow(self): # robjects.r._dotter = False def test_getitem(): letters_R = robjects.r['letters'] assert isinstance(letters_R, robjects.Vector) letters = (('a', 0), ('b', 1), ('c', 2), ('x', 23), ('y', 24), ('z', 25)) for l, i in letters: assert letters_R[i] == l as_list_R = robjects.r['as.list'] seq_R = robjects.r['seq'] mySeq = seq_R(0, 10) myList = as_list_R(mySeq) for i, li in enumerate(myList): assert myList[i][0] == i def test_eval(): # vector long enough to span across more than one line x = robjects.baseenv['seq'](1, 50, 2) res = robjects.r('sum(%s)' %x.r_repr()) assert res[0] == 625 def test_items(): v = robjects.IntVector((1,2,3)) rs = robjects.robject.RSlots(v) assert len(tuple(rs.items())) == 0 v.do_slot_assign('a', robjects.IntVector((9,))) for ((k_o,v_o), (k,v)) in zip((('a', robjects.IntVector), ), rs.items()): assert k_o == k assert v_o == type(v) def test_values(): v = robjects.IntVector((1,2,3)) rs = robjects.robject.RSlots(v) assert len(tuple(rs.items())) == 0 v.do_slot_assign('a', robjects.IntVector((9,))) for (v_o, v) in zip((robjects.IntVector, ), rs.values()): assert v_o == type(v) def test_init(): identical = rinterface.baseenv['identical'] py_a = array.array('i', [1,2,3]) with pytest.raises(ValueError): robjects.RObject(py_a) ri_v = rinterface.IntSexpVector(py_a) ro_v = robjects.RObject(ri_v) assert identical(ro_v, ri_v)[0] is True del(ri_v) assert rinterface.RTYPES.INTSXP == ro_v.typeof def test_r_repr(): obj = robjects.baseenv['pi'] s = obj.r_repr() assert s.startswith('3.14') def test_str(): prt = robjects.baseenv['pi'] s = prt.__str__() assert s.startswith('[1] 3.14') def test_rclass(): assert robjects.baseenv['letters'].rclass[0] == 'character' assert robjects.baseenv['pi'].rclass[0] == 'numeric' assert robjects.globalenv.find('help').rclass[0] == 'function' def test_rclass_set(): x = robjects.r('1:3') old_class = x.rclass x.rclass = robjects.StrVector(('Foo', )) + x.rclass assert x.rclass[0] == 'Foo' assert old_class[0] == x.rclass[1] def test_rclass_set_usingstring(): x = robjects.r('1:3') old_class = x.rclass x.rclass = 'Foo' assert x.rclass[0] == 'Foo' def test_rclass_str(): s = str(robjects.r) assert isinstance(s, str) def test_do_slot(): assert robjects.globalenv.find('BOD').do_slot('reference')[0] == 'A1.4, p. 270' def test_slots(): x = robjects.r('list(a=1,b=2,c=3)') s = x.slots assert len(s) == 1 assert tuple(s.keys()) == ('names', ) assert tuple(s['names']) == ('a', 'b', 'c') s['names'] = 0 assert len(s) == 1 assert tuple(s.keys()) == ('names', ) assert tuple(s['names']) == (0, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_rs4.py0000644000175100001770000000126214543120705020102 0ustar00runnerdockerimport pytest from rpy2 import robjects @pytest.fixture(scope='module') def set_class_A(): robjects.r('methods::setClass("A", representation(a="numeric", b="character"))') yield robjects.r('methods::removeClass("A")') def test_slotnames(set_class_A): ainstance = robjects.r('new("A", a=1, b="c")') assert tuple(ainstance.slotnames()) == ('a', 'b') def test_isclass(set_class_A): ainstance = robjects.r('new("A", a=1, b="c")') assert not ainstance.isclass("B") assert ainstance.isclass("A") def test_validobject(set_class_A): ainstance = robjects.r('new("A", a=1, b="c")') assert ainstance.validobject() #FIXME: test invalid objects ? ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_serialization.py0000644000175100001770000000117214543120705022247 0ustar00runnerdockerimport pytest import pickle import tempfile from rpy2 import robjects def test_pickle(): tmp_file = tempfile.NamedTemporaryFile() robj = robjects.baseenv["pi"] pickle.dump(robj, tmp_file) tmp_file.flush() tmp_file.seek(0) robj_again = pickle.load(tmp_file) tmp_file.close() assert isinstance(robj, robjects.FloatVector) # Check that underlying R objects are identical. assert robjects.baseenv["identical"](robj, robj_again)[0] # Check the instance dict is also identical assert set(robj.__dict__.keys()) == set(robj_again.__dict__.keys()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_translated_function.py0000644000175100001770000000165714543120705023450 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects rinterface = robjects.rinterface import array identical = rinterface.baseenv['identical'] Function = robjects.functions.Function SignatureTranslatedFunction = robjects.functions.SignatureTranslatedFunction def test_init_invalid(): with pytest.raises(ValueError): SignatureTranslatedFunction('a') def test_init(): ri_f = rinterface.baseenv.find('rank') ro_f = SignatureTranslatedFunction(ri_f) assert identical(ri_f, ro_f)[0] is True def test_init_with_translation(): ri_f = rinterface.baseenv.find('rank') ro_f = SignatureTranslatedFunction( ri_f, init_prm_translate = {'foo_bar': 'na.last'}) assert identical(ri_f, ro_f)[0] is True def test_call(): ri_f = rinterface.baseenv.find('sum') ro_f = robjects.Function(ri_f) ro_v = robjects.IntVector(array.array('i', [1,2,3])) s = ro_f(ro_v) assert s[0] == 6 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_vector.py0000644000175100001770000003013014543120705020670 0ustar00runnerdockerimport pytest import rpy2.robjects as robjects ri = robjects.rinterface import os import array import time import datetime import rpy2.rlike.container as rlc from collections import OrderedDict rlist = robjects.baseenv["list"] def test_init(): identical = ri.baseenv["identical"] py_a = array.array('i', [1,2,3]) ro_v = robjects.IntVector(py_a) assert ro_v.typeof == ri.RTYPES.INTSXP ri_v = ri.IntSexpVector(py_a) ro_v = robjects.IntVector(ri_v) assert identical(ro_v, ri_v)[0] del(ri_v) assert ro_v.typeof == ri.RTYPES.INTSXP @pytest.mark.parametrize( 'cls,expected_na', [(robjects.StrVector, ri.NA_Character), (robjects.IntVector, ri.NA_Integer), (robjects.FloatVector, ri.NA_Real), (robjects.BoolVector, ri.NA_Logical), (robjects.ComplexVector, ri.NA_Complex), ]) def test_vector_navalue(cls, expected_na): assert cls.NAvalue is expected_na @pytest.mark.parametrize( 'cls,values', [(robjects.StrVector, ['abc', 'def']), (robjects.IntVector, [123, 456]), (robjects.FloatVector, [123.0, 456.0]), (robjects.BoolVector, [True, False])]) def test_init_vectors(cls, values): vec = cls(values) assert len(vec) == len(values) for x, y in zip(vec, values): assert x == y @pytest.mark.parametrize( 'vec', (robjects.ListVector({'a': 1, 'b': 2}), robjects.ListVector((('a', 1), ('b', 2))), robjects.ListVector(iter([('a', 1), ('b', 2)]))) ) def test_new_listvector(vec): assert 'a' in vec.names assert 'b' in vec.names assert len(vec) == 2 assert len(vec.names) == 2 def test_strvector_factor(): vec = robjects.StrVector(('abc', 'def', 'abc')) fvec = vec.factor() assert isinstance(fvec, robjects.FactorVector) def test_add_operator(): seq_R = robjects.r["seq"] mySeqA = seq_R(0, 3) mySeqB = seq_R(5, 7) mySeqAdd = mySeqA + mySeqB assert len(mySeqA)+len(mySeqB) == len(mySeqAdd) for i, li in enumerate(mySeqA): assert mySeqAdd[i] == li for j, li in enumerate(mySeqB): assert mySeqAdd[i+j+1] == li def test_r_add_operator(): seq_R = robjects.r["seq"] mySeq = seq_R(0, 10) mySeqAdd = mySeq.ro + 2 for i, li in enumerate(mySeq): assert li + 2 == mySeqAdd[i] def test_r_sub_operator(): seq_R = robjects.r["seq"] mySeq = seq_R(0, 10) mySeqAdd = mySeq.ro - 2 for i, li in enumerate(mySeq): assert li - 2 == mySeqAdd[i] def test_r_mult_operator(): seq_R = robjects.r["seq"] mySeq = seq_R(0, 10) mySeqAdd = mySeq.ro * 2 for i, li in enumerate(mySeq): assert li * 2 == mySeqAdd[i] def test_r_matmul_operator(): # 1 3 # 2 4 m = robjects.r.matrix(robjects.IntVector(range(1, 5)), nrow=2) # 1*1+3*2 1*3+3*4 # 2*1+4*2 2*3+4*4 m2 = m.ro @ m for i,val in enumerate((7.0, 10.0, 15.0, 22.0,)): assert m2[i] == val def test_r_power_operator(): seq_R = robjects.r["seq"] mySeq = seq_R(0, 10) mySeqPow = mySeq.ro ** 2 for i, li in enumerate(mySeq): assert li ** 2 == mySeqPow[i] def test_r_truediv(): v = robjects.vectors.IntVector((2,3,4)) res = v.ro / 2 assert all(abs(x-y) < 0.001 for x, y in zip(res, (1, 1.5, 2))) def test_r_floor_division(): v = robjects.vectors.IntVector((2, 3, 4)) res = v.ro // 2 assert tuple(int(x) for x in res) == (1, 1, 2) def test_r_mod(): v = robjects.vectors.IntVector((2, 3, 4)) res = v.ro % 2 assert all(x == y for x, y in zip(res, (0, 1, 0))) def test_r_and(): v = robjects.vectors.BoolVector((True, False)) res = v.ro & True assert all(x is y for x, y in zip(res, (True, False))) def test_r_or(): v = robjects.vectors.BoolVector((True, False)) res = v.ro | False assert all(x is y for x, y in zip(res, (True, False))) def test_r_invert(): v = robjects.vectors.BoolVector((True, False)) res = ~v.ro assert all(x is (not y) for x, y in zip(res, (True, False))) def test_r_lt(): v = robjects.vectors.IntVector((4, 2, 1)) res = v.ro < 2 assert all(x is y for x, y in zip(res, (False, False, True))) def test_r_le(): v = robjects.vectors.IntVector((4, 2, 1)) res = (v.ro <= 2) assert all(x is y for x, y in zip(res, (False, True, True))) def test_r_gt(): v = robjects.vectors.IntVector((4, 2, 1)) res = v.ro > 2 assert all(x is y for x, y in zip(res, (True, False, False))) def test_r_ge(): v = robjects.vectors.IntVector((4, 2, 1)) res = v.ro >= 2 assert all(x is y for x, y in zip(res, (True, True, False))) def test_r_eq(): v = robjects.vectors.IntVector((4, 2, 1)) res = v.ro == 2 assert all(x is y for x, y in zip(res, (False, True, False))) def test_r_ne(): v = robjects.vectors.IntVector((4, 2, 1)) res = v.ro != 2 assert all(x is y for x, y in zip(res, (True, False, True))) def test_r_neg(): v = robjects.vectors.IntVector((4, 2, 1)) res = - v.ro assert all(x is y for x, y in zip(res, (-4, -2, -1))) def test_contains(): v = robjects.StrVector(('abc', 'def', 'ghi')) assert 'def' in v.ro assert 'foo' not in v.ro @pytest.mark.parametrize( 'cls,values', [(robjects.StrVector, ['abc', 'def']), (robjects.IntVector, [123, 456]), (robjects.FloatVector, [123.0, 456.0]), (robjects.BoolVector, [True, False])]) def test_getitem_int(cls, values): vec = cls(values) assert vec[0] == values[0] assert vec[1] == values[1] def test_getitem_outofbounds(): letters = robjects.baseenv["letters"] with pytest.raises(IndexError): letters[26] @pytest.mark.parametrize( 'cls,values', [(robjects.StrVector, ['abc', 'def']), (robjects.IntVector, [123, 456]), (robjects.FloatVector, [123.0, 456.0]), (robjects.BoolVector, [True, False])]) def test_getitem_invalidtype(cls, values): vec = cls(values) with pytest.raises(TypeError): vec['foo'] def test_setitem(): vec = robjects.r.seq(1, 10) assert vec[0] == 1 vec[0] = 20 assert vec[0] == 20 def test_setitem_outofbounds(): vec = robjects.r.seq(1, 10) with pytest.raises(IndexError): vec[20] = 20 @pytest.mark.parametrize( 'cls,values', [(robjects.StrVector, ['abc', 'def']), (robjects.IntVector, [123, 456]), (robjects.FloatVector, [123.0, 456.0]), (robjects.BoolVector, [True, False])]) def test_setitem_invalidtype(cls, values): vec = cls(values) with pytest.raises(TypeError): vec['foo'] = values[0] def get_item_list(): mylist = rlist(letters, "foo") idem = robjects.baseenv["identical"] assert idem(letters, mylist[0])[0] is True assert idem("foo", mylist[1])[0] is True def test_getnames(): vec = robjects.vectors.IntVector(array.array('i', [1,2,3])) v_names = [robjects.baseenv["letters"][x] for x in (0,1,2)] #FIXME: simplify this r_names = robjects.baseenv["c"](*v_names) vec = robjects.baseenv["names<-"](vec, r_names) for i in range(len(vec)): assert v_names[i] == vec.names[i] vec.names[0] = 'x' def test_setnames(): vec = robjects.vectors.IntVector(array.array('i', [1,2,3])) names = ['x', 'y', 'z'] vec.names = names for i in range(len(vec)): assert names[i] == vec.names[i] def test_nainteger(): vec = robjects.IntVector(range(3)) vec[0] = robjects.NA_Integer assert robjects.baseenv['is.na'](vec)[0] is True def test_tabulate(): vec = robjects.IntVector((1,2,1,2,1,2,2)) tb = vec.tabulate() assert tuple(tb) == (3, 4) def test_nareal(): vec = robjects.FloatVector((1.0, 2.0, 3.0)) vec[0] = robjects.NA_Real assert robjects.baseenv['is.na'](vec)[0] is True def test_nalogical(): vec = robjects.BoolVector((True, False, True)) vec[0] = robjects.NA_Logical assert robjects.baseenv['is.na'](vec)[0] is True @pytest.mark.xfail(reason='Edge case with conversion.') def test_nacomplex(): vec = robjects.ComplexVector((1+1j, 2+2j, 3+3j)) vec[0] = robjects.NA_Complex assert robjects.baseenv['is.na'](vec)[0] is True def test_nacomplex_workaround(): vec = robjects.ComplexVector((1+1j, 2+2j, 3+3j)) vec[0] = complex(robjects.NA_Complex.real, robjects.NA_Complex.imag) assert robjects.baseenv['is.na'](vec)[0] is True def test_nacharacter(): vec = robjects.StrVector('abc') vec[0] = robjects.NA_Character assert robjects.baseenv['is.na'](vec)[0] is True def test_int_repr(): vec = robjects.vectors.IntVector((1, 2, ri.NA_Integer)) s = repr(vec) assert s.endswith('[1, 2, NA_integer_]') def test_list_repr(): vec = robjects.vectors.ListVector((('a', 1), ('b', 2), ('b', 3))) s = repr(vec) assert s.startswith('') assert s[-2].strip().endswith('') s = vec._repr_html_(max_items=2).split('\n') assert s[2].strip().startswith('') assert s[-2].strip().endswith('
') def test_repr_nonvectorinlist(): vec = robjects.ListVector(OrderedDict((('a', 1), ('b', robjects.Formula('y ~ x')), ))) s = repr(vec).split(os.linesep) assert s[1].startswith("R classes: ('list',)") assert s[2].startswith("[IntSexpVector, LangSexpVector]") def test_items(): vec = robjects.IntVector(range(3)) vec.names = robjects.StrVector('abc') names = [k for k,v in vec.items()] assert names == ['a', 'b', 'c'] values = [v for k,v in vec.items()] assert values == [0, 1, 2] def test_itemsnonames(): vec = robjects.IntVector(range(3)) names = [k for k,v in vec.items()] assert names == [None, None, None] values = [v for k,v in vec.items()] assert values == [0, 1, 2] def test_sequence_to_vector(): res = robjects.sequence_to_vector((1, 2, 3)) assert isinstance(res, robjects.IntVector) res = robjects.sequence_to_vector((1, 2, 3.0)) assert isinstance(res, robjects.FloatVector) res = robjects.sequence_to_vector(('ab', 'cd', 'ef')) assert isinstance(res, robjects.StrVector) with pytest.raises(ValueError): robjects.sequence_to_vector(list()) def test_sample(): vec = robjects.IntVector(range(100)) spl = vec.sample(10) assert len(spl) == 10 def test_sample_probabilities(): vec = robjects.IntVector(range(100)) spl = vec.sample(10, probabilities=robjects.FloatVector([.01] * 100)) assert len(spl) == 10 def test_sample_probabilities_novector(): vec = robjects.IntVector(range(100)) spl = vec.sample(10, probabilities=[.01] * 100) assert len(spl) == 10 def test_sample_probabilities_error_len(): vec = robjects.IntVector(range(100)) with pytest.raises(ValueError): vec.sample(10, probabilities=robjects.FloatVector([.01] * 10)) def test_sample_error(): vec = robjects.IntVector(range(100)) with pytest.raises(ri.embedded.RRuntimeError): spl = vec.sample(110) def test_sample_replacement(): vec = robjects.IntVector(range(100)) spl = vec.sample(110, replace=True) assert len(spl) == 110 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_vector_datetime.py0000644000175100001770000001016414543120705022551 0ustar00runnerdockerimport datetime try: import zoneinfo except ImportError: from backports import zoneinfo import pytest import time from rpy2 import robjects import rpy2.robjects.vectors _dateval_tuple = (1984, 1, 6, 6, 22, 0, 1, 6, 0) _zones_str = (None, 'America/New_York', 'Australia/Sydney') def test_POSIXlt_from_invalidpythontime(): x = [time.struct_time(_dateval_tuple), time.struct_time(_dateval_tuple)] x.append('foo') with pytest.raises(ValueError): robjects.POSIXlt(x) def test_POSIXlt_from_pythontime(): x = [time.struct_time(_dateval_tuple), time.struct_time(_dateval_tuple)] res = robjects.POSIXlt(x) assert len(x) == 2 @pytest.mark.xfail(reason='wday mismatch between R and Python (issue #523)') def test_POSIXlt_getitem(): x = [time.struct_time(_dateval_tuple), time.struct_time(_dateval_tuple)] res = robjects.POSIXlt(x) assert res[0] == x[0] def testPOSIXlt_repr(): x = [time.struct_time(_dateval_tuple), time.struct_time(_dateval_tuple)] res = robjects.POSIXlt(x) s = repr(res) assert isinstance(s, str) def test_POSIXct_from_invalidpythontime(): x = [time.struct_time(_dateval_tuple), time.struct_time(_dateval_tuple)] x.append('foo') # string 'foo' does not have attribute 'tm_zone' with pytest.raises(AttributeError): robjects.POSIXct(x) def test_POSIXct_from_invalidobject(): x = ['abc', 3] with pytest.raises(TypeError): robjects.POSIXct(x) @pytest.fixture(scope='module', params=_zones_str) def default_timezone_mocker(request): zone_str = request.param if zone_str: rpy2.robjects.vectors.default_timezone = zoneinfo.ZoneInfo(zone_str) yield zone_str rpy2.robjects.vectors.default_timezone = None @pytest.mark.parametrize( 'x', ([time.struct_time(_dateval_tuple), ] * 2, [datetime.datetime(*_dateval_tuple[:-2]), ] * 2) ) def test_POSIXct_from_python_times(x, default_timezone_mocker): res = robjects.POSIXct(x) assert list(res.slots['class']) == ['POSIXct', 'POSIXt'] assert len(res) == 2 zone = default_timezone_mocker assert res.slots['tzone'][0] == (zone if zone else '') @pytest.mark.parametrize('zone_str', _zones_str[1:]) def test_POSIXct_from_python_timezone(zone_str): x = [ datetime.datetime(*_dateval_tuple[:-2]) .replace(tzinfo=zoneinfo.ZoneInfo(zone_str)), ] * 2 res = robjects.POSIXct(x) assert list(res.slots['class']) == ['POSIXct', 'POSIXt'] assert len(res) == 2 assert res.slots['tzone'][0] == (zone_str if zone_str else '') def testPOSIXct_fromSexp(): sexp = robjects.r('ISOdate(2013, 12, 11)') res = robjects.POSIXct(sexp) assert len(res) == 1 def testPOSIXct_repr(): sexp = robjects.r('ISOdate(2013, 12, 11)') res = robjects.POSIXct(sexp) s = repr(res) assert s.endswith('[2013-12-1...]') def testPOSIXct_getitem(): dt = ((datetime.datetime(2014, 12, 11) - datetime.datetime(1970,1,1)) .total_seconds()) sexp = robjects.r('ISOdate(c(2013, 2014), 12, 11, hour = 0, tz = "UTC")') res = robjects.POSIXct(sexp) assert (res[1] - dt) == 0 def testPOSIXct_iter_localized_datetime(): timestamp = 1234567890 timezone = 'UTC' r_vec = robjects.r('as.POSIXct')( timestamp, origin='1960-01-01', tz=timezone) r_times = robjects.r('strftime')(r_vec, format="%H:%M:%S", tz=timezone) py_value = next(r_vec.iter_localized_datetime()) assert r_times[0] == ':'.join( ('%i' % getattr(py_value, x) for x in ('hour', 'minute', 'second')) ) def test_POSIXct_datetime_from_timestamp(default_timezone_mocker): tzone = robjects.vectors.get_timezone() dt = [datetime.datetime(1900, 1, 1), datetime.datetime(1970, 1, 1), datetime.datetime(2000, 1, 1)] dt = [x.replace(tzinfo=tzone) for x in dt] ts = [x.timestamp() for x in dt] res = [robjects.POSIXct._datetime_from_timestamp(x, tzone) for x in ts] for expected, obtained in zip(dt, res): assert expected == obtained ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_vector_extractdelegator.py0000644000175100001770000000665014543120705024323 0ustar00runnerdockerimport array import pytest import rpy2.rinterface_lib.callbacks import rpy2.rinterface_lib.embedded import rpy2.rlike.container as rlc from rpy2 import robjects from .. import utils def _just_pass(s): pass @pytest.fixture(scope='module') def silent_console_print(): with utils.obj_in_module(rpy2.rinterface_lib.callbacks, 'consolewrite_print', _just_pass): yield def test_extract_getitem_by_index(): seq_R = robjects.baseenv["seq"] mySeq = seq_R(0, 10) # R indexing starts at one myIndex = robjects.vectors.IntVector(array.array('i', range(1, 11, 2))) mySubset = mySeq.rx[myIndex] for i, si in enumerate(myIndex): assert mySeq[si-1] == mySubset[i] def test_extract_by_index(): seq_R = robjects.baseenv["seq"] mySeq = seq_R(0, 10) # R indexing starts at one myIndex = robjects.vectors.IntVector(array.array('i', range(1, 11, 2))) mySubset = mySeq.rx(myIndex, drop=True) for i, si in enumerate(myIndex): assert mySeq[si-1] == mySubset[i] def test_extract_by_name(): seq_R = robjects.baseenv["seq"] mySeq = seq_R(0, 25) letters = robjects.baseenv["letters"] mySeq = robjects.baseenv["names<-"](mySeq, letters) # R indexing starts at one myIndex = robjects.vectors.StrVector(letters[2]) mySubset = mySeq.rx(myIndex) for i, si in enumerate(myIndex): assert mySubset[i] == 2 def test_extract_silent_index_error(): seq_R = robjects.baseenv["seq"] mySeq = seq_R(0, 10) # R indexing starts at one. myIndex = robjects.vectors.StrVector(['a', 'b', 'c']) with utils.obj_in_module(rpy2.rinterface_lib.callbacks, 'consolewrite_print', utils.noconsole): res = mySeq.rx(myIndex) assert all(x == rpy2.robjects.NA_Integer for x in res) def test_replace(): vec = robjects.vectors.IntVector(range(1, 6)) i = array.array('i', [1, 3]) vec.rx[rlc.TaggedList((i, ))] = 20 assert vec[0] == 20 assert vec[1] == 2 assert vec[2] == 20 assert vec[3] == 4 vec = robjects.vectors.IntVector(range(1, 6)) i = array.array('i', [1, 5]) vec.rx[rlc.TaggedList((i, ))] = 50 assert vec[0] == 50 assert vec[1] == 2 assert vec[2] == 3 assert vec[3] == 4 assert vec[4] == 50 vec = robjects.vectors.IntVector(range(1, 6)) vec.rx[1] = 70 assert tuple(vec[0:5]) == (70, 2, 3, 4, 5) vec = robjects.vectors.IntVector(range(1, 6)) vec.rx[robjects.vectors.IntVector((1, 3))] = 70 assert tuple(vec[0:5]) == (70, 2, 70, 4, 5) m = robjects.r('matrix(1:10, ncol=2)') m.rx[1, 1] = 9 assert m[0] == 9 m = robjects.r('matrix(1:10, ncol=2)') m.rx[2, robjects.vectors.IntVector((1,2))] = 9 assert m[1] == 9 assert m[6] == 9 def test_extract_recycling_rule(): # Create a vector with 22 cells. v = robjects.vectors.IntVector(array.array('i', range(1, 23))) # Promote it to a matrix with 2 columns (therefore 11 rows). m = robjects.r.matrix(v, ncol = 2) # Extraxt all elements in the first column (R is one-indexed). col = m.rx(True, 1) assert len(col) == 11 def test_extract_list(): # list letters = robjects.baseenv['letters'] myList = robjects.baseenv['list'](l=letters, f='foo') idem = robjects.baseenv['identical'] assert idem(letters, myList.rx('l')[0])[0] assert idem('foo', myList.rx('f')[0])[0] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/robjects/test_vector_factor.py0000644000175100001770000000267414543120705022242 0ustar00runnerdockerimport pytest from rpy2.rinterface import NA_Character from rpy2 import robjects def test_init(): vec = robjects.FactorVector(robjects.StrVector('abaabc')) assert len(vec) == 6 def test_factor_repr(): vec = robjects.vectors.FactorVector(('abc', 'def', 'ghi')) s = repr(vec) assert s.endswith('[abc, def, ghi]') def test_isordered(): vec = robjects.FactorVector(robjects.StrVector('abaabc')) assert vec.isordered is False def test_nlevels(): vec = robjects.FactorVector(robjects.StrVector('abaabc')) assert vec.nlevels == 3 def test_levels(): vec = robjects.FactorVector(robjects.StrVector('abaabc')) assert len(vec.levels) == 3 assert set(('a','b','c')) == set(tuple(vec.levels)) def test_levels_set(): vec = robjects.FactorVector(robjects.StrVector('abaabc')) vec.levels = robjects.vectors.StrVector('def') assert set(('d','e','f')) == set(tuple(vec.levels)) @pytest.mark.parametrize( 'values,check_values', (('abaac', 'abaac'), (('abc', None, 'efg'), ('abc', NA_Character, 'efg')))) def test_iter_labels(values, check_values): vec = robjects.FactorVector(robjects.StrVector(values)) it = vec.iter_labels() for a, b in zip(check_values, it): assert a == b def test_factor_with_attrs(): # issue #299 r_src = """ x <- factor(c("a","b","a")) attr(x, "foo") <- "bar" x """ x = robjects.r(r_src) assert 'foo' in x.list_attrs() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/rpy2/tests/utils.py0000644000175100001770000000070114543120705015655 0ustar00runnerdockerfrom contextlib import contextmanager @contextmanager def obj_in_module(module, name, obj): backup_obj = getattr(module, name, None) setattr(module, name, obj) try: yield finally: if backup_obj: setattr(module, name, backup_obj) def assert_equal_sequence(x, y): assert type(x) is type(y) assert len(x) == len(y) assert all(x_e == y_e for x_e, y_e in zip(x, y)) def noconsole(x): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8739467 rpy2-3.5.15/rpy2.egg-info/0000755000175100001770000000000014543120757014504 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/PKG-INFO0000644000175100001770000001063014543120756015600 0ustar00runnerdockerMetadata-Version: 2.1 Name: rpy2 Version: 3.5.15 Summary: Python interface to the R language (embedded R) Author-email: Laurent Gautier License: GPLv2+ Project-URL: Homepage, https://rpy2.github.io Project-URL: Documentation, https://rpy2.github.io/doc.html Project-URL: Source, https://github.com/rpy2/rpy2 Project-URL: Tracker, https://github.com/rpy2/rpy2/issue Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 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: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE License-File: AUTHORS Requires-Dist: cffi>=1.10.0 Requires-Dist: jinja2 Requires-Dist: tzlocal Requires-Dist: packaging; platform_system == "Windows" Requires-Dist: typing-extensions; python_version < "3.8" Requires-Dist: backports.zoneinfo; python_version < "3.9" Provides-Extra: test-minimal Requires-Dist: pytest; extra == "test-minimal" Requires-Dist: coverage; extra == "test-minimal" Requires-Dist: pytest-cov; extra == "test-minimal" Provides-Extra: test Requires-Dist: pytest; extra == "test" Requires-Dist: ipython; extra == "test" Requires-Dist: numpy; extra == "test" Requires-Dist: pandas>=1.3.5; extra == "test" Provides-Extra: numpy Provides-Extra: pandas Requires-Dist: numpy; extra == "pandas" Requires-Dist: pandas>=1.3.5; extra == "pandas" Provides-Extra: types Requires-Dist: mypy; extra == "types" Requires-Dist: types-tzlocal; extra == "types" Provides-Extra: all Requires-Dist: pytest; extra == "all" Requires-Dist: ipython; extra == "all" Requires-Dist: pandas>=1.3.5; extra == "all" Requires-Dist: numpy; extra == "all" # Python -> R bridge [![pypi](https://img.shields.io/pypi/v/rpy2.svg?style=flat-square)](https://pypi.python.org/pypi/rpy2) [![Codecov](https://codecov.io/gh/rpy2/rpy2/branch/master/graph/badge.svg)](https://codecov.io/gh/rpy2/rpy2) [![GH Actions](https://github.com/rpy2/rpy2/workflows/Python%20package/badge.svg)](https://github.com/rpy2/rpy2/actions?query=workflow%3A%22Python+package%22) The project's webpage is here: https://rpy2.github.io/ # Installation `pip` should work out of the box: ```bash pip install rpy2 ``` The package has optional depencies providing specific functionalities not otherwise required to use the rest of rpy2. For example, to be able to run the unit tests: ```bash pip install rpy2[test] ``` To install all optional dependencies (numpy, pandas, ipython), use: ```bash pip install rpy2[all] ``` The package is known to compile on Linux, MacOSX (provided that developper tools are installed, and you are ready figure out how by yourself). The situation is currently a little more complicated on Windows. Check the issue tracker. In case you find yourself with this source without any idea of what it takes to compile anything on your platform, try first ```bash python setup.py install ``` ## Issues loading shared C libraries Whenever R is in not installed in a system location, the system might not know where to find the R shared library. If `R` is in the `PATH`, that is entering `R` on the command line successfully starts an R terminal, but rpy2 does not work because of missing C libraries, try the following before starting Python: ```bash export LD_LIBRARY_PATH="$(python -m rpy2.situation LD_LIBRARY_PATH)":${LD_LIBRARY_PATH} ``` # Documentation Documentation is available either in the source tree (`doc/`), or [online](https://rpy2.github.io/doc.html). ## Testing `rpy2` uses `pytest`, with the plugin `pytest-cov` for code coverage. To test the package from the source tree, either to check and installation on your system or before submitting a pull request, do: ```bash pytest tests/ ``` For code coverage, do: ```bash pytest --cov=rpy2.rinterface_lib \ --cov=rpy2.rinterface \ --cov=rpy2.ipython \ --cov=rpy2.robject \ tests ``` For more options, such as how to run specify tests, please refer to the `pytest` documentation. # License RPy2 can be used under the terms of the GNU General Public License Version 2 or later (see the file gpl-2.0.txt). This is the very same license R itself is released under. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/SOURCES.txt0000644000175100001770000001032614543120756016371 0ustar00runnerdockerAUTHORS LICENSE MANIFEST.in NEWS README.md gpl-2.0.txt pyproject.toml requirements.txt setup.py doc/Makefile rpy2/__init__.py rpy2/_rinterface_cffi_build.py rpy2/rinterface.py rpy2/situation.py rpy2.egg-info/PKG-INFO rpy2.egg-info/SOURCES.txt rpy2.egg-info/dependency_links.txt rpy2.egg-info/not-zip-safe rpy2.egg-info/requires.txt rpy2.egg-info/top_level.txt rpy2/interactive/__init__.py rpy2/interactive/packages.py rpy2/interactive/process_revents.py rpy2/ipython/__init__.py rpy2/ipython/ggplot.py rpy2/ipython/html.py rpy2/ipython/rmagic.py rpy2/rinterface_lib/RPY2.h rpy2/rinterface_lib/R_API.h rpy2/rinterface_lib/R_API_eventloop.c rpy2/rinterface_lib/R_API_eventloop.h rpy2/rinterface_lib/__init__.py rpy2/rinterface_lib/_bufferprotocol.c rpy2/rinterface_lib/_rinterface_capi.py rpy2/rinterface_lib/bufferprotocol.py rpy2/rinterface_lib/callbacks.py rpy2/rinterface_lib/conversion.py rpy2/rinterface_lib/embedded.py rpy2/rinterface_lib/embedded_mswin.py rpy2/rinterface_lib/ffi_proxy.py rpy2/rinterface_lib/memorymanagement.py rpy2/rinterface_lib/na_values.py rpy2/rinterface_lib/openrlib.py rpy2/rinterface_lib/sexp.py rpy2/rlike/__init__.py rpy2/rlike/container.py rpy2/rlike/functional.py rpy2/rlike/indexing.py rpy2/robjects/__init__.py rpy2/robjects/constants.py rpy2/robjects/conversion.py rpy2/robjects/environments.py rpy2/robjects/functions.py rpy2/robjects/help.py rpy2/robjects/language.py rpy2/robjects/methods.py rpy2/robjects/numpy2ri.py rpy2/robjects/packages.py rpy2/robjects/packages_utils.py rpy2/robjects/pandas2ri.py rpy2/robjects/robject.py rpy2/robjects/vectors.py rpy2/robjects/lib/__init__.py rpy2/robjects/lib/dbplyr.py rpy2/robjects/lib/dplyr.py rpy2/robjects/lib/ggplot2.py rpy2/robjects/lib/grdevices.py rpy2/robjects/lib/grid.py rpy2/robjects/lib/tidyr.py rpy2/tests/__init__.py rpy2/tests/utils.py rpy2/tests/ipython/__init__.py rpy2/tests/ipython/test_ggplot.py rpy2/tests/ipython/test_html.py rpy2/tests/ipython/test_rmagic.py rpy2/tests/rinterface/__init__.py rpy2/tests/rinterface/test_bufferprotocol.py rpy2/tests/rinterface/test_callbacks.py rpy2/tests/rinterface/test_conversion.py rpy2/tests/rinterface/test_embedded_r.py rpy2/tests/rinterface/test_endr.py rpy2/tests/rinterface/test_environment.py rpy2/tests/rinterface/test_externalptr.py rpy2/tests/rinterface/test_functions.py rpy2/tests/rinterface/test_memorymanagement.py rpy2/tests/rinterface/test_na.py rpy2/tests/rinterface/test_noinitialization.py rpy2/tests/rinterface/test_openrlib.py rpy2/tests/rinterface/test_sexp.py rpy2/tests/rinterface/test_symbol.py rpy2/tests/rinterface/test_threading.py rpy2/tests/rinterface/test_vector_bool.py rpy2/tests/rinterface/test_vector_byte.py rpy2/tests/rinterface/test_vector_complex.py rpy2/tests/rinterface/test_vector_float.py rpy2/tests/rinterface/test_vector_int.py rpy2/tests/rinterface/test_vector_lang.py rpy2/tests/rinterface/test_vector_list.py rpy2/tests/rinterface/test_vector_numpy.py rpy2/tests/rinterface/test_vector_pairlist.py rpy2/tests/rinterface/test_vector_str.py rpy2/tests/rinterface/test_vectors.py rpy2/tests/rlike/test_container.py rpy2/tests/rlike/test_functional.py rpy2/tests/rlike/test_indexing.py rpy2/tests/robjects/__init__.py rpy2/tests/robjects/test_array.py rpy2/tests/robjects/test_conversion.py rpy2/tests/robjects/test_conversion_numpy.py rpy2/tests/robjects/test_dataframe.py rpy2/tests/robjects/test_environment.py rpy2/tests/robjects/test_formula.py rpy2/tests/robjects/test_function.py rpy2/tests/robjects/test_help.py rpy2/tests/robjects/test_language.py rpy2/tests/robjects/test_methods.py rpy2/tests/robjects/test_packages.py rpy2/tests/robjects/test_packages_utils.py rpy2/tests/robjects/test_pandas_conversions.py rpy2/tests/robjects/test_robjects.py rpy2/tests/robjects/test_rs4.py rpy2/tests/robjects/test_serialization.py rpy2/tests/robjects/test_translated_function.py rpy2/tests/robjects/test_vector.py rpy2/tests/robjects/test_vector_datetime.py rpy2/tests/robjects/test_vector_extractdelegator.py rpy2/tests/robjects/test_vector_factor.py rpy2/tests/robjects/lib/__init__.py rpy2/tests/robjects/lib/test_dbplyr.py rpy2/tests/robjects/lib/test_dplyr.py rpy2/tests/robjects/lib/test_ggplot2.py rpy2/tests/robjects/lib/test_grdevices.py rpy2/tests/robjects/lib/test_grid.py rpy2/tests/robjects/lib/test_tidyr.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/dependency_links.txt0000644000175100001770000000000114543120756020551 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/not-zip-safe0000644000175100001770000000000114543120756016731 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/requires.txt0000644000175100001770000000054514543120756017107 0ustar00runnerdockercffi>=1.10.0 jinja2 tzlocal [:platform_system == "Windows"] packaging [:python_version < "3.8"] typing-extensions [:python_version < "3.9"] backports.zoneinfo [all] pytest ipython pandas>=1.3.5 numpy [numpy] [pandas] numpy pandas>=1.3.5 [test] pytest ipython numpy pandas>=1.3.5 [test_minimal] pytest coverage pytest-cov [types] mypy types-tzlocal ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715310.0 rpy2-3.5.15/rpy2.egg-info/top_level.txt0000644000175100001770000000005714543120756017237 0ustar00runnerdocker_rinterface_cffi_abi _rinterface_cffi_api rpy2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1703715310.8779466 rpy2-3.5.15/setup.cfg0000644000175100001770000000004614543120757013737 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1703715269.0 rpy2-3.5.15/setup.py0000644000175100001770000001655614543120705013636 0ustar00runnerdocker#!/usr/bin/env python import sys if ((sys.version_info[0] < 3) or (sys.version_info[0] == 3 and sys.version_info[1] < 7)): print('rpy2 is no longer supporting Python < 3.7.' 'Consider using an older rpy2 release when using an ' 'older Python release.') sys.exit(1) import enum import os import shutil import subprocess import tempfile import typing import warnings from setuptools import setup, Extension, dist from setuptools._distutils.ccompiler import new_compiler from setuptools._distutils.sysconfig import customize_compiler from setuptools._distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError import setuptools.command.build import setuptools.command.install import importlib # spec = importlib.util.spec_from_file_location('rpy2', './rpy2/__init__.py') # rpy2 = importlib.util.module_from_spec(spec) # sys.modules['rpy2'] = rpy2 # spec.loader.exec_module(rpy2) spec = importlib.util.spec_from_file_location('situation', './rpy2/situation.py') situation = importlib.util.module_from_spec(spec) sys.modules['situation'] = situation spec.loader.exec_module(situation) PACKAGE_NAME = 'rpy2' package_prefix='.' R_MIN_VERSION = (3, 3) def cmp_version(x, y): if (x[0] < y[0]): return -1 if (x[0] > y[0]): return 1 if (x[0] == y[0]): if len(x) == 1 or len(y) == 1: return 0 return cmp_version(x[1:], y[1:]) class COMPILATION_STATUS(enum.Enum): COMPILE_ERROR=('unable to compile R C extensions - missing headers ' 'or R not compiled as a library ?') NO_COMPILER=('unable to compile sqlite3 C extensions - ' 'no c compiler?') PLATFORM_ERROR=('unable to compile R C extensions - platform error') OK = None NO_R='No R in the PATH, or R_HOME defined.' RFLAGS_ERROR='Unable to get R compilation flags' def get_c_extension_status(libraries=['R'], include_dirs=None, library_dirs=None): if os.name == 'nt': c_code = ('int main(int argc, char **argv) { return 0; }') else: c_code = ('#include \n\n' 'int main(int argc, char **argv) { return 0; }') tmp_dir = tempfile.mkdtemp(prefix='tmp_pw_r_') bin_file = os.path.join(tmp_dir, 'test_pw_r') src_file = bin_file + '.c' with open(src_file, 'w') as fh: fh.write(c_code) compiler = new_compiler() customize_compiler(compiler) try: compiler.link_executable( compiler.compile([src_file], output_dir=tmp_dir, include_dirs=include_dirs), bin_file, libraries=libraries, library_dirs=library_dirs) except CCompilerError as cce: status = COMPILATION_STATUS.COMPILE_ERROR print(cce) except DistutilsExecError as dee: status = COMPILATION_STATUS.NO_COMPILER print(dee) except DistutilsPlatformError as dpe: status = COMPILATION_STATUS.PLATFORM_ERROR print(dpe) else: status = COMPILATION_STATUS.OK shutil.rmtree(tmp_dir) return status def get_r_c_extension_status(r_home: typing.Optional[str], force_ok: bool = False): if r_home is None: return COMPILATION_STATUS.NO_R c_ext = situation.CExtensionOptions() try: c_ext.add_lib( *situation.get_r_flags(r_home, '--ldflags') ) c_ext.add_include( *situation.get_r_flags(r_home, '--cppflags') ) except subprocess.CalledProcessError as cpe: warnings.warn(str(cpe)) return COMPILATION_STATUS.RFLAGS_ERROR if force_ok: status = COMPILATION_STATUS.OK else: status = get_c_extension_status(libraries=c_ext.libraries, include_dirs=c_ext.include_dirs, library_dirs=c_ext.library_dirs) return status class install(setuptools.command.install.install): def run(self): if r_home: print( 'LD_LIBRARY_PATH in R: {}'.format( situation.r_ld_library_path_from_subprocess(r_home) ) ) super().run() r_home = situation.get_r_home() cffi_mode = situation.get_cffi_mode() c_extension_status = get_r_c_extension_status( r_home, force_ok = os.environ.get('RPY2_API_FORCE') == 'True' ) ext_modules = [] if cffi_mode == situation.CFFI_MODE.ABI: cffi_modules = ['rpy2/_rinterface_cffi_build.py:ffibuilder_abi'] elif cffi_mode == situation.CFFI_MODE.API: if c_extension_status != COMPILATION_STATUS.OK: print('API mode requested but %s' % c_extension_status.value) sys.exit(1) cffi_modules = ['rpy2/_rinterface_cffi_build.py:ffibuilder_api'] ext_modules = [ Extension('rpy2.rinterface_lib._bufferprotocol', ['rpy2/rinterface_lib/_bufferprotocol.c']) ] elif cffi_mode == situation.CFFI_MODE.BOTH: if c_extension_status != COMPILATION_STATUS.OK: print('API mode requested but %s' % c_extension_status.value) sys.exit(1) cffi_modules = ['rpy2/_rinterface_cffi_build.py:ffibuilder_abi', 'rpy2/_rinterface_cffi_build.py:ffibuilder_api'] elif cffi_mode == situation.CFFI_MODE.ANY: # default interface cffi_modules = ['rpy2/_rinterface_cffi_build.py:ffibuilder_abi'] if c_extension_status == COMPILATION_STATUS.OK: cffi_modules.append('rpy2/_rinterface_cffi_build.py:ffibuilder_api') ext_modules = [ Extension('rpy2.rinterface_lib._bufferprotocol', ['rpy2/rinterface_lib/_bufferprotocol.c']) ] else: # This should never happen. raise ValueError('Invalid value for cffi_mode') class build(setuptools.command.build.build): def run(self): print('cffi mode: %s' % cffi_mode) super().run() print('---') print(cffi_mode) if cffi_mode in (situation.CFFI_MODE.ABI, situation.CFFI_MODE.BOTH, situation.CFFI_MODE.ANY): print('ABI mode interface built.') if cffi_mode in (situation.CFFI_MODE.API, situation.CFFI_MODE.BOTH): print('API mode interface built.') if cffi_mode == situation.CFFI_MODE.ANY: if c_extension_status == COMPILATION_STATUS.OK: print('API mode interface built.') else: print('API mode interface not built because: %s' % c_extension_status) print('To change the API/ABI build mode, set or modify the environment ' 'variable RPY2_CFFI_MODE.') pack_dir = {PACKAGE_NAME: os.path.join(package_prefix, 'rpy2')} with open('README.md') as fh: long_description = fh.read() setup( cffi_modules=cffi_modules, ext_modules=ext_modules, cmdclass=dict(build=build), long_description=long_description, # List of pacakges moved to project.toml. # TODO: package_data should be be moved to project.toml when setuptools # supports it (see note in project.toml). package_data={'rpy2': ['rinterface_lib/R_API.h', 'rinterface_lib/R_API_eventloop.h', 'rinterface_lib/R_API_eventloop.c', 'rinterface_lib/RPY2.h', 'rinterface_lib/_bufferprotocol.c', 'py.typed']}, zip_safe=False )