Skip to content

Commit 01cd66d

Browse files
committed
ENH: add space and remove_space magics
1 parent 81e3fb5 commit 01cd66d

File tree

15 files changed

+720
-4
lines changed

15 files changed

+720
-4
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# distribution
2+
dist
3+
build
4+
5+
# python
6+
__pycache__
7+
*.egg-info
8+
9+
# testing
10+
.pytest_cache
11+
.tox

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Davide Sarra
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include LICENSE.txt
2+
include README.md

README.md

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,90 @@
1-
# `jupyter_spaces`
1+
# Jupyter Spaces
22

3-
`jupyter_spaces` is an IPython extension for creating parallel namespaces
4-
within the user namespace. It is designed to be used via IPython magics in
5-
Jupyter notebooks.
3+
Jupyter Spaces is an IPython extension for creating parallel namespaces
4+
availabe within the user namespace. It is designed to be used via IPython
5+
magics in Jupyter notebooks.
6+
7+
## Installation
8+
9+
```bash
10+
pip install jupyter_spaces
11+
```
12+
13+
`jupyter_spaces` supports Python versions `3.4`, `3.5` and `3.6`.
14+
15+
## Usage
16+
17+
### Load `jupyter_spaces` extension
18+
19+
```python
20+
%load_ext jupyter_spaces
21+
```
22+
23+
### Reload `jupyter_spaces` extension
24+
25+
```python
26+
%reload_ext jupyter_spaces
27+
```
28+
29+
Reloading the extension will remove all spaces.
30+
31+
### Run a cell within a space
32+
33+
```python
34+
%%space <space-name>
35+
alpha = 0.50
36+
print(alpha)
37+
```
38+
39+
When you execute a cell within a space, all references are firstly searched in
40+
the space namespace and secondly in the user namespace. All assignments are
41+
made in the space namespace. However, as expected, `global` assigments are made
42+
in the user namespace.
43+
44+
#### Console output
45+
46+
Conversely to the standard usage of the python console, you need to use print
47+
to get a output in the console or Jupyter cell output.
48+
49+
- No output to console
50+
```python
51+
%%space <space-name>
52+
100
53+
```
54+
- Output to console
55+
```python
56+
%%space <space-name>
57+
print(100)
58+
```
59+
60+
### Remove a space
61+
62+
```python
63+
%remove_space <space-name>
64+
```
65+
66+
### Access all spaces at once
67+
68+
Without using any magic you can access the spaces' namespaces.
69+
This might be useful to jointly post-process or compare the spaces' contents.
70+
71+
```python
72+
from jupyter_spaces import get_spaces
73+
74+
spaces = get_spaces()
75+
space = spaces[<space-name>]
76+
reference = space.namespace[<reference-name>]
77+
```
78+
79+
`Space` objects have two properties:
80+
81+
- `name` the name of the space
82+
- `namespace` a dictionary with the namespace of the space
83+
84+
Modifying the spaces via `get_spaces` will actually modify the underlying
85+
spaces.
86+
87+
## Acknowledgements
88+
89+
Many thanks to [Yeray Diaz Diaz](https://github.com/yeraydiazdiaz) and
90+
[Karol Duleba](https://github.com/mrfuxi)!

dev-requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ipython>=6.2.1
2+
nbconvert==5.3.1
3+
nbformat==4.4.0
4+
nbval==0.9.0
5+
pytest==3.5.0
6+
tox==3.0.0

jupyter_spaces/__init__.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Jupyter Spaces is an IPython extension for creating parallel namespaces
2+
availabe within the user namespace. It is designed to be used via IPython
3+
magics in Jupyter notebooks.
4+
5+
Copyright (c) 2018 Davide Sarra.
6+
MIT License, see LICENSE.txt for more details.
7+
"""
8+
from types import MappingProxyType
9+
10+
from jupyter_spaces.magics import SpaceMagic as _SpaceMagic
11+
from jupyter_spaces.magics import space_register as _space_register
12+
13+
14+
def get_spaces():
15+
"""Get a proxy mapping of the spaces.
16+
17+
Returns:
18+
dict: Mapping of spaces with keys being space names and values being
19+
space namespaces.
20+
"""
21+
return MappingProxyType(_space_register.register)
22+
23+
24+
def load_ipython_extension(ipython):
25+
"""Load IPython extension."""
26+
ipython.register_magics(_SpaceMagic)
27+
28+
29+
def unload_ipython_extension(ipython):
30+
"""Unload IPython extension. All existing spaces will be removed."""
31+
_space_register.remove_all_spaces()

jupyter_spaces/errors.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class BaseJupyterSpaceError(Exception):
2+
pass
3+
4+
5+
class RegistryError(BaseJupyterSpaceError):
6+
pass

jupyter_spaces/magics.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from IPython.core.magic import cell_magic, line_magic, Magics, magics_class
2+
3+
from jupyter_spaces.space import SpaceRegister
4+
5+
6+
space_register = SpaceRegister()
7+
8+
9+
@magics_class
10+
class SpaceMagic(Magics):
11+
12+
@cell_magic
13+
def space(self, line, cell):
14+
"""Execute cell contents using the space namespace as locals and the
15+
user namespace as globals.
16+
17+
Args:
18+
line (str): Content following `%%space` magic call, expected to
19+
match the space name. If the provided space name has already
20+
been used and not been removed, the same space object is used.
21+
cell (str): Content following the first line.
22+
23+
Examples:
24+
>>> %%space space_name
25+
... alpha = 0.50
26+
... print(alpha)
27+
"""
28+
space = space_register.get_space(
29+
name=line, outer_space=self.shell.user_ns)
30+
space.execute(source=cell)
31+
32+
@line_magic
33+
def remove_space(self, line):
34+
"""Remove a space.
35+
36+
Args:
37+
line (str): Content following `%%remove_space` magic call, expected
38+
to match the space name to remove.
39+
40+
Examples:
41+
>>> %remove_space space_name
42+
"""
43+
space_register.remove_space(name=line)

jupyter_spaces/space.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from jupyter_spaces.errors import RegistryError
2+
3+
4+
class SpaceRegister:
5+
__slots__ = ['_register']
6+
7+
def __init__(self):
8+
"""Initialise SpaceRegister instance."""
9+
self._register = {}
10+
11+
@property
12+
def register(self):
13+
"""Register.
14+
15+
Returns:
16+
dict: Register of Space instances.
17+
"""
18+
return self._register
19+
20+
def get_space(self, name, outer_space):
21+
"""Get existing Space if a Space has the same name, otherwise create
22+
and get new Space.
23+
24+
Args:
25+
name (str): Name of the Space.
26+
outer_space (dict): User namespace.
27+
28+
Returns:
29+
Space: Space.
30+
"""
31+
if name not in self._register:
32+
self._register[name] = Space(name=name, outer_space=outer_space)
33+
return self._register[name]
34+
35+
def remove_space(self, name):
36+
"""Remove Space from register.
37+
38+
Args:
39+
name (str): Name of the Space.
40+
41+
Raises:
42+
RegistryError: If no registered Space has the name passed.
43+
"""
44+
try:
45+
del self._register[name]
46+
except KeyError:
47+
raise RegistryError('Failed to forget space {name} because '
48+
'it does not exist'.format(name=name))
49+
50+
def remove_all_spaces(self):
51+
"""Remove all Spaces from register."""
52+
self._register.clear()
53+
54+
55+
class Space:
56+
__slots__ = ['_name', '_inner_space', '_outer_space']
57+
58+
def __init__(self, name, outer_space):
59+
"""Initialise Space instance.
60+
61+
Args:
62+
name (str): Name of the space.
63+
outer_space (dict): Outer namespace.
64+
"""
65+
self._name = name
66+
self._inner_space = {}
67+
self._outer_space = outer_space
68+
69+
def __repr__(self):
70+
return "Space(name='{name}' namespace={namespace})".format(
71+
name=self.name, namespace=self.namespace)
72+
73+
@property
74+
def name(self):
75+
"""Name of the Space instance.
76+
77+
Returns:
78+
str: Name.
79+
"""
80+
return self._name
81+
82+
@property
83+
def namespace(self):
84+
"""Namespace of the Space instance.
85+
86+
Note that modifying the namespace will modify the actual namespace
87+
of the Space instance.
88+
89+
Returns:
90+
dict: Namespace.
91+
"""
92+
return self._inner_space
93+
94+
def execute(self, source):
95+
"""Execute source code inside the space namespace and outer namespace.
96+
97+
Args:
98+
source (str): Source code.
99+
"""
100+
compiled_source = compile(
101+
source=source, filename='<string>', mode='exec')
102+
exec(compiled_source, self._outer_space, self._inner_space)

setup.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python
2+
from setuptools import find_packages, setup
3+
4+
NAME = 'jupyter_spaces'
5+
VERSION = '0.0.1'
6+
AUTHOR = 'Davide Sarra'
7+
URL = 'https://github.com/davidesarra/jupyter_spaces'
8+
DESCRIPTION = 'Create parallel namespaces in Jupyter Notebooks'
9+
LICENSE = 'MIT'
10+
CLASSIFIERS = [
11+
'Environment :: Console',
12+
'Framework :: IPython',
13+
'Framework :: Jupyter',
14+
'Intended Audience :: Developers',
15+
'Intended Audience :: Science/Research',
16+
'License :: OSI Approved :: MIT License',
17+
'Operating System :: OS Independent',
18+
'Programming Language :: Python :: 3',
19+
'Programming Language :: Python :: 3.4',
20+
'Programming Language :: Python :: 3.5',
21+
'Programming Language :: Python :: 3.6',
22+
]
23+
KEYWORDS = 'jupyter ipython magic extension namespace'
24+
25+
with open('README.md', 'r') as f:
26+
LONG_DESCRIPTION = f.read()
27+
28+
REQUIREMENTS = [
29+
'ipython>=6.2.1',
30+
]
31+
32+
if __name__ == "__main__":
33+
setup(
34+
name=NAME,
35+
version=VERSION,
36+
author=AUTHOR,
37+
url=URL,
38+
license=LICENSE,
39+
classifiers=CLASSIFIERS,
40+
keywords=KEYWORDS,
41+
description=DESCRIPTION,
42+
long_description=LONG_DESCRIPTION,
43+
long_description_content_type='text/markdown',
44+
packages=find_packages(exclude=['tests']),
45+
install_requires=REQUIREMENTS,
46+
python_requires='~=3.4',
47+
)

0 commit comments

Comments
 (0)