Ballparker Types

Define the main types and constants of this package.

To create instances of Task, it’s recommended to use the DSL in ballparker.dsl.

The Task Type

class ballparker.types.Task(description, size, subtasks=NOTHING)[source]

A task with a description and a size or sub-tasks.

description: str

A description of this task/grouping.

size: Union[ballparker.types.TShirtSizes, int, float]

The inherent size of this task.

Only specified with a valid value for leaf (non-grouping) tasks.

subtasks: List[ballparker.types.Task]

A list of sub-tasks for this task.

Only tasks with no inherent size can have a non-empty list of subtasks.

as_markdown(format_task=None, size_first=False, tshirt_sizes=False, skip_levels=None, size_levels=<_AllLevelsSentinel.ALL_LEVELS: 0>, max_levels=<_AllLevelsSentinel.ALL_LEVELS: 0>)[source]

Get this task (and any sub-tasks) as a Markdown document.

Parameters
  • format_task (Optional[Callable[[Task], str]]) – An optional function taking a task and returning a string.

  • size_first (bool) – Whether size should come before the description instead of after. Only applied if format_task is None.

  • tshirt_sizes (bool) – Whether sizes should be shown in “T-shirt sizes” (see TShirtSizes), where available, instead of their story-point equivalent.

  • skip_levels (Optional[int]) – How many levels of the tree to skip. Most common values are 0 (no levels) and 1 (skip the top level, often a “project” grouping).

  • size_levels (Union[int, _AllLevelsSentinel]) – How many levels of the tree should have their sizes annotated. The sentinel values ALL_LEVELS means “all levels” (default). Other values may be useful when processing output for supplying to customers, for example, to remove the very fine-grained estimates at tree leaves.

  • max_levels (Union[int, _AllLevelsSentinel]) – The maximum levels of tasks, starting at the root project, that should be traversed and output. Note that if skip_levels is not 0, the number of levels actually output is less (max_levels - skip_levels). The sentinel values ALL_LEVELS means “all levels” (default). Other values may be useful when processing output for supplying to customers, for example, to remove the very fine-grained tasks at tree leaves.

Return type

str

Returns

Multi-line text suitable for processing as Markdown.

to_dsl()[source]

Get the equivalent ballpark DSL of this task and any subtasks.

>>> Task.make_leaf("desc").to_dsl()
"('desc')"
>>> Task.make_leaf("desc", TShirtSizes.S).to_dsl()
"('desc', S)"
>>> Task.make_leaf("desc", 1).to_dsl()
"('desc', 1)"
>>> Task.make_grouping("desc", subtasks=[]).to_dsl()
"grouping('desc')"
>>> Task.make_grouping("desc", subtasks=[Task.make_leaf("subtask")]).to_dsl()
"grouping('desc',\n        ('subtask'))"
>>> Task.make_project(subtasks=[]).to_dsl()
'project()'
>>> Task.make_project(subtasks=[Task.make_leaf("subtask")]).to_dsl()
"project(\n    ('subtask'))"
Return type

str

apply_visitor(visitor, parent=None)[source]

Call the visitor with this task and all sub-tasks recursively.

The visitor will get a single positional argument (the task) and a keyword argument parent (which will be None by default at the initial task).

This is a pre-order, depth-first traversal, if you are curious.

See also

ballparker.visitors

Bundled standalone visitors.

Return type

None

property size_string

Get the size of this task or its recursive sub-tasks.

Same as story_points_string for groupings. For leaf tasks, this returns the t-shirt size estimate instead of the numerical story points, where available.

>>> Task.make_leaf("desc").size_string
'UNKNOWN'
>>> Task.make_leaf("desc", TShirtSizes.S).size_string
'S'
>>> Task.make_leaf("desc", 1).size_string
'1'
>>> Task.make_grouping("desc", subtasks=[]).size_string
'?'
>>> Task.make_grouping("desc", subtasks=[Task.make_leaf("subtask")]).size_string
'?'
>>> Task.make_project(subtasks=[]).size_string
'?'
>>> Task.make_project(subtasks=[Task.make_leaf("subtask", TShirtSizes.S)]).size_string
'1'
Return type

str

property story_points_string

Get a string representation of the numerical story points for this task (or its sub-tasks, recursively).

String representation allows returning "?" if no size was provided, or ">" and some value if one or more sub-tasks has unknown size.

>>> Task.make_leaf("desc").story_points_string
'?'
>>> Task.make_leaf("desc", TShirtSizes.S).story_points_string
'1'
>>> Task.make_leaf("desc", 1).story_points_string
'1.0'
>>> Task.make_grouping("desc", subtasks=[]).story_points_string
'?'
>>> Task.make_grouping("desc", subtasks=[Task.make_leaf("subtask")]).story_points_string
'?'
>>> Task.make_grouping("desc", subtasks=[
...     Task.make_leaf("subtask"),
...     Task.make_leaf("subtask 2", TShirtSizes.S)]).story_points_string
'> 1'
>>> Task.make_project(subtasks=[]).story_points_string
'?'
>>> Task.make_project(subtasks=[Task.make_leaf("subtask", TShirtSizes.S)]).story_points_string
'1'
>>> from ballparker.dsl import *
>>> make_task("desc").story_points_string
'?'
>>> make_task(("desc", S)).story_points_string
'1'
>>> make_task(("desc", 1)).story_points_string
'1.0'
>>> grouping("desc").story_points_string
'?'
>>> grouping("desc",
...         ("subtask")).story_points_string
'?'
>>> grouping("desc",
...         ("subtask"),
...         ("subtask 2", S)).story_points_string
'> 1'
>>> project().story_points_string
'?'
>>> project(
...     ("subtask", S)).story_points_string
'1'
Return type

str

property is_grouping

Check if this task is a grouping.

A grouping has no inherent size, and may have subtasks.

>>> Task.make_leaf("desc").is_grouping
False
>>> Task.make_leaf("desc", TShirtSizes.S).is_grouping
False
>>> Task.make_grouping("desc", subtasks=[]).is_grouping
True
>>> Task.make_grouping("desc", subtasks=[Task.make_leaf("subtask")]).is_grouping
True
>>> Task.make_project(subtasks=[]).is_grouping
True
>>> Task.make_project(subtasks=[Task.make_leaf("subtask")]).is_grouping
True
Return type

bool

property is_project

Return True if this is the project()-created grouping.

>>> Task.make_leaf("desc").is_project
False
>>> Task.make_leaf("desc", TShirtSizes.S).is_project
False
>>> Task.make_grouping("desc", subtasks=[]).is_project
False
>>> Task.make_grouping("desc", subtasks=[Task.make_leaf("subtask")]).is_project
False
>>> Task.make_project(subtasks=[]).is_project
True
>>> Task.make_project(subtasks=[Task.make_leaf("subtask")]).is_project
True
Return type

bool

property has_unknowns

Check if this task or any sub-tasks are missing a size estimate.

Return type

bool

property known_story_points

Get the number of known story points for this task or its sub-tasks.

Note that this is usually not as useful for display as story_points_string, since that incorporates the uncertainty of tasks with unknown size.

Return type

Optional[float]

classmethod make_leaf(description, size=<TShirtSizes.UNKNOWN: None>)[source]

Create a leaf (non-grouping) task.

Typically invoked by passing additional arguments to the grouping() or project() DSL functions.

Return type

Task

classmethod make_grouping(description, subtasks)[source]

Create a “grouping” non-leaf task.

Typically invoked by the grouping() DSL function.

Return type

Task

classmethod make_project(subtasks)[source]

Create a “project” root task.

Typically invoked by the project() DSL function.

Return type

Task

Constants

class ballparker.types.TShirtSizes(value)[source]

So-called “T-shirt size” estimation sizes.

This is a binning/quantization method of estimation: it is intentionally somewhat coarse-grained, to provide fewer choices and thus easier estimation. It is based on US T-shirt sizes: rather than a number, there is just extra-small, small, medium, large, extra-large, etc. Those five basic ones are the ones supported by Ballparker. Rather than directly estimating points (days of work), you estimate the kind of work or size category instead.

Each size is associated with a number of story points as a starting place, though the various ways to make a Task do accept numbers as sizes in addition to TShirtSizes if you want to fine-tune.

These are all imported directly by name into ballparker.dsl for ease of use.

DONE = 0

Use this as the size when you’re tracking an ongoing project with ballparker, and a task is completed.

XS = 0.5

Smallest task possible - about half a day.

S = 1

Common task size with some interaction (may have to talk to someone, etc.) - about a day.

M = 3

Common task size, taking about half a week.

L = 5

Task size of “half a sprint” (about 1 week).

You will want to split this into finer-grained tasks before finalizing the ballpark.

XL = 10

Largest task size, an entire sprint (about two weeks).

You will want to split this into finer-grained tasks before finalizing the ballpark.

UNKNOWN = None

This is the default task size if unspecified.

Adds “uncertainty” to the ballpark (doesn’t add any points to parent tasks, but does turn them into an inequality).

GROUPING = -1

This is a sentinel value used in groupings, not for manual use.

It indicates this task has no inherent size of its own, but should instead sum up all sub-task sizes.

ballparker.types.ALL_LEVELS = <_AllLevelsSentinel.ALL_LEVELS: 0>

Singleton sentinel value to pass to Task.as_markdown() as a level number.

If this, instead of a number, is passed as size_levels (the default), the size_levels value will be considered to be larger than all levels in the tree, and thus sizes will be output for all task levels.

If this, instead of a number, is passed as max_levels, the max_levels value will be considered to be larger than all levels in the tree, and thus all task levels will be traversed for output.