Skip to content

Commit

Permalink
Merge pull request #36 from TGoddessana/dev
Browse files Browse the repository at this point in the history
Restructure the project
  • Loading branch information
TGoddessana authored Nov 18, 2023
2 parents d3363fa + 3da669b commit b656efa
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 125 deletions.
5 changes: 2 additions & 3 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
[flake8]
select = B,B9,C,D,DAR,E,F,N,RST,W
ignore = E203,E501,RST201,RST203,RST301,W503,D100,D104,C901,B904,B307
select = B,B9,C,D,DAR,E,F,N,RST,S,W
ignore = E203,E501,RST201,RST203,RST301,W503,D100,D101,D102,D104
max-line-length = 80
max-complexity = 10
docstring-convention = google
per-file-ignores = tests/*:S101
rst-roles = class,const,func,meth,mod,ref
rst-directives = deprecated
157 changes: 35 additions & 122 deletions src/flask_moreshell/__main__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import os
import sys

import click
from flask.cli import with_appcontext
from flask.globals import current_app

from flask_moreshell.shells import BPythonShell
from flask_moreshell.shells import IpythonShell
from flask_moreshell.shells import PTPythonShell
from flask_moreshell.shells import PythonShell


shells = {
"ipython": IpythonShell,
"bpython": BPythonShell,
"ptpython": PTPythonShell,
"python": PythonShell,
}


@click.command(context_settings=dict(ignore_unknown_options=True))
@click.option("--shelltype", type=click.STRING, default=None)
@click.option(
"--shelltype",
type=click.Choice(["ipython", "bpython", "ptpython", "python"]),
default=None,
)
@with_appcontext
def shell(shelltype: str):
def shell(shelltype: str) -> None:
"""Run `flask shell` command with IPython, BPython, PTPython.
If you have IPython, PYTPython, or BPython installed, run them with your
Expand All @@ -20,124 +35,22 @@ def shell(shelltype: str):
:param shelltype: type of shell to use.
"""
if shelltype:
try:
if shelltype == "ipython":
_load_ipython()
elif shelltype == "bpython":
_load_bpython()
elif shelltype == "ptpython":
_load_ptpython()
elif shelltype == "python":
_load_python()
except ModuleNotFoundError:
raise ModuleNotFoundError(f"{shelltype} is not installed on your system.")
else:
try:
_load_ipython()
sys.exit()
except ModuleNotFoundError:
pass
try:
_load_bpython()
sys.exit()
except ModuleNotFoundError:
pass
try:
_load_ptpython()
sys.exit()
except ModuleNotFoundError:
_load_python()


def _load_ipython():
"""Load ipython shell, with current application."""
import IPython
from IPython.terminal.ipapp import load_default_config
from traitlets.config.loader import Config
def try_load_shell(_shelltype: str) -> None:
shell_class = shells.get(_shelltype)
shell_class().load()

if "IPYTHON_CONFIG" in current_app.config:
config = Config(current_app.config["IPYTHON_CONFIG"])
# If the user specifies a shell type, try to load it
if shelltype:
try_load_shell(shelltype)
else:
config = load_default_config()

config.TerminalInteractiveShell.banner1 = "".join(
f"Python {sys.version} on {sys.platform}\n"
f"IPython: {IPython.__version__}\n"
f"App: {current_app.import_name} [{current_app.debug}]\n"
f"Instance: {current_app.instance_path}\n"
)

IPython.start_ipython(
argv=[],
user_ns=current_app.make_shell_context(),
config=config,
)


def _load_bpython():
"""Load bpython shell, with current application."""
import bpython # type: ignore[import]

banner = "".join(
f"Python {sys.version} on {sys.platform}\n"
f"BPython: {bpython.__version__}\n"
f"App: {current_app.import_name} [{current_app.debug}]\n"
f"Instance: {current_app.instance_path}\n"
)

ctx = {}
ctx.update(current_app.make_shell_context())

bpython.embed(banner=banner, locals_=ctx)


def _load_ptpython():
from importlib.metadata import version

from flask.globals import _app_ctx_stack
from ptpython.repl import embed

banner = "".join(
f"Python {sys.version} on {sys.platform}\n"
f"PTPython: {version('ptpython')}\n"
f"App: {current_app.import_name} [{current_app.debug}]\n"
f"Instance: {current_app.instance_path}\n"
)

app = _app_ctx_stack.top.app
ctx = {}

# Support the regular Python interpreter startup script if someone
# is using it.
startup = os.environ.get("PYTHONSTARTUP")
if startup and os.path.isfile(startup):
with open(startup) as f:
eval(compile(f.read(), startup, "exec"), ctx)

ctx.update(app.make_shell_context())
print(banner)
embed(globals=ctx)


def _load_python():
"""Load default python shell."""
import code

ctx: dict = {}
startup = os.environ.get("PYTHONSTARTUP")
if startup and os.path.isfile(startup):
with open(startup) as f:
eval(compile(f.read(), startup, "exec"), ctx)
ctx.update(current_app.make_shell_context())
interactive_hook = getattr(sys, "__interactivehook__", None)
if interactive_hook is not None:
try:
import readline
from rlcompleter import Completer
except ImportError:
pass
else:
readline.set_completer(Completer(ctx).complete)
interactive_hook()
code.interact(local=ctx)
preferred_shells = ["ipython", "bpython", "ptpython", "python"]
for shelltype in preferred_shells:
try:
try_load_shell(shelltype)
break
except ModuleNotFoundError:
continue
if not shelltype:
print("No shell type is installed or recognized on your system.")
sys.exit(1)
14 changes: 14 additions & 0 deletions src/flask_moreshell/shells/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from .base_shell import BaseShell
from .bpython_shell import BPythonShell
from .ipython_shell import IpythonShell
from .ptpython_shell import PTPythonShell
from .python_shell import PythonShell


__all__ = [
"BaseShell",
"BPythonShell",
"IpythonShell",
"PTPythonShell",
"PythonShell",
]
50 changes: 50 additions & 0 deletions src/flask_moreshell/shells/base_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os
import sys
from abc import ABC
from abc import abstractmethod

from flask.globals import current_app


class BaseShell(ABC):
"""Base class for all shells.
you can extend this class to implement your own shell.
"""

def get_banner(self) -> str:
"""
Return banner text to be displayed when shell starts.
override this method to customize banner text.
"""
python_info = f"Python {sys.version} on {sys.platform}"
shell_info = f"{self.get_shell_name()} {self.get_shell_version()}"
app_info = (
f"App: {current_app.import_name} "
f"[debug: {current_app.debug}, testing: {current_app.testing}]"
)
config = f"Config: {current_app.config_class.__name__}"
return f"{python_info}\n{shell_info}\n{app_info}\n{config}"

@staticmethod
def load_context(ctx: dict) -> None: # type: ignore
startup = os.environ.get("PYTHONSTARTUP")

if startup and os.path.isfile(startup):
with open(startup) as f:
exec(f.read(), ctx) # noqa: S102

ctx.update(current_app.make_shell_context())

@abstractmethod
def get_shell_name(self) -> str:
pass

@abstractmethod
def get_shell_version(self) -> str:
pass

@abstractmethod
def load(self) -> None:
pass
20 changes: 20 additions & 0 deletions src/flask_moreshell/shells/bpython_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from flask_moreshell.shells import BaseShell


try:
import bpython # type: ignore
except ModuleNotFoundError as e:
raise ModuleNotFoundError("BPython is not installed on your system.") from e


class BPythonShell(BaseShell):
def get_shell_name(self) -> str:
return "BPython"

def get_shell_version(self) -> str:
return str(bpython.__version__)

def load(self) -> None:
ctx = {} # type: ignore
self.load_context(ctx)
bpython.embed(banner=self.get_banner(), locals_=ctx)
37 changes: 37 additions & 0 deletions src/flask_moreshell/shells/ipython_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any

from flask.globals import current_app

from flask_moreshell.shells import BaseShell


try:
import IPython
from IPython.terminal.ipapp import load_default_config
from traitlets.config.loader import Config
except ModuleNotFoundError as e:
raise ModuleNotFoundError("IPython is not installed on your system.") from e


class IpythonShell(BaseShell):
def get_shell_name(self) -> str:
return "IPython"

def get_shell_version(self) -> str:
return IPython.__version__

def load(self) -> None:
config = self._get_config()
config.TerminalInteractiveShell.banner1 = self.get_banner()

IPython.start_ipython(
argv=[],
user_ns=current_app.make_shell_context(),
config=config,
)

@staticmethod
def _get_config() -> Any | None:
if "IPYTHON_CONFIG" in current_app.config:
return Config(current_app.config["IPYTHON_CONFIG"])
return load_default_config()
20 changes: 20 additions & 0 deletions src/flask_moreshell/shells/ptpython_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import sys
from importlib.metadata import version

from ptpython.repl import embed

from flask_moreshell.shells import BaseShell


class PTPythonShell(BaseShell):
def get_shell_name(self) -> str:
return "PTPython"

def get_shell_version(self) -> str:
return version("ptpython")

def load(self) -> None:
ctx = {} # type: ignore
self.load_context(ctx)
sys.stdout.write(f"{self.get_banner()}\n")
embed(globals=ctx)
28 changes: 28 additions & 0 deletions src/flask_moreshell/shells/python_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import code
import sys

from flask_moreshell.shells import BaseShell


class PythonShell(BaseShell):
def get_shell_name(self) -> str:
return "Python"

def get_shell_version(self) -> str:
return sys.version

def load(self) -> None:
ctx = {} # type: ignore
self.load_context(ctx)

interactive_hook = getattr(sys, "__interactivehook__", None)
if interactive_hook is not None:
try:
import readline
from rlcompleter import Completer
except ImportError:
pass
else:
readline.set_completer(Completer(ctx).complete)
interactive_hook()
code.interact(local=ctx, banner=self.get_banner())
Empty file added tests/shells/__init__.py
Empty file.

0 comments on commit b656efa

Please sign in to comment.