Skip to content

Commit

Permalink
Merge pull request #479 from MasoniteFramework/develop
Browse files Browse the repository at this point in the history
beta 3 PR
  • Loading branch information
josephmancuso authored Nov 17, 2018
2 parents 520b83c + 889c196 commit 50e55a1
Show file tree
Hide file tree
Showing 26 changed files with 445 additions and 377 deletions.
3 changes: 3 additions & 0 deletions app/http/controllers/TestController.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def show(self):
def testing(self):
return 'test'

def json_response(self):
return {'id': 1}

def post_test(self):
return 'post_test'

Expand Down
4 changes: 2 additions & 2 deletions masonite/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ def resolve(self, obj, *resolving_arguments):
" constructor using the 'resolve_parameters=True' keyword argument.")
try:
return obj(*provider_list)
except TypeError:
raise ContainerError('Tried resolving the too many dependencies.')
except TypeError as e:
raise ContainerError('Either Tried resolving the too many dependencies or {}'.format(str(e)))

def collect(self, search):
"""Fetch a dictionary of objects using a search query.
Expand Down
335 changes: 35 additions & 300 deletions masonite/commands/ServeCommand.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Serve Masonite Application Command."""
import os
import sys

import time
import subprocess
import threading
import waitress
import os

from hupper.logger import DefaultLogger, LogLevel
from hupper.reloader import Reloader, find_default_monitor_factory
from cleo import Command


Expand All @@ -14,310 +12,47 @@ class ServeCommand(Command):
Run the Masonite server.
serve
{--port=8000 : Specify which port to run the server}
{--host=127.0.0.1 : Specify which ip address to run the server}
{--p|port=8000 : Specify which port to run the server}
{--b|host=127.0.0.1 : Specify which ip address to run the server}
{--r|reload : Make the server automatically reload on file changes}
{--i|reload-interval=1 : Number of seconds to wait to reload after changed are detected}
"""

def handle(self):
self._check_patch()
if self.option('reload'):
self._run_with_reloader(extra_files=[".env"])

else:
self._run_application()

def _run_application(self):
from wsgi import application
waitress.serve(
application,
host=self.option('host'),
port=self.option('port'))

def _check_patch(self):
patched = False

with open('wsgi.py', 'r') as file:
# read a list of lines into data
data = file.readlines()

# change the line that starts with KEY=
for line_number, line in enumerate(data):
if line.startswith("for provider in container.make('Providers'):"):
patched = True
break

if not patched:
print('\033[93mWARNING: {}\033[0m'.format(
"Your application does not have a 2.0 patch! You can read more about this patch here: \
https://dev.to/josephmancuso/masonite-framework-20-patch-3op2"))
logger = DefaultLogger(LogLevel.INFO)

def _run_with_reloader(self, extra_files=None, interval=1):
"""Run the given function in an independent python interpreter."""
import signal
# worker args are pickled and then passed to the new process
worker_args = [
self.option("host"), self.option("port"), "wsgi:application",
]

reloader = WatchdogReloaderLoop(
extra_files, interval, log_func=self.comment)
reloader = Reloader(
"masonite.commands._devserver.run",
find_default_monitor_factory(logger),
logger,
worker_args=worker_args,
)

signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
self._run_reloader(reloader, extra_files=[".env"])

else:
from wsgi import application
from ._devserver import run
run(self.option("host"), self.option("port"), application)

def _run_reloader(self, reloader, extra_files=[]):
reloader._capture_signals()
reloader._start_monitor()
for blob in extra_files:
reloader.monitor.add_path(os.path.join(os.getcwd(), blob))
try:
if os.environ.get('MASONITE_RUN_MAIN') == 'true':
t = threading.Thread(target=self._run_application, args=())
t.setDaemon(True)
t.start()
reloader.run()
else:
sys.exit(reloader.restart_with_reloader())
while True:
if not reloader._run_worker():
reloader._wait_for_changes()
time.sleep(float(self.option('reload-interval')))
except KeyboardInterrupt:
pass


'''
|--------------------------------------------------------------------------
| Automatic Reloading Code
|--------------------------------------------------------------------------
|
| The grand majority of this auto-reloading code is based on the automatic
| reloading code provided by Werkzeug. I edited it to remove support for
| Python 2x and to use only Watchdog, since we will know that watchdog
| is present. Additionally, I changed the light logging it had to hook
| into Cleo's logging system.
|
| Werkzeug is licensed under a BSD-like open source license. The original
| work can be found at the link below.
|
| https://github.com/pallets/werkzeug
|
'''

iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))


def _iter_module_files():
"""Iterate over all relevant Python files.
It goes through all
loaded files from modules, all files in folders of already loaded modules
as well as all files reachable through a package.
"""
# The list call is necessary on Python 3 in case the module
# dictionary modifies during iteration.
for module in list(sys.modules.values()):
if module is None:
continue

filename = getattr(module, '__file__', None)

if filename:
if os.path.isdir(filename) and \
os.path.exists(os.path.join(filename, "__init__.py")):
filename = os.path.join(filename, "__init__.py")

old = None

while not os.path.isfile(filename):
old = filename
filename = os.path.dirname(filename)
if filename == old:
break
else:
if filename[-4:] in ('.pyc', '.pyo'):
filename = filename[:-1]
yield filename


def _find_observable_paths(extra_files=None):
"""Find all paths that should be observed."""
rv = set(os.path.dirname(os.path.abspath(x))
if os.path.isfile(x) else os.path.abspath(x)
for x in sys.path)

for filename in extra_files or ():
rv.add(os.path.dirname(os.path.abspath(filename)))

for module in list(sys.modules.values()):
fn = getattr(module, '__file__', None)

if fn is None:
continue

fn = os.path.abspath(fn)
rv.add(os.path.dirname(fn))

return _find_common_roots(rv)


def _get_args_for_reloading():
"""Return the executable.
This contains a workaround for windows
if the executable is incorrectly reported to not have the .exe
extension which can cause bugs on reloading.
"""
rv = [sys.executable]
py_script = sys.argv[0]

if os.name == 'nt' and not os.path.exists(py_script) and \
os.path.exists(py_script + '.exe'):
py_script += '.exe'

if os.path.splitext(rv[0])[1] == '.exe' and os.path.splitext(py_script)[1] == '.exe':
rv.pop(0)

rv.append(py_script)
rv.extend(sys.argv[1:])
return rv


def _find_common_roots(paths):
"""Out of some paths it finds the common roots that need monitoring."""
paths = [x.split(os.path.sep) for x in paths]
root = {}

for chunks in sorted(paths, key=len, reverse=True):
node = root
for chunk in chunks:
node = node.setdefault(chunk, {})
node.clear()

rv = set()

def _walk(node, path):
for prefix, child in iteritems(node):
_walk(child, path + (prefix,))
if not node:
rv.add('/'.join(path))

_walk(root, ())
return rv


class ReloaderLoop(object):
name = None

# monkeypatched by testsuite. wrapping with `staticmethod` is required in
# case time.sleep has been replaced by a non-c function (e.g. by
# `eventlet.monkey_patch`) before we get here
_sleep = staticmethod(time.sleep)

def __init__(self, extra_files=None, interval=1, log_func=None):
self.extra_files = set(os.path.abspath(x)
for x in extra_files or ())
self.interval = interval
self.log_func = log_func

def _log(self, message):
if self.log_func:
self.log_func(' [*] {}'.format(message))

def run(self):
pass

def restart_with_reloader(self):
"""Spawn a new Python interpreter with the same arguments as this one, but running the reloader thread."""
while 1:
self._log('Restarting with {}'.format(self.name))
args = _get_args_for_reloading()
new_environ = os.environ.copy()
new_environ['MASONITE_RUN_MAIN'] = 'true'

exit_code = subprocess.call(args, env=new_environ,
close_fds=False)
if exit_code != 3:
return exit_code

def trigger_reload(self, filename):
self.log_reload(filename)
sys.exit(3)

def log_reload(self, filename):
filename = os.path.abspath(filename)
self._log('Detected change in {0!r}, reloading'.format(filename))


class WatchdogReloaderLoop(ReloaderLoop):

def __init__(self, *args, **kwargs):
ReloaderLoop.__init__(self, *args, **kwargs)
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
self.observable_paths = set()

def _check_modification(filename):
if filename in self.extra_files:
self.trigger_reload(filename)

dirname = os.path.dirname(filename)

if dirname.startswith(tuple(self.observable_paths)):
if filename.endswith(('.pyc', '.pyo', '.py')):
self.trigger_reload(filename)

class _CustomHandler(FileSystemEventHandler):

def on_created(self, event):
_check_modification(event.src_path)

def on_modified(self, event):
_check_modification(event.src_path)

def on_moved(self, event):
_check_modification(event.src_path)
_check_modification(event.dest_path)

def on_deleted(self, event):
_check_modification(event.src_path)

reloader_name = Observer.__name__.lower()

if reloader_name.endswith('observer'):
reloader_name = reloader_name[:-8]
reloader_name += ' reloader'

self.name = reloader_name
self.observer_class = Observer
self.event_handler = _CustomHandler()
self.should_reload = False

def trigger_reload(self, filename):
# This is called inside an event handler, which means throwing
# SystemExit has no effect.
# https://github.com/gorakhargosh/watchdog/issues/294
self.should_reload = True
self.log_reload(filename)

def run(self):
watches = {}
observer = self.observer_class()
observer.start()

try:
while not self.should_reload:
to_delete = set(watches)
paths = _find_observable_paths(self.extra_files)

for path in paths:
if path not in watches:
try:
watches[path] = observer.schedule(
self.event_handler, path, recursive=True)
except OSError:
# Clear this path from list of watches We don't want
# the same error message showing again in the next
# iteration.
watches[path] = None
to_delete.discard(path)

for path in to_delete:
watch = watches.pop(path, None)
if watch is not None:
observer.unschedule(watch)

self.observable_paths = paths
self._sleep(self.interval)
finally:
observer.stop()
observer.join()

sys.exit(3)
reloader._stop_monitor()
reloader._restore_signals()
Loading

0 comments on commit 50e55a1

Please sign in to comment.