groundworkΒΆ

_images/gw_slogan.png
Press s to open speaker notes view or ESC for slides overview.
For well phrased, technical details please visit http://groundwork.rtfd.io

Presentation can be freely used for meetups and co.

Python Plugin Framework

Designed for company internal projects and tools

Focused on fast development and high functional quality

How groundwork supports you

Clean, on reusability focused architecture

Batteries included for common problems

Extensive runtime information

Helpful groundwork extensions

Clean architecture, reusable components

Stop starting from scratch

How groundwork structures your code

Application Bundles plugins to an usable function set
Plugins Provides use case specific functions
Patterns Provides technical interfaces

Example:

_images/example_plantuml.png

Example code

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 code

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 code

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!")

Batteries included

Common solutions for common problems

Command line interface

Signals and Receivers

Threads

Shared objects

Recipes

Documents

Command line interface

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

Signals and Receivers

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

Threads

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

Shared objects

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

Recipes

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

Documents

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

Extensive runtime information

Know who did what in your application

groundwork forces developers during registration of objects to provide:

  • A meaningful name
  • A description

groundwork stores also:

  • plugin name
  • function name

Retrieving runtime information

In code

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))

Retrieving runtime information

on cli

With loaded plugin GwDocumentsInfo

>>> my_app doc
_images/runtime_cli_doc.png

Retrieving runtime information

in browser

Using GwWebManager of groundwork-web package

_images/webmanager_overview.png _images/webmanager_plugin.png

groundwork extensions

Some examples

groundwork-database

groundwork-web

groundwork-users

groundwork-validation

groundwork-spreadsheets

groundwork-database

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")

groundwork-web

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)

groundwork-users

Based on Flask-Security

Documentation: groundwork-users.readthedocs.io

_images/web_user_example.png

groundwork-validation

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

groundwork-spreadsheets

Documentation: groundwork-spreadsheets.readthedocs.io

Reads and validates Excel documents and provides its data as easy accessible dictionary

Thanks for your attention ...

... 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