borgcube.core package

borgcube.core.hookspec module


Called after application is configured and all plugins were discovered.

process identifies the process and may be None. Otherwise it can be one of:

  • “borgcubed”
  • “proxy”
  • “manage” (this may include running the web frontend)
borgcube.core.hookspec.borgcube_job_blocked(job, blocking_jobs)[source]

Called to sort out whether job is blocked by any blocking_jobs.

blocking_jobs is a list of jobs that are aimed at the same repository as job and whose state is not in Job.State.STABLE.

Remove any false positives from blocking_jobs that might be there because of special snowflake semantics with your jobs.

If any blocking_jobs remain, job is considered blocked.

borgcube.core.hookspec.borgcube_job_pre_state_update(job, current_state, target_state)[source]

Called before the state of job is updated from current_state (BackupJob.State) to target_state (ditto).

borgcube.core.hookspec.borgcube_job_post_state_update(job, prior_state, current_state)[source]

Called after the state of job was updated from prior_state (BackupJob.State) to current_state.

job is already saved, but the database transaction has not yet completed.

borgcube.core.hookspec.borgcube_job_post_force_state(job, forced_state)[source]

Called after the state of job was forced to forced_state (BackupJob.State).

borgcube.core.hookspec.borgcube_job_failure_cause(job, kind, kwargs)[source]

Called after the failure cause (defined by kind (str) and it’s kwargs) is set for job.


Called when a job was created, but before it will be executed.


Called before job will be deleted.


Called after archive was added (core.models.Archive).


Called before archive (core.models.Archive) will be deleted.

borgcube.core.models module

class borgcube.core.models.NumberTree[source]

Bases: BTrees.LOBTree.LOBTree

insert(key, value) → 0 or 1[source]

Add an item if the key is not already used. Return 1 if the item was added, or 0 otherwise.


Yield values in descending (high to low) key order..

class borgcube.core.models.PersistentDefaultDict(*args, factory)[source]

Bases: persistent.mapping.PersistentMapping

class borgcube.core.models.StringObjectID[source]

Bases: object

class borgcube.core.models.Updateable[source]

Bases: object

class borgcube.core.models.Volatility[source]

Bases: object

borgcube.core.models.evolve(from_version=1, to_version=None)[source]
class borgcube.core.models.Evolvable[source]

Bases: persistent.Persistent, borgcube.core.models.StringObjectID, borgcube.core.models.Updateable

Main class for persistent data in BorgCube.

This provides a means for online data migration (“evolution”). For this both objects and classes are versioned through the version attribute. When an object is written to the database the current version is stored along with it (via __getstate__), while retrieving an object may evolve it to the current version of the class.

This is best illustrated with a simple example:

class Dog(Evolvable):
    def __init__(self):

Fair enough. We acquire some dogs, but notice we forgot a few things, which we want to add:

class Dog(Evolvable):
    def __init__(self, good_boy, food_bowl):
        self.good_boy = good_boy
        self.food_bowl = food_bowl

New code will expect these attributes to be available, so it would fail with older dogs. We fix this through evolution:

class Dog(Evolvable):
    version = 2

    @evolve(from_version=1, to_version=2)
    def added_important_things(self):
        self.good_boy = True
        self.food_bowl = FoodBowl.find_free_bowl_or_create_one()

    def __init__(self, good_boy, food_bowl):
        self.good_boy = good_boy
        self.food_bowl = food_bowl

If now an old dog (version=1) is loaded, the system will notice that and run added_important_things, which will perform the changes needed to match version=2 dogs.

Note that this is subject to the regular transaction rules, so when, after loading old objects, no commit happens, the in-database data isn’t updated – the evolution would happen again next time they are loaded.

You can also provide shortcuts (or leaps), if there is a better upgrade path between certain versions, like so:

class Dog(Evolvable):
    version = 7

    @evolve(from_version=2, to_version=7):
    def direct_upgrade_2to7(self):

    @evolve(from_version=2, to_version=3):
    def upgrade_2to3(self):

Note that evolution isn’t a SAT-solver, but works in a short-circuit way; in each step the largest leap is selected. Hidden paths are not considered.

version = 1
class borgcube.core.models.DataRoot[source]

Bases: borgcube.core.models.Evolvable

The DataRoot has the following attributes:

  • repositories – a PersistentList of Repository instances.
  • archives – an OOBTree mapping hex archive IDs to Archive instances.
  • clients – an OOBTree mapping host names to Client instances.
  • jobs – an OOBTree mapping TODO to Job instances.
  • jobs_by_state – an OOBTree mapping job states to trees of Job instances.
  • schedules – a PersistentList of Schedule instances.
  • ext – a PersistentDict of extension data (see plugin_data, do not use directly).
version = 6

Return an instance generated (at some point in time) by factory.

This should be used for storing plugin data, eg.:

class AwesomePluginData(Evolvable):
    name = 'awesome-plugin'

    def __init__(self):
        self.some_data = OOBTree()


def show_some_data(request):
    # You might want to just put this in a separate helper (below)
    plugdat = data_root().plugin_data(AwesomePluginData)
    return TemplateResponse(...)

# A sample helper
def plugin_root():
    return data_root().plugin_data(AwesomePluginData)

A good name would be the entrypoint name of your plugin, or it’s root module/package name.

The name attribute on factory is not mandatory. If it is not present the qualified class name is used instead (eg.

class borgcube.core.models.Repository(name, url, description='', repository_id='', remote_borg='borg')[source]

Bases: borgcube.core.models.Evolvable

version = 2
static oid_get(oid)[source]
class Form(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None)[source]

Bases: django.forms.forms.Form

name = django.forms.CharField

description = django.forms.CharField

url = django.forms.CharField

repository_id = django.forms.CharField

remote_borg = django.forms.CharField

class ChoiceField(**kwargs)[source]

Bases: django.forms.fields.ChoiceField

static get_choices()[source]

Validates the given value and returns its “cleaned” value as an appropriate Python object.

Raises ValidationError for any errors.

class borgcube.core.models.Archive(id, repository, name, client=None, job=None, comment='', nfiles=0, original_size=0, compressed_size=0, deduplicated_size=0, duration=datetime.timedelta(0), timestamp=None, timestamp_end=None)[source]

Bases: borgcube.core.models.Evolvable

version = 2
delete(manifest, stats, cache)[source]
class borgcube.core.models.RshClientConnection(remote, rsh='ssh', rsh_options=None, ssh_identity_file=None, remote_borg='borg', remote_cache_dir=None)[source]

Bases: borgcube.core.models.Evolvable

remote = ''
rsh = 'ssh'
rsh_options = None
ssh_identity_file = None
remote_borg = 'borg'
remote_cache_dir = None
class Form(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None)[source]

Bases: django.forms.forms.Form

remote = django.forms.CharField

rsh = django.forms.CharField

rsh_options = django.forms.CharField

ssh_identity_file = django.forms.CharField

remote_borg = django.forms.CharField

remote_cache_dir = django.forms.CharField

prefix = 'connection'
class borgcube.core.models.Client(hostname, description='', connection=None)[source]

Bases: borgcube.core.models.Evolvable

version = 2
class Form(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None)[source]

Bases: django.forms.forms.Form

hostname = django.forms.CharField

description = django.forms.CharField

class borgcube.core.models.s[source]

Bases: str

class borgcube.core.models.JobExecutor[source]

Bases: object

name = 'job-executor'
classmethod can_run(job)[source]
classmethod prefork(job)[source]
classmethod run(job)[source]
class borgcube.core.models.Job(repository=None)[source]

Bases: borgcube.core.models.Evolvable

Core job model.

A job is some kind of task that is run by borgcubed. Normally these are concerned with a repository, but if that’s not the case - the field is nullable.

Steps to implement a Job class:

  1. Derive from this
  2. Define additional states, if necessary, by deriving the State class in your model from Job.State
  3. borgcubed needs to know how to run the job, therefore set executor to your JobExecutor subclass.
  4. Relevant hooks: borgcube_job_blocked, borgcubed_job_exit.
short_name = 'job'

alias of JobExecutor

class State[source]

Bases: object

job_created = 'job_created'
done = 'done'
failed = 'failed'
cancelled = 'cancelled'
STABLE = {'cancelled', 'done', 'failed', 'job_created'}
classmethod verbose_name(name)[source]
update_state(previous, to)[source]
set_failure_cause(kind, **kwargs)[source]
delete(using=None, keep_parents=False)[source]
class borgcube.core.models.TriggerID(trigger, enabled=True, access=(), comment='')[source]

Bases: borgcube.core.models.Evolvable

Defined access contexts are:

  • anonymous-web
  • local-cli
class borgcube.core.models.Trigger(target)[source]

Bases: borgcube.core.models.Evolvable

class borgcube.core.models.Schedule(name, recurrence, recurrence_enabled=True, description='')[source]

Bases: borgcube.core.models.Evolvable

version = 4
class Form(data, *args, **kwargs)[source]

Bases: django.forms.forms.Form

name = django.forms.CharField

description = django.forms.CharField

recurrence_enabled = django.forms.BooleanField

recurrence_start = django.forms.DateTimeField

recurrence = recurrence.forms.RecurrenceField


Hook for doing any extra form-wide cleaning after Field.clean() has been called on every field. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field named ‘__all__’.

class borgcube.core.models.DottedPath[source]

Bases: object

classmethod dotted_path()[source]
class borgcube.core.models.ScheduledAction(schedule)[source]

Bases: borgcube.core.models.Evolvable, borgcube.core.models.DottedPath

Implement this to add schedulable actions to the scheduler.

Make sure that your implementation is imported, since these are implicitly discovered subclasses.

name = ''
class Form(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None)[source]

Bases: django.forms.forms.Form

The form that should be presented for adding/modifying this action.

This can be a ModelForm or modify the DB; the transaction is managed for you, and no additional transaction management should be needed.

The usual form rules apply, however, note that .cleaned_data must be JSON serializable if .is_valid() returns true. This data will be used for instanciating the action class (py_args).

.save() will be called with no arguments regardless of type, if it exists.

classmethod form(*args, **kwargs)[source]
classmethod get_class(dotted_path)[source]