Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Masonite is the rapid application Python development framework that strives for: beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.
Easily send emails with the Mail Provider and the SMTP and Mailgun drivers.
Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers.
IOC container and auto resolving dependency injection.
Service Providers to easily add functionality to the framework.
Extremely simple static files configured and ready to go.
Active Record style ORM called Orator.
An extremely useful command line tool called craft commands.
Extremely extendable.
These, among many other features, are all shipped out of the box and ready to go. Use what you need when you need it.
In order to use Masonite, you’ll need:
Python 3.4+
Pip
If you are running on a Linux flavor, you’ll need the Python dev package and OpenSSL. You can download this package by running:
Or you may need to specify your python3.x-dev
version:
Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft
command line tool.
We can download our craft
command line tool by just running:
You may have to use sudo if you are on a UNIX machine
All pip commands are assuming they are connected to a Python 3.4+ installation. If you are having a hard time installation then try running all pip commands in this documentation with pip3 commands.
Great! We are now ready to create our first project. We should have the new craft
command. We can check this by running:
This should show a list of command options. If it doesn't then try closing your terminal and reopening it or running it with sudo
if you are on a UNIX machine. We are currently only interested in the craft new
command. To create a new project just run:
This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt
file.
You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:
or if you are on Windows:
Now lets install our dependencies. We can do this simply by using a craft
command:
This installs all the required dependencies of Masonite, creates a .env
file for us, generates a new secret key, and puts that secret key in our .env
file. After it’s done we can just run the server by using another craft
command:
Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications. You can learn more about craft by reading The Craft Command documentation.
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners or contributors of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
The framework has three main parts.
This MasoniteFramework/masonite
repository is the main repository that will install when creating new projects using the craft new
command. Not much development will be done in this repository and won't be changed unless new releases of Masonite require changes in the default installation project.
The MasoniteFramework/core
repository where the main masonite
pip package lives. This is where the from masonite ...
module lives.
The MasoniteFramework/craft
repository where the craft
command lives
You can read about how the framework flows, works and architectural concepts here
This repo is simple and will be able to be installed following the installation instruction in the README.
Fork the MasoniteFramework/masonite
repo.
Clone that repo into your computer:
git clone http://github.com/your-username/masonite.git
Checkout the current release branch (example: develop
)
You should now be on a develop
local branch.
Run git pull origin develop
to get the current release version.
From there simply create your feature branches (change-default-orm
) and Make your desired changes.
Push to your origin repository:
git push origin change-default-orm
Open a pull request and follow the PR process below
The trick to this is that we need it to be pip installed and then quickly editable until we like it, and then pushed back to the repo for a PR. Do this only if you want to make changes to the core Masonite package
To do this just:
Fork the MasoniteFramework/core
repo,
Clone that repo into your computer:
git clone http://github.com/your-username/core.git
Activate your masonite virtual environment (optional)
Go to where you installed masonite and activate the environment
While inside the virtual environment, cd into the directory you installed core.
Run pip install .
from inside the masonite-core directory. This will install masonite as a pip package.
Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.
craft
commands)Craft commands make up a large part of the workflow for Masonite. Follow these instructions to get the masonite-cli package on your computer and editable.
Fork the MasoniteFramework/craft
repo,
Clone that repo into your computer:
git clone http://github.com/your-username/craft.git
Activate your masonite virtual environment (optional)
Go to where you installed masonite and activate the environment
While inside the virtual environment, cd into the directory you installed cli
Run pip install --editable .
from inside the masonite-cli directory. This will install craft (which contains the craft commands) as a pip package but also keep a reference to the folder so you can make changes freely to craft commands while not having to worry about continuously reinstalling it.
Any changes you make to this package just push it to your feature branch on your fork and follow the PR process below.
Comments are a vital part of any repository and should be used where needed. It is important not to overcomment something. If you find you need to constantly add comments, you're code may be too complex. Code should be self documenting (with clearly defined variable and method names)
There are 3 main type of comments you should use when developing for Masonite:
Module Docstrings
All modules should have a docstring at the top of every module file and should look something like:
Method and Function Docstrings
All methods and functions should also contain a docstring with a brief description of what the module does
For example:
Code Comments
If you're code MUST be complex enough that future developers will not understand it, add a #
comment above it
For normal code this will look something like:
Flagpole Comments
Flag pole comments are a fantastic way to give developers an inside to what is really happening and for now should only be reserved for configuration files. A flag pole comment gets its name from how the comment looks
It's important to note that there should have exactly 75 -
above and below the header and have a trailing |
at the bottom of the comment.
You should open an issue before making any pull requests. Not all features will be added to the framework and some may be better off as a third party package. It wouldn't be good if you worked on a feature for several days and the pull request gets rejected for reasons that could have been discussed in an issue.
Ensure any changes are well commented and any configuration files that are added have a flagpole comment on the variables it's setting.
Update the README.md and MasoniteFramework/docs
repo with details of changes to the interface, this includes new environment variables, new file locations, container parameters etc.
You must add unit testing for any changes made. Of the three repositories listed above, only the craft
and core
repos require unit testing.
The PR must pass the Travis CI build. The Pull Request can be merged in once you have a successful review from two other collaborators, or the feature maintainer for your specific feature improvement or the repo owner.
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment include:
Using welcoming and inclusive language
Being respectful of differing viewpoints and experiences
Gracefully accepting constructive criticism
Focusing on what is best for the community
Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
The use of sexualized language or imagery and unwelcome sexual attention or advances
Trolling, insulting/derogatory comments, and personal or political attacks
Public or private harassment
Publishing others' private information, such as a physical or electronic
address, without explicit permission
Other conduct which could reasonably be considered inappropriate in a
professional setting
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at idmann509@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4
There are no known Masonite specific issues known and if there are then there should be an issue open for them on GitHub. With that being said, some users may experience some difficulties with installing Masonite simply because their computer environment is not the norm, they have never setup Python and may have configured it incorrectly or they have not used Python in a while and have an old version.
Before you get started reading through this FAQ make sure you have:
Python 3.4+
Pip3
Ensure you are installing masonite-cli with pip3
and not pip
.
You are likely running this command on a UNIX based machine like Mac or Linux. In that case you should either run it again with a sudo command or a user command flag:
or
You may get a strange error like:
The simple fix may just be to run:
If that does not fix the issue then continue reading.
If the above fix did not work then this likely means you installed masonite-cli using the Python 2.7 pip command. Out of the box, all Mac and Linux based machines have Python 2.7. If you run:
you should get a return value of:
But if you run:
you should get a return value of:
Now pip commands are similar:
Notice here we are using 2 different Python installations.
So if you are getting this error you should uninstall masonite-cli from pip and reinstall it using pip3:
You may have to run sudo to remove it and you may need to close your terminal to get it work
If you installed everything successfully and running:
Shows an error that it can't be found then try closing your terminal and opening it again. This should refresh any commands that were recently installed
If you still have errors and on a UNIX based machine try running:
You likely ran:
and hit this weird snag that throws this ambiguous error. You might think this is because of a Python version issue but craft is designed to work on Python 2.7 and 3.4+ (although 2.7 and not thoroughly tested) and you're years of Python experience would make you right but this is special. If you are getting this error then that means you are likely on a UNIX machine, Mac right?
The problem is that your machine does not have sufficient permissions to access these external calls from the command line because your machine does not have permission to do so. You will have to give you machine the command to do so by running:
or whatever your Python 3 version is in the middle. Now try running:
and it should work great!
There are plenty of ways to contribute to open source. Many of which don't even rely on writing code. A great open source project should have excellent documentation and have as little bugs as possible. Below I will explain how to contribute to this project in different ways.
This is not an exhaustive list and not the only ways to contribute but they are the most common. If you know of other ways to contribute then please let us know.
Of course the project requires contributions to the main development aspects but it's not the only way. But if you would like to contribute to development then a great way to get started is to simply read through this documentation. Get acquainted with how the framework works, how Controllers and Routing work and read the Architectural Concepts documentation starting with the Request Lifecycle, then the Service Providers and finally the Service Container.
It would also be good to read about the Release Cycle to get familiar with how Masonite does releases (RomVer over SemVer).
Feature Maintainers are people who are in charge of specific features (such as Caching or Creating Packages). These developers will be in charge of reviewing PR's and merging them into the development branch and also have direct contact with the repository owner to discuss.
Feature maintainers must already have significant contributions to development of the repository they are trying to be a Feature Maintainer for. Although they do not have to be contributors to the actual feature they plan to maintain.
If you don't want to touch the code and instead want to just look at it and figure it out, contribute some comments! Comments are an excellent way for future developers to read and understand the framework. Masonite strives on being extremely commented. Although most of the code itself does not need to be commented, some of the classes, modules, methods and functions do (although a lot of them already are).
Comments don't affect the working code so if you want to get used to contributing to open source or you just don't quite understand what a class method is doing or you are afraid of contributing and breaking the project (there are tests) then contributing comments is right for you!
The Masonite pip packages require testing (The main repository does not). If you want to search through all the tests in the tests directories of those repositories and write additional tests and use cases then that will be great! There are already over 100 tests but you can always write more. With more testing comes more stability. Especially as people start to contribute to the project. Check the tests that are already there and write any use cases that are missing. These tests can be things such as special characters in a url or other oddities that may not have been thought of when using TDD for that feature.
Once familiar with the project (by either contributing or by building application using the framework) it would be excellent if you could write or record tutorials and put them on Medium or YouTube. In order for the framework to be successful, it needs to have a plethora of documentation even outside of this documentation. It needs to have notoriety and if people are seeing the framework pop up in their favorite locations they will be more inclined to use the framework and contribute to it as well.
Plus there will be fantastic tutorials out there for beginners to find and watch and you could also build a following off the back of Masonite.
This documentation is fantastic but there are spots where it could be improved. Maybe we haven't explained something fully or something just doesn't make sense to you. Masonite uses Gitbook.com to host it's documentation and with that you are able to comment directly on the documentation which will start a discussion between you and the documentation collaborators. So if you want to cycle through the documentation page by page and get acquainted with the framework but at the same time contribute to the documentation, this is perfect for you.
If you just don't want to contribute code to the main project you may instead simply report bugs or improvements. You can go ahead and build any of your applications as usual and report any bugs you encounter to the GitHub.com issues page.
Look at the issues page on GitHub.com for any issues, bugs or enhancements that you are willing to fix. If you don't know how to work on them, just comment on the issue and Joseph Mancuso or other core contributors will be more than happy explaining step by step on how you can go about fixing or developing that issue.
If you have a large following on any social media or no following at all, you can contribute by trying to build up a following around Masonite. Any open source project requires an amazing community around the framework. You can either build up a community personally and be the leader of that community or you can simply send them to Masonite's GitHub repository where we can build up a community around there.
Another idea is to use Masonite to build applications such as a screencast website like LaraCasts.com or an official Masonite website or even a social network around Masonite. Every great framework needs it's "ecosystem" so you may be apart of that buy building these applications with the Masonite branding and logos. Although copying the branding requires an OK from Joseph Mancuso, as long as the website was built with Masonite and looks clean it shouldn't be a problem at all.
Questions will come in eventually either through the GitHub issues or through websites like StackOverflow. You could make it a priority to be the first to answer these peoples questions or if you don't know the answer you can redirect one of the core maintainers or contributors to the question so we can answer it further.
Most pull requests will sit inside GitHub for a few days while it gets quality tested. The main develop
branch pull requests could sit there for as long as 6 months and will only be merged in on releases. With that being said, you can look at the file changes of these pull requests and ensure they meet the community guidelines, the API is similar to other aspects of the project and that they are being respectful and following pull requests rules in accordance with the Contributing Guide documentation.
Every now and then will be a requires discussion
label on an issue or pull request. If you see this label then be sure to add your thoughts on an issue. All issues are open for discussion and Masonite strives off of developer input so feel free to enter a discussion.
Masonite 1.3 comes with a plethora of improvements over previous versioning. This version brings new features such as Queue and Mail drivers as well as various bug fixes.
Previously when a you tried to redirect using the Request.redirect()
method, Masonite would sometimes send the browser to an infinite redirection. This was because masonite was not resetting the redirection attributes of the Request
class.
Previously the content length in the request header was not being set correctly which led to the gunicorn server showing a warning that the content length did not match the content of the output.
Previously the Request class simply got the input data on both POST
and GET
requests by converting the wsgi.input
WSGI parameter into a string and parsing. All POST input data is now retrieved using FieldStorage
which adds support for also getting files from multipart/formdata
requests.
You may now simply upload images to both disk and Amazon S3 storage right out of the box. With the new UploadProvider
service provider you can simply do something like:
As well as support for Amazon S3 by setting the DRIVER
to s3
.
These helper functions are added functions to the builtin Python functions which can be used by simply calling them as usual:
Notice how we never imported anything from the module or Service Container. See the Helper Functions documentation for a more exhaustive list
Very often you will want to have a single variable accessible in all of your views, such as the Request
object or other class. We can use the new View
class for this and put it in it's own service provider:
You can now specify anything that is in the container in your middleware constructor and it will be resolved automatically from the container
Specify the subdomain you want to target with your route. It's common to want to have separate routes for your public site and multi-tenant sites. This will now look something like:
Which will target test.example.com/dashboard
and not example.com/dashboard
. Read more about subdomains in the Routing documentation.
By default, masonite will look for routes in the app/http/controllers
namespace but you can change this for individual routes:
This will look for the controller in the thirdparty.routes
module.
Masonite now ships with a QueueManager
class which can be used to build queue drivers. Masonite ships with an async
driver which sends jobs to a background thread. These queues can process Jobs which ca be created with the new craft job
command. See the Queues and Jobs documentation for more information.
Masonite 1.4 brings several new features to Masonite. These features include caching, template caching, websocket support with Masonite calls Broadcasting and much more testing to make Masonite as stable as possible. If you would like to contribute to Masonite, please read the Contributing Guide and the How To Contribute documentation.
If you are upgrading from Masonite 1.3 then please read the Masonite 1.3 to 1.4 documentation.
We recognize that in order for frameworks to keep up with modern web application, they require real time broadcasting. Masonite 1.4 brings basic broadcasting of events to masonite and comes with two drivers out of the box: pusher
and ably
. If you'd like to create more drivers then you can do so easily by reading the About Drivers documentation. If you do create a driver, please consider making it available on PyPi so others can install it into their projects or open an issue on GitHub and make to add it to the built in drivers.
Masonite now has a built in caching class that you can use to either cache forever or cache for a specific amount of time.
Templates may have a lot of logic that are only updated every few minutes or even every few months. With template caching you can now cache your templates anywhere from every few seconds to every few years. This is an extremely powerful caching technique that will allow your servers to run less intensively and easily increase the performance of your application.
If a page gets hit 100 times every second then you can cache for 5, 10 or 15 seconds at a time to lessen the load on your server.
This feature only activates if you have the CacheProvider
loaded in your PROVIDERS
list. If you try to use these features without that provider then you will be hit with a RequiredContainerBindingNotFound
exception letting you know you are missing a required binding from a service provider. This provider comes out of the box in Masonite 1.4.
We have also updated the code to closely conform to PEP 8 standards.
Because of the caching features, we have added a bootstrap/cache
folder where all caching will be put but you can change this in the new config/cache.py
file.
Masonite 1.4 brings the idea of contracts which are very similar to interfaces in other languages. Contracts ensure that a driver or manager inherits has the same functionality across all classes of the same type.
Cross-Site Request Forgery is a crucial security milestone to hit and Masonite 1.4 brings that ability. With a new Service Provider and middleware, we can now add a simple {{ csrf_field|safe }}
to our forms and ensure we are protected from CSRF attacks.
Managers were very redundant before this release so we made it much easier to create managers with 2 simple class attributes instead of the redundant method. Managers are used to manage features and drivers to Masonite.
Now the constructor of all middleware is resolved by the container. This means you may use the IOC dependency injection techniques like controller methods and drivers.
There were two unused imports in the models that Masonite created. These have been removed completely.
The Masonite framework itself follows the RomVer versioning schema which is PARADIGM.MAJOR.MINOR although all Masonite packages follow the SemVer versioning schema which is MAJOR.MINOR.FEATURE/BUGFIX.
This means that a framework version of 1.3.20 will have breaking changes with version 1.4.0.
The Masonite main repository (the MiraFramework/masonite
repository) contains only the basic file structure of the application. All of the core framework functionality is inside the MasoniteFramework/core
repository which can be updated as much as every day or once per month and therefore will follow normal SemVer. Because MiraFramework/masonite
does not require major updates, we can follow RomVer nicely and keep the versioning number artificially lower. Any major updates to this repsository will likely just be file structure changes which should rarely happen unless there are major architectural changes.
Masonite is currently on a 1 month major release cycle. This means that once every month will be a new 1.x release. This 1 month release cycle will continue until Masonite has reached a release that is stable enough to move far into the future with that releases architecture.
Once this stable release has been achieved, Masonite will move to a 6 month major release cycle with minor releases as often as every few days to as much as every few months.
Releases are planned to be upgradable from the previous release in 10 minutes or less. If they break that requirement then they should be considered for the next Paradigm release (the x.0.0
release)
Masonite is made up of three different repositories. There is
The main repository where development is done on the repo that installs on developer's systems.
The core repository which is where the main Masonite pip package is located.
The craft repository where the craft command tool is located.
Major 1 month releases will be released on or after the release date when all repositories are able to be released at the same time, as well as passing all tests.
Whenever the MasoniteFramework/craft
and MasoniteFramework/core
repositories are released on Github, Travis CI will run tests and automatically deploy to PyPi. These major version numbers should correspond to the version of Masonite they support. For example, if the MasoniteFramework/masonite
releases to version 1.4, MasoniteFramework/core
should bump up to 1.4.x regardless of changes.
This is so developers and maintainers will be able to test the new pre release with their applications. Once all QA tests have passed, it will be marked as a normal release and therefore all new applications created from there on out will be the new release.
Developers will still have the option of doing something like: craft new project_name --version 1.5
and installing that version of Masonite.
Once all three repositories are ready for release, they will all be released on GitHub under the respective new version numbers.
Masonite 1.6 brings mostly minor changes to the surface layer of Masonite. This release brings a lot of improvements to the engine of Masonite and sets up the framework for the release of 2.0.
Previously, all cookies were set with an HttpOnly flag when setting cookies. This change came after reading several articles about how cookies can be read from Javascript libraries, which is fine, unless those Javascript libraries have been compromised which could lead to a malicious hacker sending your domain name and session cookies to a third party. There is no the ability to turn the HttpOnly flag off when setting cookies by creating cookies like:
Because craft is it's own tool essentially and it needs to work across Masonite versions, all commands have been moved into the Masonite repository itself. Now each version of Masonite maintains it's own commands. The new craft version is 2.0
Before, you had to use the Manager class associated with a driver to switch a driver. For example:
Now you can switch drivers from the driver itself:
This version has been fine tuned for adding packages to Masonite. This version will come along with a new Masonite Billing package. The development of Masonite Billing has discovered some rough spots in package integrations. One of these rough spots were adding controllers that were not in the project. For example, Masonite Billing allows adding a controller that handles incoming Stripe webhooks. Although this was possible before this release, Masonite 1.6 has added a new syntax:
Notice the new forward slash in the beginning where the string controller goes.
Previously, controllers were created as they were specified. For example:
created a DashboardController. Now the "Controller" part of the controller is appended by default for you. Now we can just specify:
to create our DashboardController. You may was to actually just create a controller called Dashboard. We can do this by specifying a flag:
short for "exact"
It's also really good practice to create 1 controller per "action type." For example we might have a BlogController
and a PostController
. It's easy to not be sure what action should be in what controllers or what to name your actions. Now you can create a "Resource Controller" which will give you a list of actions such as show, store
, create
, update
etc etc. If what you want to do does not fit with an action you have then you may want to consider making another controller (such as an AuthorController
)
You can now create these Resource Controllers like:
Just like the global controllers, some packages may require you to add a view that is located in their package (like the new exception debug view in 1.6) so you may now add views in different namespaces:
This will get a template that is located in the masonite package itself.
You can now group routes based on a specific string prefix. This will now look like:
which will compile down into /dashboard/user
and /dashboard/user/1
The container was one of the first features coded in the 1.x release line. For Masonite 1.6 we have revisited how the container resolves objects. Before this release you had to put all annotation classes in the back of the parameter list:
If we put the annotation in the beginning it would have thrown an error because of how the container resolved.
Now we can put them in any order and the container will grab each one and resolve it.
This will now work when previously it did not.
The container will now resolve instances of classes as well. It's a common paradigm to "code to an interface and not an implementation." Because of this paradigm, Masonite comes with contracts that act as interfaces but in addition to this, we can also resolve instances of classes.
For example, all Upload drivers inherit the UploadContract contract so we can simply resolve the UploadContract which will return an Upload Driver:
Notice here that we annotated an UploadContract but got back the actual upload driver.
You can now search the container and "collect" objects from it by key using the new collect method:
which will find all keys in the container such as SentryExceptionHook and SentryWebHook and make a new dictionary out of them.
A complaint a few developers pointed out was that Masonite has too many dependencies. Masonite added Pusher, Ably and Boto3 packages by default which added a bit of overhead, especially if developers have no intentions on real time event broadcasting (which most applications probably won't). These dependencies have now been removed and will throw an exception if they are used without the required dependencies.
Masonite 1.6 + will slowly be rolling out various framework hooks. These hooks will allow developers and third party packages to integrate into various events that are fired throughout the framework. Currently there is the abilitity to tie into the exception hook which will call any objects loaded into the container whenever an exception is hit. This is great if you want to add things like Sentry into your project. Other hooks will be implemented such as View, Mail and Upload Hooks.
The main repository which is MasoniteFramework/masonite
does not have a corresponding PyPi package and is only for installing new Masonite projects. See the craft new
command under documentation. The craft new
command will download a zip of the latest release of Masonite, unzip it and rename the folder. Once the next release for this repository is ready, it will be released but marked as a Pre-release
and therefore will not be installable by the default craft new
command.
Controllers are a vital part of Masonite and is mainly what differs it from other Python frameworks that implement the MVC structure differently. Controllers are simply classes with methods. These methods take a self
parameter which is the normal self that Python class methods require. Controller methods can be looked at as function based views if you are coming from Django as they are simply methods inside a class and work in similar ways.
Controllers have an added benefit over straight function based views as the developer has access to to a full class they can manipulate however they want. In other words, controller methods may utilize class attributes or private methods to break up logic. They provide a lot of flexibility.
Its very easy to create a controller with Masonite with the help of our craft
command tool. We can simply create a new file inside app/http/controllers
, name the class the same name as the file and then create a class with methods. We can also use the craft controller
command to do all of that for us which is:
When we run this command we now have a new class in app/http/controllers/DashboardController.py
called DashboardController
. By convention, Masonite expects that all controllers have their own file since it’s an extremely easy way to keep track of all your classes since the class name is the same name as the file but you can obviously name this class wherever you like.
Notice that we passed in Dashboard
but created a DashboardController
. Masonite will always assume you want to append Controller
to the end.
Remember that Masonite will automatically append Controller to the end of all controllers. If you want to create the exact name of the controller then you can pass a -e
or --exact
flag.
or
This will create a Dashboard
controller located in app/http/controllers/Dashboard.py
Resource controllers are controllers that have basic CRUD / resource style methods to them such as create, update, show, store etc. We can create a resource controller by running:
or
this will create a controller that looks like:
Controller methods are very similar to function based views in a Django application except this is just a normal class method. Our controller methods at a minimum should look like:
All controller methods must have the self parameter. The self
parameter is the normal python self
object which is just an instance of the current class as usual. Nothing special here.
All controller methods are resolved by the container so you may also retrieve additional objects from the container by specifying them as a parameter:
This might look magical to you so be sure the read about the IOC container in the Service Container documentation.
It’s important to note that unlike other frameworks, we do not have to specify our route parameters as parameters in our controller method. We can retrieve the parameters using the Request.param('key')
class method.
Read about how to create and use views by reading the Views documentation
Views contain all the HTML that you’re application will use to render to the user. Unlike Django, views in Masonite are your HTML templates. All views are located inside resources/templates
directory.
All views are rendered with Jinja2 so we can use all the Jinja2 code you are used to. An example view looks like:
Since all views are located in resources/templates
, we can use simply create all of our views manually here or use our craft
command tool. To make a view just run:
This will create a template under resources/templates/hello.html
.
There are several ways we can call views in our controllers. The first recommended way is using the view()
function. Masonite ships with a HelpersProvider
Service Provider. This provider will add several new built in functions to your project. These helper functions can be used as shorthand for several commonly used classes such as the View
and Request
class. See the Helper Functions documentation for more information.
One of the helper functions is the view()
function which is accessible like any other built in Python function.
We can call views in our controllers like so:
This will return the view located at resources/templates/dashboard.html
. We can also specify a deeper folder structure like so:
This will look for the view at resources/templates/profiles/dashboard.html
The View
class is loaded into the container so we can retrieve it in our controller methods like so:
This is exactly the same as using the helper function above. So if you choose to code more explicitly, the option is there for you.
If this looks weird to you or you are not sure how the container integrates with Masonite, make sure you read the Service Container documentation
Some views may not reside in the resources/templates
directory and may even reside in third party packages such as a dashboard package. We can locate these views by passing a /
in front of our view.
For example as a use case we might pip install a package:
and then be directed or required to return one of their views:
This will look inside the dashboard.views
package for a dashboard.html
file and return that. You can obviously pass in data as usual.
It's important to note that if you are building a third party package that integrates with Masonite that you place any .html
files inside a Python package instead of directly inside a module. For example, you should place .html files inside a file structure that looks like:
and not inside the package directory. This is a Jinja limitation that says that all templates should be located in packages.
Accessing a global view such as:
will perform a relative import for your Masonite project. For example it will catch:
So if you are making a package for Masonite then keep this in mind in where you should put your templates
When you extend a view, you are isolated to the directory you are in when you want to extend templates and you're view namespace loses it's globalization and all extending should be done relative to the current template. For example, if you have a package with a directory structure like:
And a controller like:
You will have to extend your template like so:
and not extend your template with another global view:
A lot of the time we’ll need to pass in data to our views. This data is passed in with a dictionary that contains a key which is the variable with the corresponding value. We can pass data to the function like so:
Remember that by passing in parameters like Request
to the controller method, we can retrieve objects from the IOC container. Read more about the IOC container in the Service Container documentation.
This will send a variable named id
to the view which can then be rendered like:
Masonite 1.5 is focused on a few bug fixes and changes to several core classes in order to expand on the support of third party package integration.
Masonite 1.5 is releasing also with a new official package for adding RESTful API's to your project. This package used API Resources which are classes that can add a plethora of capabilities to your API endpoints. You can read more about it at the Masonite Entry documentation.
Masonite 1.5 also adds additional support HTTP methods like PUT
, PATCH
and DELETE
. More information about these new routes can be found under the Routing documentation
Nearly all dependencies have been moved to the core Masonite package. The only thing inside the project that is installed using craft new
is the WSGI server (which is waitress by default) and Masonite itself. This will improve the ability to change and update dependencies.
HTML forms only support GET and POST so there is no the ability to add any other HTTP methods like PUT
and PATCH
which will change the actual request method on submission. You can read more about this in the Requests documentation.
You can now use functions for routes instead of the classes. These new functions are just wrappers around the classes itself. Read more about this under the Routing documentation.
In Masonite 1.4 and below was the Api()
route which added some very basic API endpoints. All references to API's have been removed from core and added to the new Masonite Entry official package.
If you would like to use API's you will have to use the Masonite Entry package instead.
You can now get and set any header information using the new Request.header
method. This also allows third party packages to manipulate header information. Read more about this in the Requests documentation.
You can now delete cookies using the delete_cookie
method as well as set expiration dates for them. See the Requests documentation for more information.
The Cache driver now has an update method which can update a cache value by key. This is useful if you want to change a key value or increment it. Storing a cache file also now auto creates that directory. Read more about this in the Caching documentation.
Craft commands have been built from the ground up with the cleo package. It's an excellent package that is built around the extendability of commands by using primarily classes (instead of decorator functions). Read more under The Craft Command documentation
It is now possible to add craft commands to craft. You can read more about how under The Craft Command documentation
You can now add more migration directories by adding it to the container with a key ending in MigrationDirectory
. This will add the directory to the list of directory that run when migrate commands are ran. You can read more about this in the Creating Packages documentation.
You can now add data to sessions using the new Sessions feature which comes with a memory and cookie driver for storing data.
In previous versions, Masonite has not been able to fetch the site packages directory if you were in a virtual environment because virtual environment directories are dynamically named depending on who created it. We have found a way to detect the virtual environment and the site packages directory so now there is no need to add the site packages directory manually to the packages configuration file
The craft command tool is a powerful developer tool that lets you quickly scaffold your project with models, controllers, views, commands, providers, etc. which will condense nearly everything down to it’s simplest form via the craft namespace. No more redundancy in your development time creating boilerplate code. Masonite condenses all common development tasks into a single namespace.
For example, In Django you may need to do something like:
The craft tool condenses all commonly used commands into its own namespace
All scaffolding of Masonite can be done manually (manually creating a controller and importing the view
function for example) but the craft command tool is used for speeding up development and cutting down on mundane development time.
When craft is used outside of masonite directory, it will only show a few commands such as the new
and install
commands. Other commands such as commands for creating controllers or models are loaded in from the Masonite project itself.
Many commands are loaded into the framework itself and fetched when craft is ran in a Masonite project directory. This allows version specific Masonite commands to be efficiently handled on each subsequent version as well as third party commands to be loaded in which expands craft itself.
The possible commands for craft include:
To create an authentication system with a login, register and a dashboard system, just run:
This command will create several new templates, controllers and routes so you don’t need to create an authentication system from scratch, although you can. If you need a custom authentication system, this command will scaffold the project for you so you can go into these new controllers and change them how you see fit.
These new controllers are not apart of the framework itself but now apart of your project. Do not look at editing these controllers as editing the framework source code.
If you wish to scaffold a controller, just run:
This command will create a new controller under app/http/controllers/DashboardController.py
. By convention, all controllers should have an appended “Controller” and therefore Masonite will append "Controller" to the controller created.
You can create a controller without appending "Controller" to the end by running:
This will create a app/http/controllers/Dashboard.py file with a Dashboard controller. Notice that "Controller" is not appended.
-e
is short for --exact
. Either flag will work.
You may also create resource controllers which include standard resource actions such as show, create, update, etc:
-r
is short for --resource
. Either flag will work.
You can also obviously combine them:
If you’d like to start a new project, you can run:
This will download a zip file of the MasoniteFramework/masonite
repository and unzip it into your current working directory. This command will default to the latest release of the repo.
You may also specify some options. The --version
option will create a new project depending on the releases from the MasoniteFramework/masonite
repository.
Or you can specify the branch you would like to create a new project with:
After you have created a new project, you will have a requirements.txt
file with all of the projects dependencies. In addition to this file, you will also have a .env-example
file which contains a boiler plate of a .env
file. In order to install the dependencies, as well as copy the example environment file to a .env
file, just run:
The craft install
command will also run craft key --store
as well which generates a secret key and places it in the .env
file.
All frameworks have a way to create migrations in order to manipulate database tables. Masonite uses a little bit of a different approach to migrations than other Python frameworks and makes the developer edit the migration file. This is the command to make a migration for an existing table:
If you are creating a migration for a table that does not exist yet which the migration will create it, you can pass the --create
flag like so:
These two flags will create slightly different types of migrations.
After your migrations have been created, edited, and are ready for migrating, we can now migrate them into the database. To migrate all of your unmigrated migrations, just run:
You can also refresh and rollback all of your migrations and remigrate them.
This will essentially rebuild your entire database.
You can also rollback all migrations without remigrating
Lastly, you can rollback just the last set of migrations you tried migrating
If you'd like to create a model, you can run:
This will scaffold a model under app/ModelName
and import everything needed.
If you need to create a model in a specific folder starting from the app
folder, then just run:
This will create a model in app/Models/ModelName.py.
Service Providers are a really powerful feature of Masonite. If you'd like to create your own service provider, just run:
This will create a file at app/providers/DashboardProvider.py
Views are simply html files located in resources/templates
and can be created easily from running the command:
This command will create a template at resources/templates/blog.html
You can also create a view deeper inside the resources/templates
directory.
This will create a view under resources/templates/auth/home.html
but keep in mind that it will not create the directory for you. If the auth
directory does not exist, this command will fail.
Jobs are designed to be loaded into queues. We can take time consuming tasks and throw them inside of a Job. We can then use this Job to push to a queue to speed up the performance of our application and prevent bottlenecks and slowdowns.
You can scaffold out basic command boilerplate:
This will create a app/commands/HelloCommand.py
file with the HelloCommand
class.
You can run the WSGI server by simply running:
To generate a secret key
, we can run:
This will generate a 32 bit string which you can paste into your .env
file under the KEY
setting.
You may also pass the --store
flag which will automatically add the key to your .env
file:
This command is ran whenever you run craft install
Great! You are now a master at the craft command line tool.
Read more about Service Providers under the documentation.
Jobs will be put inside the app/jobs
directory. See the documentation for more information.
You may create a PyPi package with an added integrations.py
file which is specific to Masonite. You can learn more about packages by reading the documentation. To create a package boilerplate, just run:
Masonite comes with a way to encrypt data and by default, encrypts all cookies set by the framework. Masonite uses a key
to encrypt and decrypt data. Read the documentation for more information on encryption.
It's extremely simple to add commands to Masonite via the craft command tool and Service Providers. If you have been using Masonite for any amount of time you will learn that commands are a huge part of developing web applications with Masonite. We have made it extremely easy to create these commands and add them to craft to build really fast personal commands that you might use often.
Masonite uses the Cleo package for creating and consuming commands so for more extensive documentation on how to utilize commands themselves, how to get arguments and options, and how to print colorful text to the command line.
Read more about Cleo by visiting the Cleo Documentation.
You can create commands by using craft itself:
This will create a app/commands/HelloCommand.py
file with boiler plate code that looks like this:
Let's create a simple hello name application which prints "hello your-name" to the console.
Where it says command:name
inside the docstring we can put hello
and inside the argument we can put name
like so:
Inside the handle
method we can get the argument passed by specifying self.argument('name')
. Simply put:
That's it! Now we just have to add it to our craft command.
We can add commands to craft by creating a Service Provider and registering our command into the container. Craft will automatically run all the register methods on all containers and retrieve all the commands.
Let's create a Service Provider:
This will create a provider in app/providers/HelloProvider.py
that looks like:
Let's import our command and register it into the container. Also because we are only registering things into the container, we can set wsgi = False
so it is not ran on every request and only before the server starts:
Make sure you instantiate the command. Also the command name needs to end in "Command". So binding HelloCommand
will work but binding Hello
will not. Craft will only pick up commands that end in Command
. This is also case sensitive so make sure Command
is capitalized.
Like normal, we need to add our Service Provider to the PROVIDERS
list inside our config/application.py
file:
That's it! Now if we run:
We will see our new hello
command:
and if we run:
We will see an output of:
Masonite works on getting rid of all those mundane tasks that developers either dread writing or dread writing over and over again. Because of this, Masonite has several helper functions that allows you to quickly write the code you want to write without worrying about imports or retrieving things from the Service Container. Many things inside the Service Container are simply retrieved using several functions that Masonite sets as builtin functions.
These functions do not require any imports and are simply just available which is similiar to the print()
function. These functions are all set inside the HelpersProvider
Service Provider.
It may make more sense if we take a peak at this Service Provider:
Notice how we simply just add builtin functions via this provider.
The Request class has a simple request()
helper function.
is exactly the same as:
Notice we didn't import anything at the top of the file, nor did we inject anything from the Service Container.
The view()
function is just a shortcut to the View
class.
is exactly the same as:
The auth()
function is a shortcut around getting the current user. We can retrieve the user like so:
is exactly the same as:
This will return None
if there is no user so in a real world application this may look something like:
This is because you can't call the .id
attribute on None
We can get the container by using the container()
function
is exactly the same as:
We may need to get some environment variables inside our controller or other parts of our application. For this we can use the env()
function.
is exactly the same as:
We can resolve anything from the container by using this resolve()
function.
is exactly the same as:
That's it! These are simply just functions that are added to Python's builtin functions.
Masonite 1.5 doesn't bring many file changes to Masonite so this upgrade is fairly straight forward and should take less than 10 minutes.
All requirements are now gone with the exception of the WSGI server (waitress
) and the Masonite dependency. You should remove all dependencies and only put:
If you have added your site packages directory to our packages configuration file, you can now remove this because Craft commands can now detect your site packages directory in your virtual environment.
Remove the masonite.providers.ApiProvider.ApiProvider
from the PROVIDERS
list as this has been removed completely in 1.5
If you are using the Api()
route inside routes/api.py
for API endpoints then remove this as well. You will need to implement API endpoints using the new Official Masonite Entry package instead.
You'll also have to add a new RESOURCES = []
line to your routes/api.py
file for the new Masonite Entry package if you choose to use it.
This release works with the new craft command release. Upgrade to version masonite-cli / 1.1+
. <1.1
will only work with Masonite 1.4 and below.
Simply run:
You may have to run sudo if you are using a UNIX machine.
Masonite 1.5 now has sessions that can be used to hold temporary data. It comes with the cookie and memory drivers. Memory stores all data in a class which is lost when the server restarts and the cookie driver sets cookies in the browser.
There is a new config/session.py
file you can copy and paste:
As well as add the SessionProvider
inside your PROVIDERS
list just below the AppProvider
:
That's it! You have officially upgrades to Masonite 1.5
Masonite Routing is an extremely simple but powerful routing system that at a minimum takes a url and a controller. Masonite will take this route and match it against the requested route and execute the controller on a match.
All routes are created inside routes/web.py
and are contained in a ROUTES
constant. All routes consist of either a Get()
route or a Post()
route. At the bare minimum, a route will look like:
Most of your routes will consist of a structure like this. All URI’s should have a preceding /
. Routes that should only be executed on Post requests (like a form submission) will look very similar:
Notice the controller here is a string. This is a great way to specify controllers as you do not have to import anything into your web.py
file. All imports will be done in the backend. More on controllers later.
If you wish to not use string controllers and wish to instead import your controller then you can do so by specifying the controller as well as well as only passing a reference to the method. This will look like:
It’s important here to recognize that we didn't initialize the controller or the method, we did not actually call the method. This is so Masonite can pass parameters into the constructor and method when it executes the route, typically through auto resolving dependency injection.
There are a few methods you can use to enhance your routes. Masonite typically uses a setters approach to building instead of a parameter approach so to add functionality, we can simply attach more methods.
There are several HTTP verbs you can use for routes:
If the syntax is a bit cumbersome, you just want to make it shorter or you like using shorthand helper functions, then you can also use these:
These return instances of their respective classes so you can append on to them:
Most developers choose to use these instead of the classes.
Sometimes routes can be very similiar such as having many dashboard or profile routes:
These routes can be grouped using the group
helper:
Notice that this is the same as above and can help organize and group routes. This feature will also be expanded on in future releases of Masonite.
We can name our routes so we can utilize these names later when or if we choose to redirect to them. We can specify a route name like so:
It is good convention to name your routes since route URI's can change but the name should always stay the same.
Middleware is a great way to execute classes, tasks or actions either before or after requests. We can specify middleware specific to a route after we have registered it in our config/middleware.py
file but we can go more in detail in the middleware documentation. To add route middleware we can use the middleware method like so:
This middleware will execute either before or after the route is executed depending on the middleware.
All controllers are located in app/http/controllers
but sometimes you may wish to put your controllers in different modules deeper inside the controllers directory. For example, you may wish to put all your product controllers in app/http/controllers/products
or all of your dashboard controllers in app/http/controllers/users
. In order to access these controllers in your routes we can simply specify the controller using our usual dot notation:
Controllers are defaulted to the app/http/controllers
directory but you may wish to completely change the directory for a certain route. We can use a forward slash in the beginning of the controller namespace:
This can enable us to use controllers in third party packages.
Very often you’ll need to specify parameters in your route in order to retrieve information from your URI. These parameters could be an id
for the use in retrieving a certain model. Specifying route parameters in Masonite is very easy and simply looks like:
That’s it. This will create a dictionary inside the Request
object which can be found inside our controllers.
In order to retrieve our parameters from the request we can use the param
method on the Request
object like so:
Sometimes you will want to make sure that the route parameter is of a certain type. For example you may want to match a URI like /dashboard/1
but not /dashboard/joseph
. In order to do this we simply need to pass a type to our parameter. If we do not specify a type then our parameter will default to matching all alphanumeric and underscore characters.
This will match all integers but not strings. So for example it will match /dashboard/10283
and not /dashboard/joseph
If we want to match all strings but not integers we can pass:
This will match /dashboard/joseph
and not /dashboard/128372
. Currently only the integer and string types are supported.
You may wish to only render routes if they are on a specific subdomain. For example you may want example.com/dashboard
to route to a different controller than joseph.example.com/dashboard
. To do this we can use the .domain()
method on our routes like so:
This route will match to joseph.example.com/dashboard
but not to example.com/dashboard
or test.example.com/dashboard
.
It may be much more common to match to any subdomain. For this we can pass in an asterisk instead.
This will match all subdomains such as test.example.com/dashboard
, joseph.example.com/dashboard
but not example.com/dashboard
.
If a match is found, it will also add a subdomain
parameter to the Request class. We can retrieve the current subdomain like so:
Masonite 1.4 brings several new features and a few new files. This is a very simple upgrade and most of the changes were done in the pip package of Masonite. The upgrade from 1.3 to 1.4 should take less than 10 minutes
This requirement file has the masonite>=1.3,<=1.3.99
requirement. This should be changed to masonite>=1.4,<=1.4.99
. You should also run pip install --upgrade -r requirements.txt
to upgrade the Masonite pip package.
There is now a new cache folder under bootstrap/cache
which will be used to store any cached files you use with the caching feature. Simply create a new bootstrap/cache
folder and optionally put a .gitignore
file in it so your source control will pick it up.
Masonite comes with a lot of out of the box functionality and nearly all of it is optional but Masonite 1.4 ships with three new providers. Most Service Providers are not ran on every request and therefore does not add significant overhead to each request. To add these 3 new Service Providers simple add these to the bottom of the list of framework providers:
Note however that if you add the CsrfProvider
then you will also need the CSRF middleware which is new in Masonite 1.4. Read the section below to add the middleware
Masonite 1.4 adds CSRF protection. So anywhere there is any POST form request, you will need to add the {{ csrf_field|safe }}
to it. For example:
Lastly, put that middleware into the HTTP_MIDDLEWARE
list inside config/middleware.py
like so:
should now be:
with this change
Masonite tries to make static files extremely easy and comes with whitenoise out of the box. White noise wraps the WSGI application and listens for certain URI requests that can be resistered in your configuration files.
All configurations that are specific to static files can be found in config/storage.py
. In this file you'll find a constant file called STATICFILES
which is simply a dictionary of directories as keys and aliases as the value.
The directories to include as keys is simply the location of your static file locations. For example, if your css files are in storage/assets/css
then put that folder location as the key. For the value, put the alias you want to use in your templates. For this example, we will use css/
as the alias.
For this setup, our STATICFILES
constant should look like:
Now in our templates we can use:
Which will get the storage/assets/css/style.css
file.
Thats it! Static files are extremely simple. You are now a master at static files!
Very often you will find a need to validate forms after you have submitted them. Masonite comes with a very simple and reusable way to validate input data with the Masonite Validator()
class. In this documentation, we'll talk about how you can create your own validator to use within your project. Masonite uses the validator.py
library for this feature.
The best way to create a reusable validator class within your Masonite project is to create a class and inherit from the Validator
class inside the masonite.validator
module.
We can make a class called RegistrationValidator()
, inherit from masonite.validator.Validator()
and put it inside app/validators/RegistrationValidator.py
like so:
Awesome! By inheriting from Validator
, this will add several key methods to our validator class that we'll need to verify our request input which we'll talk about below.
Since we inherited from Validator
, we have access to a validate()
method which we can call with self.validate()
. Inside this method call, we can use some useful attributes in order to check our request data.
An example of a simple validation will look something like:
By default, Masonite will supply you with a dictionary of errors depending on the validator class. More information on what the default error messages are found below.
Although this is very convenient, you may wish to specify your own error messages. To do so, we can call the self.messages()
method after we validate. This will look like:
This will change the default error messages to the defaults provided. If you do not specify a custom error message for a validation field then the default one will display as usual.
In order to check the input data we receive from a request, such as a form submission, we can use this validator like so:
Notice that we pass in the request inside the constructor of our RequestValidator
class. Our class is using the constructor inherited from Masonite's Validator
class.
Sometimes we may wish to use our validator class without request data. In order to do this we can just pass a dictionary of values into our .check()
method like so:
Notice how we passed a dictionary into our .check()
method here and didn't pass the request object in the constructor.
There are a plethora of options that you can use to validate your forms. In addition to validating your request input, we also get a dictionary of errors. In order to get the errors if a validation fails, we can get use the method:
This method will return a dictionary of errors that will be different depending on the validation class used but this method will return None
if there are no errors. Below each option will be what the value of .errors()
will be as well as how you would use them inside Masonite.
By default, all keys registered for validation are optional. Any key that doesn't exist in the validation will skip any of the missing input data. For example, if a validation is not set for password
then it will simply not check any validation on that specific request input. In this case, we can leave our password
validation out entirely.
Unlike other validator classes, this class does not need to be instantiated (contain parenthesis at the end). So Required
is the correct usage and not Required()
.
The Truthy()
validator class will check whatever is truthy to Python. This includes True, non-0 integers, non-empty lists, and strings
This validator will check that a value is equal to the value given.
Note that all request input data will be a string. so make sure you use Equals('1')
and not Equals(1)
. Just be sure to maintain the data type in your validation.
This validator checks that the dictionary value falls inclusively between the start and end values passed to it.
The Pattern validator checks that the dictionary value matches the regex pattern that was passed to it.
Since Orator returns a collection, we can specify an Orator Collection as well:
This validator negates a validator that is passed to it and checks the dictionary value against that negated validator.
This validator checks that the dictionary value is an instance of the base class passed to it, or an instance of one of its subclasses.
This validator checks that the dictionary value inherits from the base class passed to it. To be clear, this means that the dictionary value is expected to be a class, not an instance of a class.
This validator checks that value the must have at least minimum elements and optionally at most maximum elements.
Read more about how to use and create middleware in the documentation.
Masonite 1.4 brings a new config/cache.py
and config/broadcast.py
files. These files can be found on the page and can be copied and pasted into your project. Take a look at the new file and the file. Just copy and paste those configuration files into your project.
This type of protection prevents cross site forgery. In order to activate this feature, we also need to add the . Copy and paste the middleware into your project under the app/http/middleware/CsrfMiddleware.py
file.
There has been a slight change in the constants used in the file. Mainly just for consistency and coding standards. Your file may have some slight changes but this change is optional. If you do make this change, be sure to change any places in your code where you have used the Orator Query Builder. For example any place you may have:
Service Providers are the key building blocks to Masonite. The only thing they do is register things into the Service Container, or retrieve things from the Service Container. You can read more about the Service Container in the Service Container documentation. If you look inside the config/application.py
file, you will find a PROVIDERS
list which contains all the Service Providers involved in building the framework.
You may create your own service provider and add it to your providers list to extend Masonite, or even remove some providers if you don't need their functionality. If you do create your own Service Provider, consider making it available on PyPi so others can install it into their framework.
We can create a Service Provider but simply using a craft command:
This will create a new Service Provider under our app/providers/DashboardProvider.py
. This new Service Provider will have two simple methods, a register
method and a boot
method. We'll explain both in detail.
There are a few architectural examples we will walk through to get you familiar with how Service Providers work under the hood. Let's look at a simple provider and walk through it.
We can see that we have a simple provider that registers the User
model into the container. There are three key features we have to go into detail here.
First, the wsgi = False
just tells Masonite that this specific provider does not need the WSGI server to be running. When the WSGI server first starts, it will execute all service providers that have wsgi
set to False
. Whenever a provider only binds things into the container and we don't need things like requests or routes, then consider setting wsgi
to False
. the ServiceProvider
class we inherited from sets wsgi
to True
by default. Whenever wsgi
is True
then the service provider will fire on every request.
In our register
method, it's important that we only bind things into the container. When the server is booted, Masonite will execute all register methods on all service providers. This is so the boot
method will have access to the entire container.
The boot method will have access to everything that is registered in the container and is actually resolved by the container. Because of this, we can actually rewrite our provider above as this:
This will be exactly the same as above. Notice that the boot
method is resolved by the container.
Great! It's really that simple. Just this knowledge will take you a long way. Take a look at the other service providers to get some inspiration on how you should create yours. Again, if you do create a Service Provider, consider making it available on PyPi so others can install it into their framework.
It's common to want to use a Service Provider to add new methods to a class. For example, you may want to add a is_authenticated
method to the Request
class. Your package and Service Provider may be for a better authentication system.
You may easily extend classes that inherit from the Extendable
class. Many of the built in classes inherit from it.
You have a few options for adding methods to any of the core classes. You can extend a class with functions, classes and class methods. Typical usage may look like:
Usage is very simple and has several options for extending a class. Notice that we don't call the function but we pass the reference to it.
This will simply add the function as a bound method to the Request
class
We can also extend a class method which will take the method given and add it as a bound method.
We can even extend a whole class which will get all the classes methods and create bound methods to the Request class.
The Service Container is an extremely powerful feature of Masonite and should be used to the fullest extent possible. It's important to understand the concepts of the Service Container. It's a simple concept but is a bit magical if you don't understand what's going on under the hood.
The Service Container is just a dictionary where classes are loaded into it by key-value pairs, and then can be retrieved by either the key or value through resolving objects. That's it.
Think of "resolving objects" as Masonite saying "what does your object need? Ok, I have them in this dictionary, let me get them for you."
The container holds all of the frameworks classes and features so adding features to Masonite only entails adding classes into the container to be used by the developer later on. This typically means "registering" these classes into the container (more about this later on).
This allows Masonite to be extremely modular.
There are a few objects that are resolved by the container by default. These include your controller methods (which are the most common and you have probably used them so far) driver and middleware constructors and any other classes that are specified in the documentation.
There are four methods that are important in interacting with the container: bind
, make
and resolve
In order to bind classes into the container, we will just need to use a simple bind
method on our app
container. In a service provider, that will look like:
This will load the key value pair in the providers
dictionary in the container. The dictionary after this call will look like:
The service container is available in the Request
object and can be retrieved by:
In order to retrieve a class from the service container, we can simply use the make
method.
That's it! This is useful as an IOC container which you can load a single class into the container and use that class everywhere throughout your project.
You may want to collect specific kinds of objects from the container based on the key. For example we may want all objects that start with "Exception" and end with "Hook" or want all keys that end with "ExceptionHook" if we are building an exception handler. We can do this easily:
This will return a dictionary of all objects that are binded to the container that start with anything and end with "ExceptionHook" such as "SentryExceptionHook" or "AwesomeExceptionHook".
We can also do the opposite and collect everything that starts with a specific key:
This will collect all keys that start with "Sentry" such as "SentryWebhook" or "SentryExceptionHandler."
Lastly, we may want to collect things that start with "Sentry" and end with "Hook"
This will get keys like "SentryExceptionHook" and "SentryHandlerHook"
You may also collect objects from the container depending on a specific class. This is useful if you need to get all objects in the container that you loaded in yourself and uses a proprietary base object or even just to get all drivers from the container.
This will search the container for all objects that are subclasses of the BaseDriver
and return them as a dictionary.
This is the most useful part of the container. It is possible to retrieve objects from the container by simply passing them into the parameters. Certain aspects of Masonite are resolved such as controller methods, middleware and drivers.
For example, we can hint that we want to get the Request
class and put it into our controller. All controller methods are resolved by the container.
In this example, before the show method is called, Masonite will look at the parameters and look inside the container for a key with the same name. In this example we are looking for Request
so Masonite will look for a key inside the provider dictionary called Request
and inject that value from the container into our method for us. Request
is already loaded into the container for you out of the box.
Another way to resolve classes is by using Python 3 annotations:
Masonite will know that you are trying to get the Request
class and will actually retrieve that class from the container. Masonite will search the container for a Request
class regardless of what the key is in the container, retrieve it, and inject it into the controller method. Effectively creating an IOC container with dependency injection. Think of this as a get by value instead of a get by key like the earlier example.
You can pass in keys and annotations in any order:
Pretty powerful stuff, eh?
Another powerful feature of the container is it can actually return instances of classes you annotate. For example, all Upload
drivers inherit from the UploadContract
which simply acts as an interface for all Upload
drivers. Many programming paradigms say that developers should code to an interface instead of an implementation so Masonite allows instances of classes to be returned for this specific use case.
Take this example:
Notice that we passed in a contract instead of the upload class. Masonite went into the container and fetched a class with the instance of the contract.
The service container can also be used outside of the flow of Masonite. Masonite takes in a function or class method, and resolves it's dependencies by finding them in the service container and injecting them for you.
Because of this, you can resolve any of your own classes or functions.
Remember not to call it and only reference the function. The Service Container needs to inject dependencies into the object so it requires a reference and not a callable.
This will fetch all of the parameters of randomFunction
and retrieve them from the service container. There probably won't be many times you'll have to resolve your own code but the option is there.
Middleware is an extremely important aspect of web applications as it allows you to run important code either before or after every request or even before or after certain routes. In this documentation we'll talk about how middleware works, how to consume middleware and how to create your own middleware. Middleware is only ran when the route is found and a status code of 200 will be returned.
Middleware classes are placed inside the app/http/middleware
by convention but can be placed anywhere you like. All middleware are just classes that contain a before
method and/or an after
method.
There are four types of middleware in total:
Middleware ran before every request
Middleware ran after every request
Middleware ran before certain routes
Middleware ran after certain routes
There are what Masonite calls HTTP Middleware which are middleware designed to run on every request and Route Middleware which is middleware designed to only be called on certain requests, such as checking if a user is logged in.
We have one of two configuration constants we need to work with. These constants both reside in our config/middleware.py
file and are HTTP_MIDDLEWARE
and ROUTE_MIDDLEWARE
.
HTTP_MIDDLEWARE
is a simple list which should contain an aggregation of your middleware classes. This constant is a list because all middleware will simply run in succession one after another, similar to Django middleware
Middleware is a string to the module location of your middleware class. If your class is located in app/http/middleware/DashboardMiddleware.py
then the value we place in our middleware configuration will be a string: app.http.middleware.DashboardMiddleware.DashboardMiddleware
. Masonite will locate the class and execute either the before
method or the after
method.
In our config/middleware.py
file this type of middleware may look something like:
ROUTE_MIDDLEWARE
is also simple but instead of a list, it is a dictionary with a custom name as the key and the middleware class as the value. This is so we can specify the middleware based on the key in our routes file.
In our config/middleware.py
file this might look something like:
There are 3 default middleware that comes with Masonite. These middleware can be changed or removed to fit your application better.
This middleware is design to redirect users to the login page if they are not logged in. This is loaded as a Route Middleware inside config/middleware.py
and design to only be ran on specific routes you specify.
You can run this middleware on any route by specifying the key in the middleware method on your route:
This middleware is an HTTP Middleware and runs on every request. This middleware checks for the CSRF token on POST
requests. For GET
requests, Masonite generates a new token. The default behavior is good for most applications but you may change this behavior to suit your application better.
This middleware checks if the user is logged in and if so, loads the user into the request object. This enables you to use something like:
Again, middleware should live inside the app/http/middleware
folder and should look something like:
Middleware constructors are resolved by the container so simply pass in whatever you like in the parameter list and it will be injected for you.
Read more about this in the Service Container documentation.
If Masonite is running a “before” middleware, that is middleware that should be ran before the request, Masonite will check all middleware and look for a before
method and execute that. The same for “after” middleware.
You may exclude either method if you do not wish for that middleware to run before or after.
This is a boilerplate for middleware. It's simply a class with a before and/or after method. Creating a middleware is that simple. Let's create a middleware that checks if the user is authenticated and redirect to the login page if they are not. Because we have access to the request object from the Service Container, we can do something like:
That's it! Now we just have to make sure our route picks this up. If we wanted this to execute after a request, we could use the exact same logic in the after
method instead.
Since we are not utilizing the after
method, we may exclude it all together. Masonite will check if the method exists before executing it.
We have one of two configuration constants we need to work with. These constants both reside in our config/middleware.py
file and are HTTP_MIDDLEWARE
and ROUTE_MIDDLEWARE
.
HTTP_MIDDLEWARE
is a simple list which should contain an aggregation of your middleware classes. This constant is a list because all middleware will simply run in succession one after another, similar to Django middleware
Middleware is a string to the module location of your middleware class. If your class is located in app/http/middleware/DashboardMiddleware.py
then the value we place in our middleware configuration will be a string: app.http.middleware.DashboardMiddleware.DashboardMiddleware
. Masonite will locate the class and execute either the before
method or the after
method.
In our config/middleware.py
file this type of middleware may look something like:
ROUTE_MIDDLEWARE
is also simple but instead of a list, it is a dictionary with a custom name as the key and the middleware class as the value. This is so we can specify the middleware based on the key in our routes file.
In our config/middleware.py
file this might look something like:
Using middleware is also simple. If we put our middleware in the HTTP_MIDDLEWARE
constant then we don't have to worry about it anymore. It will run on every successful request, that is when a route match is found from our web.py
file.
If we are using a route middleware, we'll need to specify which route should execute the middleware. To specify which route we can just append a .middleware()
method onto our routes. This will look something like:
This will execute the auth middleware only when the user visits the /dashboard
url and as per our middleware will be redirected to the named route of login
Awesome! You’re now an expert at how middleware works with Masonite.
This is an unreleased version. Scheduled Release June 15th 2018
Masonite is the rapid application Python development framework that strives for: beautiful and elegant syntax, actual batteries included with a lot of out of the box functionality, and extremely extendable. Masonite works hard to be fast and easy from install to deployment so developers can go from concept to creation in as quick and efficiently as possible. Try it once and you’ll fall in love.
Easily send emails with the Mail Provider and the SMTP and Mailgun drivers.
Send websocket requests from your server with the Broadcast Provider and Pusher and Ably drivers.
IOC container and auto resolving dependency injection.
Service Providers to easily add functionality to the framework.
Extremely simple static files configured and ready to go.
Active Record style ORM called Orator.
An extremely useful command line tool called craft commands.
Extremely extendable.
These, among many other features, are all shipped out of the box and ready to go. Use what you need when you need it.
In order to use Masonite, you’ll need:
Python 3.4+
Pip3
If you are running on a Linux flavor, you’ll need the Python dev package. You can download this package by running:
Or you may need to specify your python3.x-dev
version:
Masonite works at being simple to install and get going. We use a simple command line that will become your best friend. You’ll never want to develop again without it. We call it the craft
command line tool.
We can download our craft
command line tool by just running:
You may have to use sudo if you are on a UNIX machine
All examples of pip in this documentation are based on pip3. If you see a pip command it is implied you are using a Python 3 pip command and not a Python 2 pip command
Great! We are now ready to create our first project. We should have the new craft
command. We can check this by running:
This should show a list of command options. If it doesn't then try closing your terminal and reopening it or running it with sudo
if you are on a UNIX machine. We are currently only interested in the craft new
command. To create a new project just run:
This will get the latest Masonite project template and unzip it for you. We just need to go into our new project directory and install the dependencies in our requirements.txt
file.
You can optionally create a virtual environment if you don't want to install all of masonite's dependencies on your systems Python. If you use virtual environments then create your virtual environment by running:
or if you are on Windows:
The python
command here is utilizing Python 3. Your machine may run Python 2 (typically 2.7) by default. You may set an alias on your machine for Python 3 or simply run python3
anytime you see the python
command.
For example, you would run python3 -m venv venv
instead of python -m venv venv
Now lets install our dependencies. We can do this simply by using a craft
command:
This command is just a wrapper around the pip
command with a few added Masonite specific install instructions. This installs all the required dependencies of Masonite, creates a .env
file for us, generates a new secret key, and puts that secret key in our .env
file.
Two of the libraries that Masonite uses are currently not up to date with Python 3.7 installation. These libraries have old versions the .pyc files inside their distributions and need to be installed outside of the normal install workflow. Installing for Python 3.7 will be:
After it’s done we can just run the server by using another craft
command:
You can also run the server in auto-reload mode which will rerun the server when file changes are detected:
Congratulations! You’ve setup your first Masonite project! Keep going to learn more about how to use Masonite to build your applications.
You can learn more about craft by reading The Craft Command documentation or continue on to learning about how to create web application by first reading the Routing documentation
The Request class is initialized when the server first starts and is modified on every request. This means that the Request class acts as a singleton and is not reinitialized on every request. This presents both pros and cons during developing Masonite. It's great to not have to worry about a new object being instantiated everytime but the con is that some attributes need to be reset at the end of the request.
The Request class is loaded into the IOC container first so any Service Provider will have access to it. The IOC container allows all parts of the framework to be resolved by the IOC container and auto inject any dependencies they need.
Read more about the IOC container in the Service Container documentation.
The Request class is bound into the IOC container once when the server is first started. This takes the WSGI environment variables generated by your WSGI server as a parameter. Because of this, we reload the WSGI values on every request but the actual Request object does not change. In other words, the memory address of the Request object is always the same but the class attributes will change of every request. This is done already for you by the Masonite framework itself. This Request class is bound and initialized inside the AppProvider
Service Provider. We grab this request object by simply passing in Request
into the parameters of anything resolved by the Service Container such as middleware, drivers and controller methods like so:
Masonite is smart enough to know that we need the Request
class and it will inject it into our method for us.
Masonite ships with a HelpersProvider
Service Provider which adds several helper functions. One of these helper functions is the request()
function. This function will return the request object. Because of this, these two pieces of code are identical:
Notice we didn't import anything at the top of our file and also didn't retrieve any objects from the IOC container. Masonite helper functions act just like any other built in Python function.
Read more about helper functions in the Helper Functions documentation.
The Request
has several helper methods attached to it in order to interact with various aspects of the request.
In order to get the current request input variables such as the form data during a POST
request or the query string during a GET
request looks like:
There is no difference between any HTTP methods (GET, POST, PUT, etc) when it comes to getting input data. They are all retrieved through this .input()
method so there is no need to make a distinction if the request is GET
or POST
We can get all the request input variables such as input data from a form request or GET data from a query string. Note that it does not matter what HTTP method you are using, the input method will know what input data to get dependent on the current HTTP method (GET
, POST
, PUT
, etc)
This will return all the available request input variables for that request as a dictionary.
To get a specific input:
To check if some request input data exists:
To get the request parameter retrieved from the url. This is used to get variables inside: /dashboard/@firstname
for example.
You may also set a cookie in the browser. The below code will set a cookie named key
to the value of value
.
By default, all cookies are encrypted with your secret key which is generated in your .env
file when you installed Masonite. This is a security measure to ensure malicious Javascript code cannot fetch cookies if they are somehow retrieved. All cookies are set with the HTTP_ONLY flag meaning that Javascript cannot read them although you can turn this off using a parameter.
Sometimes you may want to handle incoming JSON requests. This could be form external API's like Github.
Masonite will detect that an incoming request is a JSON request and put the cast the JSON to a dictionary and load it into the payload request input. For example if you have an incoming request of:
Then we can fetch this input in a controller like so:
If you choose to not encrypt your values and create cookies with the plain text value then you can pass a third value of True
or False
. You can also be more explicit if you like:
All cookies are set as session cookies. This means that when the user closes out the browser completely, all cookies will be deleted.
This will set a cookie thats expires 5 minutes from the current time.
Again, as a security measure, all cookies automatically are set with the HttpOnly
flag which makes it unavailable to any Javascript code. You can turn this off:
This will now allow Javascript to read the cookie.
You can get all the cookies set from the browser
You can get a specific cookie set from the browser
Again, all cookies are encrypted by default so if you set a cookie with encryption then this method will decrypt the cookie. If you set a cookie in plain text then you should pass the False
as the second parameter here to tell Masonite not to decrypt your plain text cookie value.:
This will return the plain text version of the cookie.
If Masonite attempts to decrypt a cookie but cannot then Masonite will assume that the secret key that encrypted it was changed or the cookie has been tampered with and will delete the cookie completely.
If your secret key has been compromised then you may change the key at anytime and all cookies set on your server will be removed.
You may also delete a cookie. This will remove it from the browser.
You can also get the current user from the request. This requires the LoadUserMiddleware
middleware which is in Masonite by default. This will return an instance of the current user.
You can specify a url to redirect to
If the url contains http
than the route will redirect to the external website
You can redirect to a named route
You can also go back to a named route specified from the form input back
. This will get the request input named back
and redirect to that named route. This is great if you want to redirect the user to a login page and then back to where they came from. Just remember during your form submission that you need to supply a back
input.
This is equivalent to:
You can also specify the input parameter that contains the named route
Sometimes your routes may require parameters passed to it such as redirecting to a route that has a url like: /url/@firstname:string/@lastname:string
. For this you can use the send
method. Currently this only works with named routes.
You can load a specific secret key into the request by using:
This will load a secret key into the request which will be used for encryptions purposes throughout your Masonite project.
Note that by default, the secret key is pulled from your configuration file so you do NOT need to supply a secret key, but the option is there if you need to change it for testing and development purposes.
You can also get and set any headers that the request has.
You can get all WSGI information by printing:
This will print the environment setup by the WSGI server. Use this for development purposes.
You can also get a specific header:
This will return whatever the HTTP_AUTHORIZATION
header if one exists. If that does not exist then the AUTHORIZATION
header will be returned. If that does not exist then None
will be returned.
We can also set headers:
Masonite will automatically prepend a HTTP_
to the header being set for standards purposes so this will set the HTTP_AUTHORIZATION
header. If you do not want the HTTP
prefix then pass a third parameter:
This will set the AUTHORIZATION
header instead of the HTTP_AUTHORIZATION
header.
Masonite will set a status code of 404 Not Found
at the beginning of every request. If the status code is not changed throughout the code, either through the developer or third party packages, as it passes through each Service Provider then the status code will continue to be 404 Not Found
when the output is generated. You do not have to explicitly specify this as the framework itself handles status codes. If a route matches and your controller method is about to be hit then Masonite will set 200 OK
and hit your route. This allows Masonite to specify a good status code but also allows you to change it again inside your controller method.
You could change this status code in either any of your controllers or even a third party package via a Service Provider.
For example, the Masonite Entry package sets certain status codes upon certain actions on an API endpoint. These can be 429 Too Many Requests
or 201 Created
. These status codes need to be set before the StartProvider
is ran so if you have a third party package that sets status codes or headers, then they will need to be placed above this Service Provider in a project.
If you are not specifying status codes in a package and simple specifying them in a controller then you can do so freely without any caveats. You can set status codes like so:
This will set the correct status code before the output is sent to the browser. You can look up a list of HTTP status codes from an online resource and specify any you need to. There are no limitations to which ones you can use.
Typically, forms only have support for GET
and POST
. You may want to change what HTTP method is used when submitting a form such as PATCH
.
This will look like:
or you can optionally use a helper method:
When the form is submitted, it will process as a PUT request instead of a POST request.
This will allow this form to hit a route like this:
Very often you will need to upload user images such as a profile image. Masonite let's you handle this very elegantly and allows you to upload to both the disk, and Amazon S3 out of the box. The UploadProvider
Service Provider is what adds this functionality. Out of the box Masonite supports the disk
driver which uploads directly to your file system and the s3
driver which uploads directly to your Amazon S3 bucket.
You may build more drivers if you wish to expand Masonite's capabilities. If you do create your driver, consider making it available on PyPi so others may install it into their project.
Read the "Creating an Email Driver" for more information on how to create drivers. Also look at the drivers
directory inside the MasoniteFramework/core
repository.
All uploading configuration settings are inside config/storage.py
. The settings that pertain to file uploading are just the DRIVER
and the DRIVERS
settings.
This setting looks like:
This defaults to the disk
driver. The disk driver will upload directly onto the file system. This driver simply needs one setting which is the location
setting which we can put in the DRIVERS
dictionary:
This will upload all images to the storage/uploads
directory. If you change this directory, make sure the directory exists as Masonite will not create one for you before uploading. Know that the dictionary inside the DRIVERS
dictionary should pertain to the DRIVER
you set. For example, to set the DRIVER
to s3
it will look like this:
Some deployment platforms are Ephemeral. This means that either hourly or daily, they will completely clean their file systems which will lead to the deleting of anything you put on the file system after you deployed it. In other words, any user uploads will be wiped. To get around this, you'll need to upload your images to Amazon S3 or other asset hosting services which is why Masonite comes with Amazon S3 capability out of the box.
Uploading with masonite is extremely simple. We can use the Upload
class which is loaded into the container via the UploadProvider
Service Provider. Whenever a file is uploaded, we can retrieve it using the normal Request.input()
method. This will look something like:
And inside our controller we can do:
That's it! We specified the driver we want to use and just uploaded an image to our file system.
This action will return the file system location. We could use that to input into our database if we want:
We may also need to get the filename of the upload. If the request input is a file upload, we have some additional attributes we can use:
Lastly, we may need to prepend the file name with something like a uuid
or something or even just a normal string. We can do so by using the storePrepend()
method:
When uploading, it may be important to only accept certain file types. Out of the box Masonite allows uploading of all file types including .doc and .dmg and .exe. This is a security hazard so you whenever you upload, you should specify which file types you want to accept:
Notice that we specified jpeg
and jpg
. You should specify both if you want to accept either of them as the file extensions are unpredictable.
This doesn't only work for images. You can specify any file types:
Before you get started with uploading to Amazon S3, you will need the boto3 library:
Uploading to S3 is exactly the same. Simply add your username, secret key and bucket to the S3 setting:
Make sure that your user has the permission for uploading to your S3 bucket.
Then in our controller:
How the S3 driver currently works is it uploads to your file system using the disk
driver, and then uploads that file to your Amazon S3 bucket. So do not get rid of the disk
setting in the DRIVERS
dictionary.
Because of Masonite's Service Container, It is extremely easy to make drivers that can be used by simply adding your service provider.
Masonite comes shipped with a Service Provider called MailProvider
which loads a few classes into the container as well as boots the default mail driver using the MailManager
. This manager class will fetch drivers from the container and instantiate them. We can look at the MailProvider
class which will gives us a better explanation as to what's going on:
We can see here that because we are only binding things into the container and we don't need the WSGI server to be running, we set wsgi = False
. Service Providers that set wsgi
to False
will only run when the server starts and not on every request.
We can see here that we are binding a few drivers into the container and then binding the MailManager
on boot. Remember that our boot method has access to everything that has been registered into the container. The register methods are executed on all providers before the boot methods are executed.
The MailManager
here is important to understand. When the MailManager
is instantiated, it accepts the container as a parameter. When the MailManager
is instantiated, it fires a create_driver
method which will grab the driver from the configuration file and retrieve a MailXDriver
from the container. The create_driver
method is a very simple method:
Notice that when the driver is created, it tries to get a Mail{0}Driver
from the container. Therefore, all we need to do is register a MailXDriver
into the container ('X' being the name of the driver) and Masonite will know to grab that driver.
So now we know that we need a MailXDriver
so let's walk through how we could create a maildrill
email driver.
We can simply create a class which can become our driver. We do not need to inherit anything, although Masonite comes with a BaseMailDriver
to get you started faster and all drivers should inherit from it for consistency reasons. You can make your driver from a normal class object but it will be harder and won't be considered in Pull Requests.
Let's create a class anywhere we like and inherit from BaseMailDriver
:
Great! We are almost done. We just have to implement one method on this class and that's the send
method. All other methods like to
and template
are inherited from the BaseMailDriver
class. You can find out how to send an email using Maildrill and implement it in this send
method.
We can look at other drivers for inspiration but let's look at the MailMailgunDriver
class now:
If you are wondering where the self.message_body
and self.config
are coming from, check the BaseMailDriver
. All driver constructors are resolved by the service container so you can grab anything you need from the container to make your driver work. Notice here that we don't need a constructor because we inherited it from the BaseMailDriver
Our AppProvider
class might look something like this:
Great! Our new driver is registered into the container. It is now able to be created with Masonite's MailManager
class. We can retrieve your new driver by doing:
If we want the MailManager
to use our new driver by default, change the DRIVER
in our config/mail.py
file. In addition, you may have the users of your driver require a special dictionary entry to the DRIVERS
dictionary:
This way, users can easily swap drivers by simply changing the driver in the config file.
That's it! We just extended our Masonite project and created a new driver. Consider making it available on PyPi so others can install it!
Masonite comes with email support out of the box. Most projects you make will need to send emails upon actions like account creation or notifications. Because email is used so often with software applications, masonite provides mail support with several drivers.
All mail configuration is inside config/mail.py
and contains several well documented options. There are several built in drivers you can use but you can make your own if you'd like.
By default, Masonite uses the smtp
driver. Inside your .env
file, just put your smtp credentials. If you are using Mailgun then switch your driver to mailgun
and put your Mailgun credentials in your .env
file.
There are two drivers out of the box that masonite uses and there is a tiny bit of configuration for both.
The SMTP driver takes several configuration files we can all put in our .env
file.
Because this is SMTP, we can utilize all SMTP services such as mailtrap and gmail.
Thats it! As long as the authentication works, we can send emails.
Remember that it is save to put sensitive data in your .env
file because it is not committed to source control and it is inside the .gitignore
file by default.
Mailgun does not use SMTP and instead uses API calls to their service to send emails. Mailgun only requires 2 configuration settings:
If you change to using Mailgun then you will need to change the driver. By default the driver looks like:
This means you can specify the mail driver in the .env file:
or we can specify the driver directly inside config/mail.py
Masonite will retrieve the configuration settings for the mailgun driver from the DRIVERS
configuration setting which Masonite has by default, you do not have to change this.
The Mail
class is loaded into the container via the the MailProvider
Service Provider. We can fetch this Mail
class via our controller methods:
We can send an email like so:
You can also obviously specify a specific user:
All mail drivers are managed by the MailManager
class and bootstrapped with the MailProvider
Service Provider.
We can specify which driver we want to use. Although Masonite will use the DRIVER
variable in our mail
config file by default, we can change the driver on the fly.
You can see in our MailProvider
Service Provider that we can use the MailManager
class to set the driver. We can use this same class to change the driver:
Sending an email may take several seconds so it might be a good idea to create a Job. Jobs are simply Python classes that inherit from the Queueable
class and can be pushed to queues or ran asynchronously. This will look something like:
Instead of taking seconds to send an email, this will seem immediate and be sent using whatever queue driver is set. The async
driver is set by default which requires no additional configuration and simply sends jobs into a new thread to be ran in the background.
We can also specify the subject:
You can specify which address you want the email to appear from:
If you don't want to pass a string as the message, you can pass a view template.
This will render the view into a message body and send the email as html. Notice that we didn't pass anything into the send
message
Not much has changed in the actual project structure of Masonite 1.6 so we just need to make some minor changes to our existing 1.5 project
We just have to change our Masonite dependency version in this file:
Masonite 1.6 now wraps the majority of the application in a try and catch block so we can add exception handling such as the new debug view and other exception features down the road such as Sentry and other error logging.
In the middle of the file (line 45 if you have not made changes to this file) we can simply wrap the application:
This will also display an exception view whenever an exception is it.
That's all the changes we have to make for our project.
Since the MailManager
class creates the driver on boot, we can simply register the driver into the container via any service providers register method. We could create a new Service Provider and register it there. You can read more about created Service Providers under the documentation. For now, we will just register it from within our AppProvider
.
You can follow the documentation here at . If you do make your own, consider making it available on PyPi so others can install it. We may even put it in Masonite by default.
Read more about creating Jobs and sending emails asynchronously in the documentation.
Read for any futher changes you may want or have to make to your code base.