Skip to content

peterpeter5/horst

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HORST

(WIP: some of the described features here might not have been implemented. Currently only linux is supported (espacially tested. This might change in the future)

package-development made simple

Lets start with some code and a maybe-timeline of a package-development-process.

  1. Have an idea and an name -> develop horst2

  2. make a folder called horst2 and inside start a package horst2: -> horst2 | - horst2 | - init.py

  3. make your first build.py

from horst import *
Horst(__file__)
  1. open a commandline
horst2 $ horst
>>>
Options:
  -d, --dry      DryRun nothing will be executed
  -v, --verbose  Output everything to cli
  --help         Show this message and exit.

Commands:
  env:create
  env:update
  test
  debug
  1. Create a new virutalenv by:
$ horst env:create

Now you have a new virtualenv in your main-folder called ".env"

  1. write some code and tests
  2. run all test:
horst test
  1. devlop some more code and build your package as a wheel
horst build

this command will run first your tests and then build your package as wheel

  1. release your build
horst release

this command will: - run a build (with your tests in front) - copy your tests to a new temporary directory. - make a new virtualenv with only the test-dependencies - install your package in environment - run all tests in this environment

This should check if your forgot to specify a dependency or to include some data as package_data

what it is

horst is a build-automation-system. It aims to help python-developers to get an more easy experience when it comes to building a package and releasing it. For that horst tries to be as simple as possible and does not invent anything new. Because of this horst depends on many great libraries like: - pytest + plugins - bumpversion - flake8 - virtualenv - ...

Horst is extensible and can be used as a task-runner, BUT there are better projects out there, which mostly focus on this part and do this job with a smaller dependency-footprint. Said this, the biggest difference between HORST and others, is that it tries to come with batteries-fully-included.

what it is NOT

  1. small footprint: you NEVER want to have Horst as a production dependency! There are to many downstream dependencies from Horst

  2. a new build-system: unlike conda or others, the main goal of horst is to provide a good "standard" - process for developing and releasing packages. Horst simply orchestreates other "standard" - tools

  3. The one true way to do it: At the moment Horst simply refelects what I believe is a good process. You may not agree with me, thats fine. Horst tries to rely on as many "pythonic" - conventions as possible, but it is opinionated. There are ways to change Horst behaviour, but sometimes it may be easier to stick to the Horst-way

Core-Conceptes

build.py

The build.py describes your package / process. This file should be located right next to a potential setup.py (although the setup.py will be generated by horst). Invoking the horst commandline, horst will look for a build.py file and evaluate it. You can use arbitrary python code in this file. A more advanced build.py file looks like this:

from horst import *
Horst(__file__)

Horst(__file__)


dependencies(
    install=["click", "jinja2", "bumpversion", "pytest-cov", "pytest", "virtualenv"],
    test=[],
    build=[],
    environment=virtualenv(
        {
            ".env": {'python': "python3", 'main': True},
        }
    )
)


package(
    name="horst",
    version=bumpversion(),
    description="Horst is a simple build-automation-tool for python packages",
    url=from_git_config("origin"),
)


test(
    unittest=pytest(
        exclude=[marked_as("slow")],
        include=[],
        report=junit(path=path.join(".testresults", "results.xml"), prefix=""),
        coverage=pytest_coverage(
            report=["html", "term"],
        )
    ),

this will give you a complete and custom setup for building and deploying your package

configuration vs. execution

all functions should configure a effect / task on a stage. all registered conf-function will be executed during start-up (everytime)

A configuration must be bound to a stage via decorator

@root.config(test)
def test_configuration(folder=None):
    # configuration phase
    yield folder, None
    # creation phase
    _creator_execute_tests_effect(folder)

the configuration - function is divided (by the yield-statement) in two phases. during the configuration-phase the function arguments, or some side-effects which are needed for the next phase are evaluated. This data is then yielded back to horst as a global configuration which can be queried by all other stages / routes. The second return value is either None or a description of routes this stage should depend on (more on this later). After the configuration is yielded, the function will return and horst garantees that all other config-functions will have yielded their data, which can then be queried. After the yield you should call all registered routes which you want to configure and make available at the cli.

Stage

A stage a container which can be composed with other Stages and can contain several effects. These effects will be run by horst duiring execution. To add some effects to a Stage use a decorator

dummy = Stage("dummy")
@root.register(dummy)
def config_function():
    return []

Route

Stages can be composed by using the / - protocol

route = Stage("begin") / Stage("middle") / Stage("end")

@root.register(route)
def configure_last_stage_in_route():
    return []

The code above would configure the last stage in the route. Horst will always execute routes. For that, horst will iterate over every stage in the route and execute all tasks / effects of this single-stage (TODO for the future: in parallel). Stages will be executed in way they are composed:

a = Stage("a")
b = Stage("b")
c = Stage("c")

@root.register(a)  # not executable because a is just a stage
def print_a():
   return Print("a - stage")

@root.register(a/b)  # executable
def print_b():
   return Print("a:b- route")

@root.register(a/b/c):
def print_c():
   return Print("a:b:c - route. 'a' and 'a:b' were executed before")

About

a simple python build-automation-tool

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages