Full documentation is available on readthedocs
flaskerize
flaskerize
is a code generation and project modification command line interface (CLI) written in Python and created for Python. It is heavily influenced by concepts and design patterns of the Angular CLI available in the popular JavaScript framework Angular. In addition to vanilla template generation, flaskerize
supports hooks for custom run
methods and registration of user-provided template functions. It was built with extensibility in mind so that you can create and distribute your own library of schematics for just about anything.
Use flaskerize
for tasks including:
- Generating resources such as Dockerfiles, new
flaskerize
schematics, blueprints, yaml configs, SQLAlchemy entities, or even entire applications, all with functioning tests - Upgrading between breaking versions of projects that provide flaskerize upgrade schematics with one command
- Bundling and serving static web applications such as Angular, React, Gatsby, Jekyll, etc within a new or existing Flask app.
- Registering Flask resources as new routes within an existing application
- Creating new schematics for your own library or organization
What about cookiecutter?
Cookiecutter is awesome and does something different than flaskerize
, but understandably they sound similar at first. Whereas cookiecutter
is designed for scaffolding new projects, flaskerize
is for ongoing use within an existing project for generation of new components, resources, etc and for modification of existing code.
Both projects use Jinja templates and JSON files for configuration of parameters. If you like cookiecutter
(like me), you should feel right at home with flaskerize
.
Flaskerize is looking for developers!
At the time of this writing, the flaskerize
project is somewhat of a experiment that was born out of a personal weekend hackathon. I am pretty happy with how that turned out, particularly the CLI syntax, but there are many aspects of the current internal code that should be changed. See the Issues section for updates on this. The rest of this section details the grander vision for the project
Currently, there is nothing even remotely close to the Angular CLI descried previously in the Python community, but we would benefit from it immensely. This is the reason for flaskerize
. The vision is to create a generalized and extensible CLI for generation of new code and modification of existing code. This functionality could include, but is not limited to, things such as generating:
- Flask API resources, such as those described in this blog post (multi-file templates)
- SQLAlchemy models
- Marshmallow schemas
- Typed interfaces
- Flask/Django views and other Jinja templates
- Data science modeling pipelines
- Anything else the community wants to provide templates for
This last one is important, as providing a hook to make the system extensible opens an entire ecosystem of possibilities. Imagine being able to pip install <some_custom_templates>
and then being able to use flaskerize
to generate a bunch of custom code that is specific to your organization, personal project, enthusiast group, etc.
In addition to code generation, this CLI could modify existing files. For example -- create a new directory containing a Flask-RESTplus Namespace and associated resources, tests, and then register that within an existing Flask app. This would need to be able to inspect the existing app and determine if the registration has already been provided and adding it only if necessary. The magic here is that with one command the user will be able to generate a new resource, reload (or hot-reload) their app, and view the new code already wired up with working tests. I cannot emphasize enough how much this improves developer workflow, especially among teams and/or on larger projects.
Why do I need a tool like this?
Productivity, consistency, and also productivity.
Flaskerize is an incredible productivity boost (something something 10x dev). This project is based on the concept of schematics, which can be used to generate code from parameterized templates. However, schematics can do much more. They support the ability to register newly created entities with other parts of the app, generate functioning tests, and provide upgrade paths across breaking version of libraries. Perhaps more important than the time this functionality saves the developer is the consistency it provides to the rest of the team, resulting in decreased time required for code reviews and collaborative development. Also, it promotes testing, which is always a good thing.
Installation
Simple, pip install flaskerize
Schematics that ship with flaskerize
For a list of the schematics that are available by default, see here
Creating your own schematics
You can easily create your own schematics through use of fz generate schematic path/to/schematics/schematic_name
. This will create a new, blank schematic with the necessary files, and you can then render this new schematic with fz generate path/to/schematics/:schematic_name [args]
-- note the :
used to separate the schematic name when invoking. For simplicity, you can optionally drop the trailing schematics/
from the path as this folder is always required by the convention in flaskerize
(e.g. fz generate /path/to:schematic_name [args]
)
Custom arguments, run functionality, and functions can then be provided in schema.json, run.py, and custom_functions.py, respectively. See the other sections of this README for specific details on each of these.
Schematics in third-party packages
flaskerize
is fully extensible and supports schematics provided by external libraries. To target a schematic from another package, simply use the syntax fz generate <package_name>:<schematic_name> [OPTIONS]
Flaskerize expects to find a couple of things when using this syntax:
- The package
<package_name>
should be installed from the current python environment - The top-level source directory of
<package_name>
should contain aschematics/
package. Inside of that directory should be one or more directories, each corresponding to a single schematic. See the section "Structure of a schematic" for details on schematic contents. - A
schematics/__init__.py
file, just so that schematics can be found as a package
For schematics that are built into
flaskerize
, you can drop the<package_name>
piece of the schematic name. Thus the commandfz generate flaskerize:app new_app
is exactly equivalent tofz generate app new_app
. For all third-party schematics, you must provide both the package and schematic name.
For example, the command fz generate test_schematics:resource my/new/resource
will work if test_schematics is an installed package in the current path with a source directory structure similar to:
├── setup.py
└── test_schematics
├── __init__.py
└── schematics
├── __init__.py
├── resource
│ ├── run.py
│ ├── schema.json
│ ├── someConfig.json.template
│ ├── thingy.interface.ts.template
│ ├── thingy.py.template
│ └── widget.py.template
Structure of a schematic
schema.json
Each schematic contains a schema.json
file that defines configuration parameters including the available CLI arguments, template files to include, etc.
parameters:
- templateFilePatterns: array of glob patterns representing files that are to be rendered as Jinja templates
- ignoreFilePatterns: array of glob patterns representing files that are not to be rendered as part of the schematic output, such as helper modules
- options: array of dicts containing parameters for argument parsing with the addition of an array parameter
aliases
that is used to generate alternative/shorthand names for the command. These dicts are passed along directly toargparse.ArgumentParser.add_argument
and thus support the same parameters. See here for more information.
Running custom code
The default behavior of a schematic is to render all template files; however, flaskerize
schematics may also provide custom code to be executed at runtime through providing a run
method inside of a run.py
within the top level of the schematic. A basic run.py looks as follows:
from typing import Any, Dict
from flaskerize import SchematicRenderer
def run(renderer: SchematicRenderer, context: Dict[str, Any]) -> None:
template_files = renderer.get_template_files()
for filename in template_files:
renderer.render_from_file(filename, context=context)
renderer.print_summary()
The run
method takes two parameters:
- renderer: A SchematicRenderer instance which contains information about the configured schematic such as the fully-qualified
schematic_path
, the Jinjaenv
, handles to the file system, etc. It also has helper methods such asget_template_files
for obtaining a list of template files based upon the contents of the schematic and the configuration settings ofschema.json
andrender_from_file
which reads the contents of a (template) file and renders it withcontext
. - context: A
dict
containing the key-value pairs of the parsed command line arguments provided in theoptions
array fromschema.json
.
With these two parameters, it is possible to accomplish quite a lot of custom modification. For example, suppose a schematic optionally contains an app-engine.yaml
file for deployment to Google Cloud, which the consumer might not be interested in. The schematic author can then provide a --no-app-engine
switch in schema.json
and then provide a custom run method:
from os import path
from typing import Any, Dict
from flaskerize import SchematicRenderer
def run(renderer: SchematicRenderer, context: Dict[str, Any]) -> None:
for filename in renderer.get_template_files():
dirname, fname = path.split(filename)
if fname == 'app-engine.yaml' and context.get('no_app_engine', False):
continue
renderer.render_from_file(filename, context=context)
Although rendering templates is the most common operation, you can perform arbitrary code execution inside of run
methods, including modification/deletion of existing files, logging, API requests, test execution, etc. As such, it is important to be security minded with regard to executing third-party schematics, just like any other script.
Customizing template functions
Schematics optionally may provide custom template functions for usage within the schematic.
Currently, custom_functions.py is provided at the schematic level. There is not yet a means to register custom functions "globally" within a family of schematics, but there are plans to do so if there are interested parties. Comment/follow #16 for updates if this is something in which you are interested
To register custom functions, create a file called custom_functions.py
within the schematic (at the same directory level as schema.json, run.py, etc). Within this file, apply the flaskerize.register_custom_function
decorator to functions that you would like to make available. Within a template, the function can then be invoked using whatever name and signature was used to define it in custom_functions.py
.
Here is an example
# custom_functions.py
from flaskerize import register_custom_function
@register_custom_function
def truncate(val: str, max_length: int) -> str:
return val[:max_length]
That's all! You can now invoke truncate
from within templates. Suppose a template file {{name}}.txt.template
containing the following
Hello {{ truncate(name, 3) }}!
Then an invocation of fz generate <package:schematic_name> voodoo
will yield a file voodoo.txt
containing
Hello voo!
Additional examples can be found within the Flaskerize test code
Examples
Create a new Entity
An entity
is a combination of a Marshmallow schema, type-annotated interface, SQLAlchemy model, Flask controller, and CRUD service as described in this blog post
The command fz generate entity path/to/my/doodad
would produce an entity
called Doodad
with the following directory structure.
Note: the current version of flaskerize
generates the code for an Entity, but does not yet automatically wire it up to an existing application, configure routing, etc. That will come soon, but for now you will need to make that modification yourself. To do so, invoke the register_routes
method from the entity's __init__py file from within your application factory. For more information, check out a full working example project here. This is also a great opportunity to become a contributor!
path
└── to
└── my
└── doodad
├── __init__.py
├── controller.py
├── controller_test.py
├── interface.py
├── interface_test.py
├── model.py
├── model_test.py
├── schema.py
├── schema_test.py
├── service.py
└── service_test.py
Create a new React + Flask project and bundle together with Flaskerize
Install yarn and create-react-app
Make a new react project and build into a static site:
create-react-app test
cd test
yarn build --prod
cd ..
Generate a new Flask app with flaskerize
fz generate app app
Bundle the new React and Flask apps together:
fz bundle --from test/build/ --to app:create_app
Run the resulting app:
python app.py
The app will now be available on http:localhost:5000/!
Generate a basic Flask app
Generating a basic Flask app is simple:
fz generate app my_app
Then you can start the app with python my_app.py
and navigate to http://localhost:5000/health to check that the app is online
Create new React app
Install yarn and create-react-app
create-react-app test
cd test
yarn build --prod
cd ..
Upon completion the built site will be contained in test/build/
To view the production React app as-is (no Flask), you can use serve
(you'll need to install it globally first yarn global add serve
)
serve -s test/build/
Alternatively, you could also serve directly with python http.server
:
python -m http.server 5000 --directory test/build
The app will now be available on http:localhost:5000/
Now, to serve this from a new Flask app with flaskerize
, run the following
fz generate app --from test/build/ app.py
This command will generate a file app.py
containing the Flask app, which can then be run with python app.py
The Flask-ready version of your React app can now be viewed at http:localhost:5000/!
Create new Angular app
Install yarn and the Angular CLI
ng new
cd <project name>
yarn build --prod
fz generate app ng_app
fz generate app --from dist/<project name>/ app.py
This command will generate a file app.py
containing the Flask app, which can then be run with python app.py
The Flask-ready version of your Angular app can now be viewed at http:localhost:5000/!
Attach site to an existing Flask app
Flaskerize uses the factory pattern exclusively. If you're existing application does not follow this, see Factory pattern
Attach with one command and generate Dockerfile
fz bundle --from test/build/ --to app:create_app --with-dockerfile
Separate generation and attachment
First, create a blueprint from the static site
fz generate bp --from test/build/ _fz_blueprint.py
Next, attach the blueprint to your existing Flask app
fz a --to app.py:create_app _fz_blueprint.py