Skip to content
This repository has been archived by the owner on Dec 26, 2021. It is now read-only.

Feature/10 #28

Merged
merged 15 commits into from Feb 7, 2021
5 changes: 5 additions & 0 deletions .github/workflows/pythontest.yml
Expand Up @@ -20,6 +20,11 @@ jobs:
run: |
make init
- name: Test with pytest
env:
MAIL_HOST: ${{ github.secrets.MAIL_HOST }}
MAIL_PORT: ${{ github.secrets.MAIL_PORT }}
MAIL_USERNAME: ${{ github.secrets.MAIL_USERNAME }}
MAIL_PASSWORD: ${{ github.secrets.MAIL_PASSWORD }}
run: |
make test
lint:
Expand Down
5 changes: 3 additions & 2 deletions setup.py
Expand Up @@ -8,7 +8,7 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version="4.0.0",
version="4.0.0a",
package_dir={"": "src"},
description="The Masonite Framework",
long_description=long_description,
Expand Down Expand Up @@ -37,7 +37,8 @@
"hupper>=1.10,<1.11",
"waitress>=1.4,<1.5",
"bcrypt>=3.2,<3.3",
"whitenoise>=5.2,<5.3"
"whitenoise>=5.2,<5.3",
"python-dotenv>=0.15,<0.16",
],
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
Expand Down
49 changes: 49 additions & 0 deletions src/masonite/drivers/mail/MailgunDriver.py
@@ -0,0 +1,49 @@
import requests
from .Recipient import Recipient


class MailgunDriver:
def __init__(self, application):
self.application = application
self.options = {}
self.content_type = None

def set_options(self, options):
self.options = options
return self

def get_mime_message(self):
data = {
"from": self.options.get("from"),
"to": Recipient(self.options.get("to")).header(),
"subject": self.options.get("subject"),
"h:Reply-To": self.options.get("reply_to"),
"html": self.options.get("html_content"),
"text": self.options.get("text_content"),
}

if self.options.get("cc"):
data.update({"cc", self.options.get("cc")})
if self.options.get("bcc"):
data.update({"bcc", self.options.get("bcc")})

return data

def get_attachments(self):
files = []
for attachment in self.options.get("attachments", []):
files.append(("attachment", open(attachment.path, "rb")))

return files

def send(self):
domain = self.options["domain"]
secret = self.options["secret"]
attachments = self.get_attachments()

return requests.post(
f"https://api.mailgun.net/v3/{domain}/messages",
auth=("api", secret),
data=self.get_mime_message(),
files=attachments,
)
18 changes: 18 additions & 0 deletions src/masonite/drivers/mail/Recipient.py
@@ -0,0 +1,18 @@
class Recipient:
def __init__(self, recipient):
if isinstance(recipient, (list, tuple)):
recipient = ",".join(recipient)
self.recipient = recipient

def header(self):
headers = []
for address in self.recipient.split(","):

if "<" in address:
headers.append(address)
continue

if address.strip():
headers.append(f"<{address.strip()}>")

return ", ".join(headers)
73 changes: 73 additions & 0 deletions src/masonite/drivers/mail/SMTPDriver.py
@@ -0,0 +1,73 @@
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
from .Recipient import Recipient
import ssl


class SMTPDriver:
def __init__(self, application):
self.application = application
self.options = {}

def set_options(self, options):
self.options = options
return self

def get_mime_message(self):
message = MIMEMultipart("alternative")

message["Subject"] = self.options.get("subject")

message["From"] = Recipient(self.options.get("from")).header()
message["To"] = Recipient(self.options.get("to")).header()
if self.options.get("reply_to"):
message["Reply-To"] = Recipient(self.options.get("reply_to")).header()

if self.options.get("cc"):
message["Cc"] = Recipient(self.options.get("cc")).header()

if self.options.get("bcc"):
message["Bcc"] = Recipient(self.options.get("bcc")).header()

if self.options.get("html_content"):
message.attach(MIMEText(self.options.get("html_content"), "html"))

if self.options.get("text_content"):
message.attach(MIMEText(self.options.get("text_content"), "plain"))

for attachment in self.options.get("attachments", []):
with open(attachment.path, "rb") as fil:
part = MIMEApplication(fil.read(), Name=attachment.alias)

part["Content-Disposition"] = f"attachment; filename={attachment.alias}"
message.attach(part)

return message

def make_connection(self):
options = self.options
if options.get("ssl"):
smtp = smtplib.SMTP_SSL("{0}:{1}".format(options["host"], options["port"]))
else:
smtp = smtplib.SMTP("{0}:{1}".format(options["host"], int(options["port"])))

if options.get("tls"):
context = ssl.create_default_context()
context.check_hostname = False

# Check if correct response code for starttls is received from the server
if smtp.starttls(context=context)[0] != 220:
raise smtplib.SMTPNotSupportedError(
"Server is using untrusted protocol."
)

if options.get("username") and options.get("password"):
smtp.login(options.get("username"), options.get("password"))

return smtp

def send(self):
smtp = self.make_connection()
smtp.send_message(self.get_mime_message())
31 changes: 31 additions & 0 deletions src/masonite/drivers/mail/TerminalDriver.py
@@ -0,0 +1,31 @@
import requests
from .Recipient import Recipient


class TerminalDriver:
def __init__(self, application):
self.application = application
self.options = {}
self.content_type = None

def set_options(self, options):
self.options = options
return self

def send(self):
print("-------------------------------------")
print(f"To: {Recipient(self.options.get('to')).header()}")
print(f"From: {Recipient(self.options.get('from')).header()}")
print(f"Cc: {Recipient(self.options.get('cc')).header()}")
print(f"Bcc: {Recipient(self.options.get('bcc')).header()}")
print(f"Subject: {self.options.get('subject')}")
print("-------------------------------------")
print(f"{self.options.get('html_content')}")
if self.options.get("text_content"):
print("-------------------------------------")
print(f"Text Content: {self.options.get('text_content')}")
if self.options.get("attachments"):
print("-------------------------------------")
for index, attachment in enumerate(self.options.get("attachments")):
index += 1
print(f"Attachment {index}: {attachment.alias} from {attachment.path}")
3 changes: 3 additions & 0 deletions src/masonite/drivers/mail/__init__.py
@@ -0,0 +1,3 @@
from .SMTPDriver import SMTPDriver
from .MailgunDriver import MailgunDriver
from .TerminalDriver import TerminalDriver
1 change: 1 addition & 0 deletions src/masonite/environment/__init__.py
@@ -0,0 +1 @@
from .environment import LoadEnvironment, env
71 changes: 71 additions & 0 deletions src/masonite/environment/environment.py
@@ -0,0 +1,71 @@
"""Module for the LoadEnvironment class."""

import os
import sys
from pathlib import Path


class LoadEnvironment:
"""This class is used for loading the environment from .env and .env.* files."""

def __init__(self, environment=None, override=True, only=None):
"""LoadEnvironment constructor.

Keyword Arguments:
env {string} -- An additional environment file that you want to load. (default: {None})
override {bool} -- Whether or not the environment variables found should overwrite existing ones. (default: {False})
only {string} -- If this is set then it will only load that environment. (default: {None})
"""
from dotenv import load_dotenv

self.env = load_dotenv

if only:
self._load_environment(only, override=override)
return

env_path = str(Path(".") / ".env")
self.env(env_path, override=override)

if os.environ.get("APP_ENV"):
self._load_environment(os.environ.get("APP_ENV"), override=override)
if environment:
self._load_environment(environment, override=override)

if "pytest" in sys.modules:
self._load_environment("testing", override=override)

def _load_environment(self, environment, override=False):
"""Load the environment depending on the env file.

Arguments:
environment {string} -- Name of the environment file to load from

Keyword Arguments:
override {bool} -- Whether the environment file should overwrite existing environment keys. (default: {False})
"""
env_path = str(Path(".") / ".env.{}".format(environment))
self.env(dotenv_path=env_path, override=override)


def env(value, default="", cast=True):
env_var = os.getenv(value, default)

if not cast:
return env_var

if env_var == "":
env_var = default

if isinstance(env_var, bool):
return env_var
elif env_var is None:
return None
elif env_var.isnumeric():
return int(env_var)
elif env_var in ("false", "False"):
return False
elif env_var in ("true", "True"):
return True
else:
return env_var
9 changes: 9 additions & 0 deletions src/masonite/foundation/Kernel.py
Expand Up @@ -4,19 +4,28 @@
from ..storage import StorageCapsule
from ..auth import Sign
import os
from ..environment import LoadEnvironment


class Kernel:
def __init__(self, app):
self.application = app

def register(self):
self.load_environment()
self.set_framework_options()
self.register_framework()
self.register_commands()
self.register_controllers()
self.register_templates()
self.register_storage()

def load_environment(self):
LoadEnvironment()

def set_framework_options(self):
self.application.bind("config.mail", "tests.integrations.config.mail")

def register_controllers(self):
self.application.bind("controller.location", "tests.integrations.controllers")

Expand Down
32 changes: 32 additions & 0 deletions src/masonite/mail/Mail.py
@@ -0,0 +1,32 @@
class Mail:
def __init__(self, application, driver_config=None):
self.application = application
self.drivers = {}
self.driver_config = driver_config or {}
self.options = {}

def add_driver(self, name, driver):
self.drivers.update({name: driver})

def set_configuration(self, config):
self.driver_config = config
return self

def get_driver(self, name=None):
if name is None:
return self.drivers[self.driver_config.get("default")]
return self.drivers[name]

def get_config_options(self, driver=None):
if driver is None:
return self.driver_config[self.driver_config.get("default")]

return self.driver_config.get(driver, {})

def mailable(self, mailable):
self.options = mailable.set_application(self.application).build().get_options()
return self

def send(self, driver=None):
self.options.update(self.get_config_options(driver))
return self.get_driver(driver).set_options(self.options).send()