Designed for company internal projects and tools
Focused on fast development and high functional quality
Clean, on reusability focused architecture
Batteries included for common problems
Extensive runtime information
Helpful groundwork extensions
Application | Bundles plugins to an usable function set |
Plugins | Provides use case specific functions |
Patterns | Provides technical interfaces |
Example:
Example: Application JenkinsGate
from groundwork import App
class JenkinsGate:
def __init__(self):
self.app = App()
def start(self):
my_plugins = ["ViewDatabase", "ViewJenkinsData", "InformTeam"]
# Knowing plugin name is enough for activation
self.app.plugins.activate(my_plugins)
# Finally start the interface of choice, here a cli
self.app.commands.start_cli()
Example: Plugin ViewJenkinsData
from groundwork_database.patterns import GwSqlPattern
from .patterns import MyJenkins
class ViewJenkinsData(GwSqlPattern, MyJenkins):
def __init__(self, app, **kwargs):
self.name = "ViewJenkinsData"
super().__init__(app, **kwargs)
def activate(self):
self.db = self.databases.register("jenkins", "sqlite:///",
"database for jenkins data")
# Get and store first data already on activation
data = self.get_jenkins_data()
self.store_jenkins_data(data)
def deactivate(self):
pass
def get_jenkins_data(self):
data = self.jenkins.get_job("MyJob")
return data
def store_jenkins_data(self, data)
self.db.add(data)
self.db.commit()
Example: Pattern MyJenkins
from groundwork.patterns import GwBasePattern
class MyJenkins(GwBasePattern):
def _init_(self, app, *args, **kwargs):
super().__init__(app, *args, **kwargs)
self.jenkins = Jenkins()
class Jenkins:
def get_job(self, job):
req = requests.get("http://my_jenkins.com/{0}".format(job))
if req.status_code < 300:
return req.json()
else:
raise Exception("Ups, error happened!")
Command line interface
Signals and Receivers
Threads
Shared objects
Recipes
Documents
Based on Click
Application: Start cli
from groundwork import App
my_app = App()
my_app.plugins.activate(["MyPlugin"])
my_app.commands.start_cli()
Plugin: Register command
from groundwork.patterns import GwCommandsPattern
class MyPlugin(GwCommandsPattern):
def activate(self):
self.commands.register(command="my_command",
description="executes something",
function=self.my_command,
params=[])
def my_command(self, plugin, **kwargs):
print("Yehaaa")
groundwork documentation: Commands
Based on Blinker
Sending a signal
class MyPlugin(GwBasePattern):
def activate(self):
self.signals.register(signal ="my_signal",
description="this is my first signal")
self.signals.send("my_signal")
Receive a signal
class MyPlugin_2(GwBasePattern):
def activate(self):
self.signals.connect(receiver="My signal receiver",
signal="my_signal",
function=self.fancy_stuff,
description="Doing some fancy")
def fancy_stuff(plugin, **kwargs):
print("FANCY STUFF!!! " * 50)
groundwork documentation: Signals and Receivers
Based on Python Threading
from groundwork.patterns import GwThreadsPattern
class MyPlugin(GwThreadsPattern):
def activate(self):
my_thread = self.threads.register(name="my_thread",
description="run something",
function=self.my_thread)
my_thread.run()
def my_thread(self, plugin, **kwargs):
print("Yehaaa")
groundwork documentation: Threading
Provide a shared objects
from groundwork.patterns import GwSharedObjectsPattern
class MyPlugin(GwSharedObjectsPattern):
def activate(self):
self.my_shared_object = {"name": "shared"
"name2": "object"}
self.shared_objects.register(name="my_shared_object",
description="A shared object of My Plugin",
obj=self.my_shared_object)
Access a shared object
class MyPlugin2(GwSharedObjectsPattern)
def some_function(self):
obj = self.shared_objects.access("my_shared_object")
groundwork documentation: Shared Objects
Based on Cookiecutter
Register a recipe
class My_Plugin(GwRecipesPattern):
def activate(self):
...
self.recipes.register("my_recipe",
os.path.abspath("path/to/recipe/folder"),
description="An awesome recipe",
final_words="Yehaa, installation is done")
Recipe folder structure
/
|-- cookiecutter.json
|
|-- {{ cookiecutter.project_name}}
|
|-- other directories/files, which will be copied.
Using recipe
groundwork recipe_build my_recipe
groundwork documentation: Recipes
Based on Jinja and RestructuredText
Register a document
from groundwork.patterns import GwDocumentsPattern
class My_Plugin(GwDocumentsPattern):
def activate(self):
my_content = """
My Plugin
=========
Application name: {{app.name}}
Plugin name: {{plugin.name}}
"""
self.documents.register(name="my_document",
content=my_content,
description="Provides information about 'My Plugin'")
groundwork documentation: Documents
groundwork forces developers during registration of objects to provide:
groundwork stores also:
On plugin level
from groundwork.patterns import GwCommandsPattern
class MyPlugin(GwCommandsPattern):
def print_my_commands():
commands = self.commands.get() # Gets commands for this plugin only
for command in commands:
print("{command} registered by {plugin}".format(
command.command, command.plugin.name))
On application level
from groundwork import App
my_app = App()
my_app.plugins.activate("MyPlugin", "AnotherPlugin", "AwesomePlugin")
commands = my_app.commands.get() # Gets all commands of used app
for command in commands:
print("{command} registered by {plugin}".format(command.command, command.plugin.name))
With loaded plugin GwDocumentsInfo
>>> my_app doc
Using GwWebManager of groundwork-web package
groundwork-database
groundwork-web
groundwork-users
groundwork-validation
groundwork-spreadsheets
Based on SQLAlchemy
Documentation: groundwork-database.readthedocs.io
from groundwork import App
from groundwork_database.patterns import GwSqlPattern
class MyPlugin(GwSqlPattern):
def _init_(self, app, *args, **kwargs):
self.name = "My Plugin"
super().__init__(app, *args, **kwargs)
def activate(self):
name = "my_db"
database_url = "sqlite:///:memory:"
description = "My personal test database"
db = self.databases.register(name, database_url, description)
User = _get_user_class(db.Base)
my_user = User(name="Me")
db.add(my_user)
db.commit()
def print_user(name):
db = self.databases.get("my_db")
user = db.query(User).filter_by(name=name).first()
if user is not None:
print(user.name)
else:
print("User %s not found." % name)
def _get_user_class(base):
class User(base):
id = Column(Integer, primary_key=True)
name = Column(String)
return User
if __name__ == "__main__":
my_app = App()
my_plugin = MyPlugin(my_app)
my_plugin.activate()
my_plugin.print_user("me")
Based on Flask
Documentation: groundwork-web.readthedocs.io
self.web.contexts.register("webmanager",
template_folder=template_folder,
static_folder=static_folder,
url_prefix="/webmanager",
description="context for web manager urls")
self.web.routes.register("/", ["GET"], self.__manager_view, context="webmanager",
name="manager_view", description="Entry-Page for the webmanager")
def __manager_view(self, my_object):
return self.web.render("manager.html", me=my_object)
Based on Flask-Security
Documentation: groundwork-users.readthedocs.io
Documentation: groundwork-validation.readthedocs.io
Validates interactions with files, folders, databases or applications
Validations can be based on hashes or expected content of return values
Developed to support ISO 26262
Documentation: groundwork-spreadsheets.readthedocs.io
Reads and validates Excel documents and provides its data as easy accessible dictionary
... and see you on groundwork’s issue tracker :)
github | github.com/useblocks/groundwork |
One-pager | groundwork.useblocks.com |
Technical documentation | groundwork.rtfd.io |
Tutorial | useblocks.github.io/groundwork-tutorial |
This presentation | groundwork-presentation.rtfd.io |