From f1cc3047171d73ce9b1455a7dd2610244b8a4203 Mon Sep 17 00:00:00 2001 From: Roman Yurchak Date: Sat, 31 Oct 2020 21:00:58 +0100 Subject: [PATCH] DOC Refactor API reference documentation (#782) --- .gitignore | 4 + docs/api_reference.md | 238 +++++-------------------- docs/changelog.md | 3 +- docs/conf.py | 21 ++- docs/index.rst | 2 +- docs/js-api/pyodide_globals.md | 13 ++ docs/js-api/pyodide_loadPackage.md | 19 ++ docs/js-api/pyodide_loadedPackages.md | 8 + docs/js-api/pyodide_pyimport.md | 22 +++ docs/js-api/pyodide_repr.md | 19 ++ docs/js-api/pyodide_runPython.md | 19 ++ docs/js-api/pyodide_runPythonAsync.md | 36 ++++ docs/js-api/pyodide_version.md | 18 ++ docs/loading_packages.md | 23 ++- docs/new_packages.md | 4 +- docs/requirements-doc.txt | 2 + docs/type_conversions.md | 11 +- docs/using_pyodide_from_iodide.md | 4 +- docs/using_pyodide_from_javascript.md | 9 +- packages/micropip/micropip/micropip.py | 22 ++- src/pyodide.py | 103 +++++++++-- 21 files changed, 357 insertions(+), 243 deletions(-) create mode 100644 docs/js-api/pyodide_globals.md create mode 100644 docs/js-api/pyodide_loadPackage.md create mode 100644 docs/js-api/pyodide_loadedPackages.md create mode 100644 docs/js-api/pyodide_pyimport.md create mode 100644 docs/js-api/pyodide_repr.md create mode 100644 docs/js-api/pyodide_runPython.md create mode 100644 docs/js-api/pyodide_runPythonAsync.md create mode 100644 docs/js-api/pyodide_version.md diff --git a/.gitignore b/.gitignore index 0f70dd5dc..3355b0223 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ packages/libxml/libxml* packages/libxslt/libxslt* packages/libiconv/libiconv* packages/zlib/zlib* + +docs/python-api/ +docs/micropip-api/ +docs/_build/ diff --git a/docs/api_reference.md b/docs/api_reference.md index 2155e4eec..fc2c7af64 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -1,216 +1,62 @@ # API Reference -*pyodide version 0.1.0* +## Python API Backward compatibility of the API is not guaranteed at this point. -## Python API +```{eval-rst} +.. currentmodule:: pyodide +.. autosummary:: + :toctree: ./python-api/ -### pyodide.open_url(url) + pyodide.as_nested_list + pyodide.eval_code + pyodide.find_imports + pyodide.get_completions + pyodide.open_url +``` -Fetches a given *url* and returns a `io.StringIO` to access its contents. - -*Parameters* - -| name | type | description | -|-------|------|-----------------| -| *url* | str | the URL to open | - - -*Returns* - -A `io.StringIO` object with the URL contents./ - -### pyodide.eval_code(code, ns) - -Runs a string of code. The last part of the string may be an expression, in which case, its value is returned. - -This function may be overridden to change how `pyodide.runPython` interprets code, for example to perform -some preprocessing on the Python code first. - -*Parameters* - -| name | type | description | -|--------|-------|-----------------------| -| *code* | str | the code to evaluate | -| *ns* | dict | evaluation name space | - - -*Returns* - -Either the resulting object or `None`. - -### pyodide.as_nested_list(obj) - -Converts Javascript nested arrays to Python nested lists. This conversion can not -be performed automatically, because Javascript Arrays and Objects can be combined -in ways that are ambiguous. - -*Parameters* - -| name | type | description | -|--------|-------|-----------------------| -| *obj* | JS Object | The object to convert | - -*Returns* - -The object as nested Python lists. ## Javascript API -(api_pyodide_loadpackage)= -### pyodide.loadPackage(names, messageCallback, errorCallback) +Backward compatibility of the API is not guaranteed at this point. -Load a package or a list of packages over the network. - -This makes the files for the package available in the virtual filesystem. -The package needs to be imported from Python before it can be used. - -*Parameters* - -| name | type | description | -|-------------------|-----------------|---------------------------------------| -| *names* | {String, Array} | package name, or URL. Can be either a single element, or an array. | -| *messageCallback* | function | A callback, called with progress messages. (optional) | -| *errorCallback* | function | A callback, called with error/warning messages. (optional) | - -*Returns* - -Loading is asynchronous, therefore, this returns a `Promise`. +| | | +|-|-| +| **{ref}`js_api_pyodide_globals`** | An alias to the global Python namespace | +| **{ref}`pyodide.loadPackage(names, ...) `** | Load a package or a list of packages over the network | +| **{ref}`js_api_pyodide_loadedPackages`** | `Object` with loaded packages. | +| **{ref}`js_api_pyodide_pyimport`** | Access a Python object in the global namespace from Javascript | +| **{ref}`js_api_pyodide_repr`** | Gets the Python's string representation of an object. | +| **{ref}`js_api_pyodide_runPython`** | Runs Python code from Javascript. | +| **{ref}`pyodide.runPythonAsync(code, ...) `** | Runs Python code with automatic preloading of imports. | +| **{ref}`js_api_pyodide_version`** | Returns the pyodide version. | -### pyodide.loadedPackages +```{eval-rst} +.. toctree:: + :hidden: -`Object` with loaded packages. - -Use `Object.keys(pyodide.loadedPackages)` to access the names of the -loaded packages, and `pyodide.loadedPackages[package_name]` to access -install location for a particular `package_name`. - -### pyodide.pyimport(name) - -Access a Python object from Javascript. The object must be in the global Python namespace. - -For example, to access the `foo` Python object from Javascript: - - `var foo = pyodide.pyimport('foo')` - -*Parameters* - -| name | type | description | -|---------|--------|----------------------| -| *names* | String | Python variable name | - - -*Returns* - -| name | type | description | -|-----------|---------|---------------------------------------| -| *object* | *any* | If one of the basic types (string, number,
boolean, array, object), the Python
object is converted to Javascript and
returned. For other types, a Proxy
object to the Python object is returned. | - -(api_pyodide_globals)= -### pyodide.globals - -An object whose attributes are members of the Python global namespace. This is a -more convenient alternative to `pyodide.pyimport`. - -For example, to access the `foo` Python object from Javascript: - - `pyodide.globals.foo` - -### pyodide.repr(obj) - -Gets the Python's string representation of an object. - -This is equivalent to calling `repr(obj)` in Python. - -*Parameters* - -| name | type | description | -|---------|--------|---------------------| -| *obj* | *any* | Input object | - - -*Returns* - -| name | type | description | -|------------|---------|-------------------------------------------| -| *str_repr* | String | String representation of the input object | - - -### pyodide.runPython(code) - -Runs a string of code. The last part of the string may be an expression, in which case, its value is returned. - -*Parameters* - -| name | type | description | -|---------|--------|--------------------------------| -| *code* | String | Python code to evaluate | - - -*Returns* - -| name | type | description | -|------------|---------|---------------------------------| -| *jsresult* | *any* | Result, converted to Javascript | - - - -(api_pyodide_runPythonAsync)= -### pyodide.runPythonAsync(code, messageCallback, errorCallback) - -Runs Python code, possibly asynchronously loading any known packages that the code -chunk imports. - -For example, given the following code chunk - -```python -import numpy as np -x = np.array([1, 2, 3]) + js-api/pyodide_globals.md + js-api/pyodide_loadPackage.md + js-api/pyodide_loadedPackages.md + js-api/pyodide_pyimport.md + js-api/pyodide_repr.md + js-api/pyodide_runPython.md + js-api/pyodide_runPythonAsync.md + js-api/pyodide_version.md ``` -pyodide will first call `pyodide.loadPackage(['numpy'])`, and then run the code -chunk, returning the result. Since package fetching must happen asynchronously, -this function returns a `Promise` which resolves to the output. For example, to -use: -```javascript -pyodide.runPythonAsync(code, messageCallback) - .then((output) => handleOutput(output)) +## Micropip API + +```{eval-rst} +.. currentmodule:: micropip + +.. autosummary:: + :toctree: ./micropip-api/ + + micropip.install ``` - -*Parameters* - -| name | type | description | -|-------------------|----------|--------------------------------| -| *code* | String | Python code to evaluate | -| *messageCallback* | function | A callback, called with progress messages. (optional) | -| *errorCallback* | function | A callback, called with error/warning messages. (optional) | - -*Returns* - -| name | type | description | -|------------|---------|------------------------------------------| -| *result* | Promise | Resolves to the result of the code chunk | - - -### pyodide.version() - -Returns the pyodide version. - -It can be either the exact release version (e.g. `0.1.0`), or -the latest release version followed by the number of commits since, and -the git hash of the current commit (e.g. `0.1.0-1-bd84646`). - -*Parameters* - -None - -*Returns* - -| name | type | description | -|-----------|--------|------------------------| -| *version* | String | Pyodide version string | diff --git a/docs/changelog.md b/docs/changelog.md index 384be0836..ab0cd6ea0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -34,7 +34,8 @@ https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js It now supports versioning and should provide faster downloads. The latest release can be accessed via `https://pyodide-cdn2.iodide.io/latest/full/` -- Adds `messageCallback` and `errorCallback` to `pyodide.loadPackage`. +- Adds `messageCallback` and `errorCallback` to + {ref}`pyodide.loadPackage `. - Reduces the initial memory footprint (`TOTAL_MEMORY`) from 1 GiB to 5 MiB. More memory will be allocated as needed. - When building from source, only a subset of packages can be built by setting diff --git a/docs/conf.py b/docs/conf.py index 016fe460a..4c9318372 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,8 +15,12 @@ import os import sys -sys.path.insert(0, os.path.abspath(".")) -sys.path.insert(0, os.path.abspath("..")) +for base_path in [".", ".."]: + sys.path.insert(0, os.path.abspath(base_path)) + sys.path.insert(1, os.path.abspath(os.path.join(base_path, "src"))) + sys.path.insert( + 2, os.path.abspath(os.path.join(base_path, "packages", "micropip", "micropip")) + ) # -- Project information ----------------------------------------------------- @@ -24,7 +28,8 @@ project = "Pyodide" copyright = "2019, Mozilla" author = "Mozilla" -from src import pyodide +import pyodide +import micropip # noqa # The full version, including alpha/beta/rc tags. release = version = pyodide.__version__ @@ -39,7 +44,15 @@ release = version = pyodide.__version__ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "myst_parser"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinxcontrib.napoleon", + "myst_parser", +] + +autosummary_generate = True +autodoc_default_flags = ["members", "inherited-members"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/docs/index.rst b/docs/index.rst index ca9ff319f..8da41898e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,7 +29,7 @@ Although still experimental, additional packages may be installed from PyPI to be used with Pyodide. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 :caption: Usage using_pyodide_from_iodide.md diff --git a/docs/js-api/pyodide_globals.md b/docs/js-api/pyodide_globals.md new file mode 100644 index 000000000..4934fddbf --- /dev/null +++ b/docs/js-api/pyodide_globals.md @@ -0,0 +1,13 @@ +(js_api_pyodide_globals)= +# pyodide.globals + +An alias to the global Python namespace. + +An object whose attributes are members of the Python global namespace. This is a +more convenient alternative to {ref}`pyodide.pyimport `. + +For example, to access the `foo` Python object from Javascript: + +```javascript +pyodide.globals.foo +``` diff --git a/docs/js-api/pyodide_loadPackage.md b/docs/js-api/pyodide_loadPackage.md new file mode 100644 index 000000000..553c5eccf --- /dev/null +++ b/docs/js-api/pyodide_loadPackage.md @@ -0,0 +1,19 @@ +(js_api_pyodide_loadpackage)= +# pyodide.loadPackage(names, messageCallback, errorCallback) + +Load a package or a list of packages over the network. + +This makes the files for the package available in the virtual filesystem. +The package needs to be imported from Python before it can be used. + +*Parameters* + +| name | type | description | +|-------------------|-----------------|---------------------------------------| +| *names* | {String, Array} | package name, or URL. Can be either a single element, or an array. | +| *messageCallback* | function | A callback, called with progress messages. (optional) | +| *errorCallback* | function | A callback, called with error/warning messages. (optional) | + +*Returns* + +Loading is asynchronous, therefore, this returns a `Promise`. diff --git a/docs/js-api/pyodide_loadedPackages.md b/docs/js-api/pyodide_loadedPackages.md new file mode 100644 index 000000000..065817fc3 --- /dev/null +++ b/docs/js-api/pyodide_loadedPackages.md @@ -0,0 +1,8 @@ +(js_api_pyodide_loadedPackages)= +# pyodide.loadedPackages + +`Object` with loaded packages. + +Use `Object.keys(pyodide.loadedPackages)` to access the names of the +loaded packages, and `pyodide.loadedPackages[package_name]` to access +install location for a particular `package_name`. diff --git a/docs/js-api/pyodide_pyimport.md b/docs/js-api/pyodide_pyimport.md new file mode 100644 index 000000000..aa45af5ad --- /dev/null +++ b/docs/js-api/pyodide_pyimport.md @@ -0,0 +1,22 @@ +(js_api_pyodide_pyimport)= +# pyodide.pyimport(name) + +Access a Python object in the global namespace from Javascript. + +For example, to access the `foo` Python object from Javascript: +```javascript +var foo = pyodide.pyimport('foo') +``` + +*Parameters* + +| name | type | description | +|---------|--------|----------------------| +| *names* | String | Python variable name | + + +*Returns* + +| name | type | description | +|-----------|---------|---------------------------------------| +| *object* | *any* | If one of the basic types (string, number,
boolean, array, object), the Python
object is converted to Javascript and
returned. For other types, a Proxy
object to the Python object is returned. | diff --git a/docs/js-api/pyodide_repr.md b/docs/js-api/pyodide_repr.md new file mode 100644 index 000000000..2c39a57b6 --- /dev/null +++ b/docs/js-api/pyodide_repr.md @@ -0,0 +1,19 @@ +(js_api_pyodide_repr)= +# pyodide.repr(obj) + +Gets the Python's string representation of an object. + +This is equivalent to calling `repr(obj)` in Python. + +*Parameters* + +| name | type | description | +|---------|--------|---------------------| +| *obj* | *any* | Input object | + + +*Returns* + +| name | type | description | +|------------|---------|-------------------------------------------| +| *str_repr* | String | String representation of the input object | diff --git a/docs/js-api/pyodide_runPython.md b/docs/js-api/pyodide_runPython.md new file mode 100644 index 000000000..412125626 --- /dev/null +++ b/docs/js-api/pyodide_runPython.md @@ -0,0 +1,19 @@ +(js_api_pyodide_runPython)= +# pyodide.runPython(code) + +Runs a string of Python code from Javascript. + +The last part of the string may be an expression, in which case, its value is returned. + +**Parameters** + +| name | type | description | +|---------|--------|--------------------------------| +| *code* | String | Python code to evaluate | + + +**Returns** + +| name | type | description | +|------------|---------|---------------------------------| +| *jsresult* | *any* | Result, converted to Javascript | diff --git a/docs/js-api/pyodide_runPythonAsync.md b/docs/js-api/pyodide_runPythonAsync.md new file mode 100644 index 000000000..6158e1671 --- /dev/null +++ b/docs/js-api/pyodide_runPythonAsync.md @@ -0,0 +1,36 @@ +(js_api_pyodide_runPythonAsync)= +# pyodide.runPythonAsync(code, messageCallback, errorCallback) + +Runs Python code, possibly asynchronously loading any known packages that the code +chunk imports. + +For example, given the following code chunk + +```python +import numpy as np +x = np.array([1, 2, 3]) +``` + +pyodide will first call `pyodide.loadPackage(['numpy'])`, and then run the code +chunk, returning the result. Since package fetching must happen asynchronously, +this function returns a `Promise` which resolves to the output. For example, to +use: + +```javascript +pyodide.runPythonAsync(code, messageCallback) + .then((output) => handleOutput(output)) +``` + +*Parameters* + +| name | type | description | +|-------------------|----------|--------------------------------| +| *code* | String | Python code to evaluate | +| *messageCallback* | function | A callback, called with progress messages. (optional) | +| *errorCallback* | function | A callback, called with error/warning messages. (optional) | + +*Returns* + +| name | type | description | +|------------|---------|------------------------------------------| +| *result* | Promise | Resolves to the result of the code chunk | diff --git a/docs/js-api/pyodide_version.md b/docs/js-api/pyodide_version.md new file mode 100644 index 000000000..2c83f42cf --- /dev/null +++ b/docs/js-api/pyodide_version.md @@ -0,0 +1,18 @@ +(js_api_pyodide_version)= +# pyodide.version() + +Returns the pyodide version. + +It can be either the exact release version (e.g. `0.1.0`), or +the latest release version followed by the number of commits since, and +the git hash of the current commit (e.g. `0.1.0-1-bd84646`). + +**Parameters** + +None + +**Returns** + +| name | type | description | +|-----------|--------|------------------------| +| *version* | String | Pyodide version string | diff --git a/docs/loading_packages.md b/docs/loading_packages.md index d68becc2b..0a18661bb 100644 --- a/docs/loading_packages.md +++ b/docs/loading_packages.md @@ -1,15 +1,24 @@ (loading_packages)= -# Loading Python packages +# Loading packages -Only the Python standard library and six are available after importing Pyodide. To use other libraries, you’ll need to load their package using either, - - `pyodide.loadPackage` for packages built with pyodide. - - `micropip.install` for pure Python packages with wheels available on PyPi or on other URLs. +Only the Python standard library and six are available after importing Pyodide. +To use other libraries, you’ll need to load their package using either, + - {ref}`pyodide.loadPackage ` for packages built + with pyodide. + - `micropip.install` for pure Python packages with wheels available on PyPi or + on other URLs. ```{note} -Note that `micropip` can also be used to load packages built in pyodide (in which case it relies on `pyodide.loadPackage`). +Note that `micropip` can also be used to load packages built in pyodide (in +which case it relies on {ref}`pyodide.loadPackage +`). ``` -Alternatively you can run Python code without manually pre-loading packages. You can do this with {ref}`pyodide.runPythonAsync `) function, which will automatically download all packages that the code snippet imports. It only supports packages included in Pyodide (not on PyPi) at present. +Alternatively you can run Python code without manually pre-loading packages. +You can do this with {ref}`pyodide.runPythonAsync +`) function, which will automatically download all +packages that the code snippet imports. It only supports packages included in +Pyodide (not on PyPi) at present. ## Loading packages with pyodide.loadPackage @@ -65,7 +74,7 @@ stemmer.stemWords('go goes going gone'.split()) ``` For use outside of Iodide (just Python), you can use the `then` method on the -`Promise` that `micropip.install` returns to do work once the packages have +`Promise` that {func}`micropip.install` returns to do work once the packages have finished loading: ```py diff --git a/docs/new_packages.md b/docs/new_packages.md index e74b31fb9..43b04ef9b 100644 --- a/docs/new_packages.md +++ b/docs/new_packages.md @@ -21,8 +21,8 @@ libraries to the build. We automate the following steps: the virtual filesystem. Lastly, a `packages.json` file is output containing the dependency tree of all -packages, so `pyodide.loadPackage` can load a package's dependencies -automatically. +packages, so {ref}`pyodide.loadPackage ` can +load a package's dependencies automatically. ## mkpkg diff --git a/docs/requirements-doc.txt b/docs/requirements-doc.txt index fda4068e0..0602a1cd9 100644 --- a/docs/requirements-doc.txt +++ b/docs/requirements-doc.txt @@ -1,3 +1,5 @@ +# Temporarly commented as a workaround for pypa/pip#9031 # sphinx sphinx_rtd_theme myst-parser +sphinxcontrib-napoleon diff --git a/docs/type_conversions.md b/docs/type_conversions.md index 81a8b3060..c75a90b9d 100644 --- a/docs/type_conversions.md +++ b/docs/type_conversions.md @@ -3,8 +3,10 @@ Python to Javascript conversions occur: -- when returning the final expression from a `pyodide.runPython` call (evaluating a Python cell in Iodide) -- using `pyodide.pyimport` +- when returning the final expression from a + {ref}`pyodide.runPython ` call + (evaluating a Python cell in Iodide) +- using {ref}`pyodide.pyimport ` - passing arguments to a Javascript function from Python Javascript to Python conversions occur: @@ -146,8 +148,9 @@ foo.call_method(); // This will raise an exception, since the object has been ## Using Python objects from Javascript A Python object (in global scope) can be brought over to Javascript using the -`pyodide.pyimport` function. It takes a string giving the name of the variable, -and returns the object, converted to Javascript. +{ref}`pyodide.pyimport ` function. It takes a string +giving the name of the variable, and returns the object, converted to +Javascript. ```javascript var sys = pyodide.pyimport('sys'); diff --git a/docs/using_pyodide_from_iodide.md b/docs/using_pyodide_from_iodide.md index f15346099..188fddd8a 100644 --- a/docs/using_pyodide_from_iodide.md +++ b/docs/using_pyodide_from_iodide.md @@ -44,8 +44,8 @@ np.arange(10) For most uses, that is all you need to know. However, if you want to use your own custom package or load a package from -another provider, you'll need to use the `pyodide.loadPackage` function from a -Javascript chunk. For example, to load a special distribution of Numpy from +another provider, you'll need to use the {ref}`pyodide.loadPackage ` +function from a Javascript chunk. For example, to load a special distribution of Numpy from `custom.com`: ```js diff --git a/docs/using_pyodide_from_javascript.md b/docs/using_pyodide_from_javascript.md index d64e27ff1..3357d6ee5 100644 --- a/docs/using_pyodide_from_javascript.md +++ b/docs/using_pyodide_from_javascript.md @@ -31,9 +31,10 @@ languagePluginLoader.then(() => { ## Running Python code -Python code is run using the `pyodide.runPython` function. It takes as input a -string of Python code. If the code ends in an expression, it returns the result -of the expression, converted to Javascript objects (see {ref}`type_conversions`). +Python code is run using the {ref}`pyodide.runPython ` +function. It takes as input a string of Python +code. If the code ends in an expression, it returns the result of the +expression, converted to Javascript objects (see {ref}`type_conversions`). ```javascript pyodide.runPython(` @@ -123,7 +124,7 @@ Create and save a test `index.html` page with the following contents: ## Accessing Python scope from JavaScript -You can also access from JavaScript all functions and variables defined in Python using the {ref}`pyodide.globals `) object. +You can also access from JavaScript all functions and variables defined in Python using the {ref}`pyodide.globals `) object. For example, if you initialize the variable `x = numpy.ones([3,3])` in Python, you can access it from JavaScript in your browser's developer console as follows: `pyodide.globals.x`. The same goes for functions and imports. See {ref}`type_conversions` for more details. diff --git a/packages/micropip/micropip/micropip.py b/packages/micropip/micropip/micropip.py index 3e7ad708e..096b4e051 100644 --- a/packages/micropip/micropip/micropip.py +++ b/packages/micropip/micropip/micropip.py @@ -262,11 +262,25 @@ del _PackageManager def install(requirements: Union[str, List[str]]): - """ - Install the given package and all of its dependencies. + """Install the given package and all of its dependencies. - Returns a Promise that resolves when all packages have downloaded and - installed. + This only works for pure Python wheels or for packages built + in pyodide. If a package is not found in the pyodide repository + it will be loaded from PyPi. + + Parameters + ---------- + requirements + a requirements or a list of requirements to install. + Can be composed either of + + - package names, as defined in pyodide repository or on PyPi + - URLs pointing to pure Python wheels. The file name of such wheels + end with ``none-any.whl``. + + Returns + ------- + a Promise that resolves when all packages have downloaded and installed. """ def do_install(resolve, reject): diff --git a/src/pyodide.py b/src/pyodide.py index 9af12f4ee..2eb4f99c6 100644 --- a/src/pyodide.py +++ b/src/pyodide.py @@ -3,28 +3,50 @@ A library of helper utilities for connecting Python to the browser environment. """ import ast -import io +from io import StringIO from textwrap import dedent +from typing import Dict, List, Optional, Any __version__ = "0.15.0" -def open_url(url): +def open_url(url: str) -> StringIO: """ - Fetches a given *url* and returns a io.StringIO to access its contents. + Fetches a given URL + + Parameters + ---------- + url + URL to fetch + + Returns + ------- + a io.StringIO object with the contents of the URL. """ from js import XMLHttpRequest req = XMLHttpRequest.new() req.open("GET", url, False) req.send(None) - return io.StringIO(req.response) + return StringIO(req.response) -def eval_code(code, ns): - """ - Runs a string of code, the last part of which may be an expression. +def eval_code(code: str, ns: Dict[str, Any]) -> None: + """Runs a code string + + The last part of the provided code may be an expression. + + Parameters + ---------- + code + the Python code to run. + ns + `locals()` or `globals()` context where to execute code. + + Returns + ------- + None """ # handle mis-indented input from multi-line strings code = dedent(code) @@ -33,6 +55,7 @@ def eval_code(code, ns): if len(mod.body) == 0: return None + expr: Any if isinstance(mod.body[-1], ast.Expr): expr = ast.Expression(mod.body[-1].value) del mod.body[-1] @@ -47,10 +70,25 @@ def eval_code(code, ns): return None -def find_imports(code): +def find_imports(code: str) -> List[str]: """ - Finds the imports in a string of code and returns a list of their package - names. + Finds the imports in a string of code + + Parameters + ---------- + code + the Python code to run. + + Returns + ------- + A list of module names that are imported in the code. + + Examples + -------- + >>> from pyodide import find_imports + >>> code = "import numpy as np; import scipy.stats" + >>> find_imports(code) + ['numpy', 'scipy'] """ # handle mis-indented input from multi-line strings code = dedent(code) @@ -60,18 +98,30 @@ def find_imports(code): for node in ast.walk(mod): if isinstance(node, ast.Import): for name in node.names: - name = name.name - imports.add(name.split(".")[0]) + node_name = name.name + imports.add(node_name.split(".")[0]) elif isinstance(node, ast.ImportFrom): - name = node.module - imports.add(name.split(".")[0]) + module_name = node.module + if module_name is None: + continue + imports.add(module_name.split(".")[0]) return list(imports) -def as_nested_list(obj): - """ +def as_nested_list(obj) -> List: + """Convert a nested JS array to nested Python list. + Assumes a Javascript object is made of (possibly nested) arrays and converts them to nested Python lists. + + Parameters + ---------- + obj + a Javscript object made of nested arrays. + + Returns + ------- + Python list, or a nested Python list """ try: it = iter(obj) @@ -80,9 +130,26 @@ def as_nested_list(obj): return obj -def get_completions(code, cursor=None, namespaces=None): +def get_completions( + code: str, cursor: Optional[int] = None, namespaces: Optional[List] = None +) -> List[str]: """ - Get code autocompletion candidates. + Get code autocompletion candidates + + Note that this function requires to have the jedi module loaded. + + Parameters + ---------- + code + the Python code to complete. + cursor + optional position in the code at which to autocomplete + namespaces + a list of namespaces + + Returns + ------- + a list of autocompleted modules """ import jedi import __main__