Skip to content

Commit 91817e3

Browse files
Use zetup.toplevel, .module for nodely, .bin
1 parent 29ecec8 commit 91817e3

File tree

2 files changed

+63
-70
lines changed

2 files changed

+63
-70
lines changed

nodely/__init__.py

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,33 @@
2222
"""
2323

2424
import json
25+
import os
2526
import sys
2627

2728
from path import Path
29+
from pkg_resources import get_distribution
2830
import whichcraft
2931
import zetup
3032

31-
# __version__ module is created by setuptools_scm during setup
32-
from .__version__ import version as __version__
33-
3433
from .error import NodeCommandError
3534

36-
__all__ = (
37-
'install', 'uninstall', 'which', 'Popen', 'call',
38-
'NodeCommandError')
35+
# HACK: Fix inconsistently hard-coded whichcraft.__version__
36+
whichcraft.__version__ = get_distribution('whichcraft').version
37+
38+
zetup.toplevel(__name__, [
39+
'NodeCommandError',
40+
'Popen',
41+
'call',
42+
'install',
43+
'uninstall',
44+
'which',
45+
])
3946

4047

41-
#: The absolute path to the local node_modules/ sub-directory used for
42-
# installing Node.js packages under the python environment root
48+
#: The absolute path to the local ``node_modules/`` sub-directory.
49+
#
50+
# Which is located under the current Python environment root, and which is
51+
# used for installing Node.js packages into
4352
NODE_MODULES_DIR = (Path(sys.prefix) / 'node_modules').mkdir_p()
4453

4554
# create a dummy package.json in python environment root for making npm
@@ -56,33 +65,33 @@
5665

5766
def install(package):
5867
"""
59-
Install given Node.js `package` into ``node_modules/`` of current Python
60-
environment
68+
Install given Node.js `package`.
69+
70+
Into ``node_modules/`` of current Python environment
6171
"""
6272
command = ['npm', 'install', package]
6373
with Path(sys.prefix):
6474
status = zetup.call(command)
6575
if status:
66-
raise RuntimeError("Command {!r} failed with status {}"
67-
.format(command, status))
76+
raise NodeCommandError(command, status, os.getcwd())
6877

6978

7079
def uninstall(package):
7180
"""
72-
Uninstall given Node.js `package` from ``node_modules/`` of current Python
73-
environment
81+
Uninstall given Node.js `package`.
82+
83+
From ``node_modules/`` of current Python environment
7484
"""
7585
command = ['npm', 'uninstall', package]
7686
with Path(sys.prefix):
7787
status = zetup.call(command)
7888
if status: # pragma: no cover
79-
raise RuntimeError("Command {!r} failed with status {}"
80-
.format(command, status))
89+
raise NodeCommandError(command, status, os.getcwd())
8190

8291

8392
def which(executable):
8493
"""
85-
Find `executable` in ``node_modules/.bin/`` of current Python environment
94+
Find `executable` in ``node_modules/.bin/`` of current Python environment.
8695
8796
:return: Absolute ``path.Path`` instance or ``None``
8897
"""
@@ -93,9 +102,13 @@ def which(executable):
93102

94103
def Popen(executable, args=None, **kwargs):
95104
"""
96-
Create a subprocess for given Node.js `executable` with given sequence
97-
of `args` strings and optional `kwargs` for ``zetup.Popen``, including all
98-
options for ``subprocess.Popen``
105+
Create a subprocess for given Node.js `executable`.
106+
107+
:param args:
108+
Optional sequence of command argument strings
109+
:param options:
110+
General options for ``zetup.call``, including all options for
111+
``subprocess.call``
99112
"""
100113
import nodely.bin
101114

@@ -105,9 +118,13 @@ def Popen(executable, args=None, **kwargs):
105118

106119
def call(executable, args=None, **kwargs):
107120
"""
108-
Call given Node.js `executable` with given sequence of `args` strings and
109-
optional `kwargs` for ``zetup.call``, including all options for
110-
``subprocess.call``
121+
Call given Node.js `executable`.
122+
123+
:param args:
124+
Optional sequence of command argument strings
125+
:param kwargs:
126+
General options for ``zetup.call``, including all options for
127+
``subprocess.call``
111128
"""
112129
import nodely.bin
113130

nodely/bin.py

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -34,68 +34,44 @@
3434

3535
from .error import NodeCommandError
3636

37-
__all__ = ['Command']
37+
zetup.module(
38+
__name__, ['Command'],
39+
__getitem__=lambda cmdname: __getitem__(cmdname),
40+
__getattr__=lambda name: __getattr__(name),
41+
__dir__=lambda: __dir__())
3842

3943

4044
#: Running on Windows?
4145
WIN = platform.system() == 'Windows'
4246

4347

44-
#: The original nodely.bin module object
45-
ORIGIN = sys.modules[__name__]
46-
47-
48-
class Module(ModuleType):
48+
def __getitem__(cmdname):
4949
"""
50-
Wrapper module class for :mod:`nodely.bin`.
50+
Get a :class:`nodely.bin.Command` instance for given `cmdname`.
5151
52-
Makes module directly act as command proxy to the ``node_modules/.bin/``
53-
directory via :meth:`.__getitem__` and :meth:`.__getattr__`
52+
:raises OSError: if executable can not be found
5453
"""
54+
return Command(cmdname)
5555

56-
def __init__(self):
57-
"""
58-
Set ``.__name__`` and ``.__doc__``, and update ``.__dict__``.
59-
60-
All taken from original ``nodely.bin`` module
61-
"""
62-
super(Module, self).__init__(__name__, ORIGIN.__doc__)
63-
self.__dict__.update(ORIGIN.__dict__)
64-
65-
def __getitem__(self, cmdname):
66-
"""
67-
Get a :class:`nodely.bin.Command` instance for given `cmdname`.
6856

69-
:raises OSError: if executable can not be found
70-
"""
71-
return Command(cmdname)
57+
def __getattr__(name):
58+
"""
59+
Get a :class:`nodely.bin.Command` instance for given command `name`.
7260
73-
def __getattr__(self, name):
74-
"""
75-
Get a :class:`nodely.bin.Command` instance for given command `name`.
61+
:raises OSError: if executable cannot be found
62+
"""
63+
try:
64+
return __getitem__(name)
7665

77-
:raises OSError: if executable cannot be found
78-
"""
79-
try: # first check if original module has the given attribute
80-
return getattr(ORIGIN, name)
81-
except AttributeError:
82-
pass
83-
# and don't treat special Python member names as Node.js commands
84-
if name.startswith('__'):
85-
raise AttributeError(
86-
"{!r} has no attribute {!r}".format(self, name))
87-
return self[name]
88-
89-
def __dir__(self):
90-
cmdnames = (f.basename()
91-
for f in (NODE_MODULES_DIR / '.bin').files())
92-
if WIN: # pragma: no cover
93-
cmdnames = (cmd for cmd in cmdnames if cmd.ext.lower() != '.cmd')
94-
return dir(ORIGIN) + list(cmdnames)
66+
except (IOError, OSError) as exc:
67+
raise AttributeError(str(exc))
9568

9669

97-
# replace nodely.bin module with wrapper instance
98-
sys.modules[__name__] = Module()
70+
def __dir__():
71+
cmdnames = (f.basename() for f in (NODE_MODULES_DIR / '.bin').files())
72+
if WIN: # pragma: no cover
73+
cmdnames = (cmd for cmd in cmdnames if cmd.ext.lower() != '.cmd')
74+
return list(cmdnames)
9975

10076

10177
class Command(zetup.object, Path):

0 commit comments

Comments
 (0)