Skip to content

Commit

Permalink
Merge pull request #83 from LanceMaverick/new-folder-structure
Browse files Browse the repository at this point in the history
New folder structure
  • Loading branch information
LanceMaverick committed Jan 30, 2017
2 parents e75c806 + cd4d24b commit 335f97b
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 52 deletions.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/callback/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from skybeard.utils import setup_beard

setup_beard("callback")
1 change: 1 addition & 0 deletions examples/configexample/config.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello_world: Hello world! Your config has been copied!
22 changes: 22 additions & 0 deletions examples/configexample/python/configexample/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from skybeard.beards import BeardChatHandler
from skybeard.predicates import Filters
from skybeard.utils import get_beard_config


config = get_beard_config()


class Echo(BeardChatHandler):

__userhelp__ = """A simple echo beard. Echos whatever it is sent."""

__commands__ = [
(Filters.text, 'msg_config',
'Messages the hello_world variable from config.')
]

# __init__ is implicit

async def msg_config(self, msg):
# await self.sender.sendMessage(msg['text'])
await self.sender.sendMessage(config['hello_world'])
6 changes: 6 additions & 0 deletions examples/configexample/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from skybeard.utils import setup_beard

setup_beard(
"configexample",
copy_config=True
)
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/database/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from skybeard.utils import setup_beard

setup_beard("database")
File renamed without changes.
1 change: 1 addition & 0 deletions examples/dicebeard/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dice<=1.1.0
3 changes: 3 additions & 0 deletions examples/dicebeard/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from skybeard.utils import setup_beard

setup_beard("dicebeard")
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import telepot
from skybeard.beards import BeardChatHandler, Filters
from skybeard.beards import BeardChatHandler
from skybeard.predicates import Filters


class Echo(BeardChatHandler):
Expand Down
5 changes: 5 additions & 0 deletions examples/echo/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from skybeard.utils import setup_beard

setup_beard(
"echo",
)
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/onerror/setup_beard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from skybeard.utils import setup_beard

setup_beard("onerror")
103 changes: 65 additions & 38 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python
import os
import asyncio
import sys
import logging
import itertools
import importlib
import argparse
import pyconfig
Expand All @@ -15,6 +15,12 @@

from skybeard.beards import Beard, BeardChatHandler, SlashCommand
from skybeard.help import create_help
from skybeard.utils import (is_module,
contains_setup_beard_py,
get_literal_path,
get_literal_beard_paths,
all_possible_beards,
PythonPathContext)
import config

logger = logging.getLogger(__name__)
Expand All @@ -24,36 +30,36 @@ class DuplicateCommand(Exception):
pass


def is_module(filename):
fname, ext = os.path.splitext(filename)
if ext == ".py":
return True
elif os.path.exists(os.path.join(filename, "__init__.py")):
return True
else:
return False
# def is_module(filename):
# fname, ext = os.path.splitext(filename)
# if ext == ".py":
# return True
# elif os.path.exists(os.path.join(filename, "__init__.py")):
# return True
# else:
# return False


def get_literal_path(path_or_autoloader):
try:
return path_or_autoloader.path
except AttributeError:
assert type(path_or_autoloader) is str,\
"beard_path is not a str or an AutoLoader!"
return path_or_autoloader
# def get_literal_path(path_or_autoloader):
# try:
# return path_or_autoloader.path
# except AttributeError:
# assert type(path_or_autoloader) is str,\
# "beard_path is not a str or an AutoLoader!"
# return path_or_autoloader


def get_literal_beard_paths(beard_paths):
return [get_literal_path(x) for x in beard_paths]
# def get_literal_beard_paths(beard_paths):
# return [get_literal_path(x) for x in beard_paths]


def all_possible_beards(paths):
literal_paths = get_literal_beard_paths(paths)
# def all_possible_beards(paths):
# literal_paths = get_literal_beard_paths(paths)

for path in literal_paths:
for f in os.listdir(path):
if is_module(os.path.join(path, f)):
yield os.path.basename(f)
# for path in literal_paths:
# for f in os.listdir(path):
# if is_module(os.path.join(path, f)):
# yield os.path.basename(f)


def delegator_beard_gen(beards):
Expand All @@ -71,22 +77,36 @@ def main(config):
if pyconfig.get('start_server'):
from skybeard import server

for beard_path in config.beard_paths:
sys.path.insert(0, get_literal_path(beard_path))

logger.info("The following plugins were found:\n {}".format(
', '.join(list(all_possible_beards(config.beard_paths)))))
logger.info("config.beards: {}".format(config.beards))

if config.beards == "all":
for beard_name in all_possible_beards(config.beard_paths):
importlib.import_module(beard_name)
beards_to_load = all_possible_beards(config.beard_paths)
else:
for beard_name in config.beards:
beards_to_load = config.beards

# Not sure importing is for the best
for beard_path, possible_beard in itertools.product(
config.beard_paths, beards_to_load):

with PythonPathContext(get_literal_path(beard_path)):
try:
importlib.import_module(possible_beard+".setup_beard")
except ImportError as ex:
# If the module named by possible_beard does not exist, pass.
#
# If the module named by possible_beard does exist, but
# .setup_beard does not exist, the module is imported anyway.
pass

# Check if all expected beards were imported.
#
# NOTE: This does not check for beards, only that the modules specified in
# config.beards have been imported.
for beard_name in beards_to_load:
try:
importlib.import_module(beard_name)

for beard_path in config.beard_paths:
sys.path.pop(0)
except ImportError as e:
logging.error("{} was not imported! Check your config.".format(
beard_name))
raise e

# Check if there are any duplicate commands
all_cmds = set()
Expand Down Expand Up @@ -124,11 +144,18 @@ def main(config):
parser.add_argument('--no-help', action='store_true')
parser.add_argument('-d', '--debug', action='store_const', dest="loglevel",
const=logging.DEBUG, default=logging.INFO)
parser.add_argument('--start-server', action='store_const', const=True, default=False)
parser.add_argument('--start-server', action='store_const', const=True,
default=False)
parser.add_argument('--no-auto-pip', action='store_const', const=True,
default=False)
parser.add_argument('--auto-pip-upgrade', action='store_const', const=True,
default=False)

parsed = parser.parse_args()

pyconfig.set('start_server', parsed.start_server)
pyconfig.set('no_auto_pip', parsed.no_auto_pip)
pyconfig.set('auto_pip_upgrade', parsed.auto_pip_upgrade)

logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
Expand Down
13 changes: 3 additions & 10 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
aiohttp==1.2.0
async-timeout==1.1.0
chardet==2.3.0
multidict==2.1.4
pkg-resources==0.0.0
telepot==10.4
urllib3==1.19.1
yarl==0.8.1
pyconfig==3.1.1
dataset==0.7.0
telepot<=10.4
pyconfig<=3.1.1
dataset<=0.7.0
100 changes: 98 additions & 2 deletions skybeard/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import os
import shutil
import importlib
import sys
import inspect
import shlex
import logging
import pip
import yaml

import pyconfig

logger = logging.getLogger(__name__)

Expand All @@ -11,15 +19,103 @@ def is_module(path):
fname, ext = os.path.splitext(path)
if ext == ".py":
return True
elif os.path.exists(os.path.join(path, "__init__.py")):
return True
try:
# Python 3 allows modules not to have an __init__.py
if any(os.path.splitext(x)[1] == ".py" for x in os.listdir(path)):
return True
except FileNotFoundError:
pass


def contains_setup_beard_py(path):
"""Checks if path contains setup_beard.py."""

return os.path.isfile(os.path.join(path, "setup_beard.py"))


class PythonPathContext:
def __init__(self, path_to_add):
self.path_to_add = path_to_add

def __enter__(self):
sys.path.insert(0, self.path_to_add)

def __exit__(self, type, value, tb):
assert sys.path[0] == self.path_to_add
sys.path.pop(0)


def get_beard_config(config_file="../../config.yml"):
"""Attempts to load a yaml file in the beard directory.
NOTE: The file location should be relative from where this function is
called.
"""
callers_frame = inspect.currentframe().f_back
logger.debug("This function was called from the file: " +
callers_frame.f_code.co_filename)
base_path = os.path.dirname(callers_frame.f_code.co_filename)
config = yaml.safe_load(open(os.path.join(base_path, config_file)))
return config


def setup_beard(beard_module_name,
*,
beard_python_path="python",
beard_requirements_file="requirements.txt",
config_file="config.yml",
example_config_file="config.yml.example",
copy_config=False):
"""Sets up a beard for use.
Note: beard_python_path must be a path relative to the file setup_beard is
called from.
"""
callers_frame = inspect.currentframe().f_back
logger.debug("This function was called from the file: " +
callers_frame.f_code.co_filename)
base_path = os.path.dirname(callers_frame.f_code.co_filename)

if copy_config:
if not os.path.isfile(os.path.join(base_path, config_file)):
logger.info("Attempting to copy config file.")
shutil.copyfile(
os.path.join(base_path, example_config_file),
os.path.join(base_path, config_file),
)

# Install requirements
requirements_file = os.path.join(base_path, beard_requirements_file)
if not pyconfig.get('no_auto_pip') and os.path.isfile(requirements_file):
pip_args = [
'install',
'-r',
requirements_file
]

if pyconfig.get('auto_pip_upgrade'):
pip_args.append('--upgrade')

pip.main(pip_args)
# Invalidate import path cache, since it's probably changed if new
# requirements have been installed
importlib.invalidate_caches()

# Import beard
beard_python_path = os.path.join(base_path, beard_python_path)
with PythonPathContext(beard_python_path):
# Attempt to import the module named specified in the call to
# setup_beard.
#
# Often, a module with the same name has already be imported, so the
# module is reloaded to ensure that if a module is found in
# beard_python_path called beard_module_name, *that* module is loaded.
mod = importlib.import_module(beard_module_name)
importlib.reload(mod)


def get_literal_path(path_or_autoloader):
"""Gets literal path from AutoLoader or returns input."""

Expand Down

0 comments on commit 335f97b

Please sign in to comment.