Starting Off¶
What is a Hive¶
A hivemind project, colloquially known as a “hive”, is an organizational structure for all components of the hivemind universe. With a number of benefits over one-off files, the hive provides benefits for development and production environments alike. All while keeping the handcuffs off for when programmers want to do the awesome hackery that Python is known for.
Tip
Think of a city. A bustling of people and information. When walking through the streets, you don’t care about what most people or things are doing. You observe and relay information to people who are listening all while potentially listening in on others when the time is right.
If you’re familiar with the Django project, this should feel very familiar as many of the concepts were modeled after it’s project structure.
hm
Command¶
The CLI interface for hivemind. This comes with utilities to help initialize the project, new nodes, and even run in development and production environments. Below is the current help info of the hm
command
usage: hm [-h] {new,create_node,dev} ...
Hivemind utility belt with quick access tools for running task nodes, starting
new projects, and more.
positional arguments:
{new,create_node,dev}
Commands that can be run
optional arguments:
-h, --help show this help message and exit
Create a Project¶
Let’s go to wherever we normally keep our code repositories and make a new directory. To follow with our “hive as a city” concept, we’ll call it gotham
.
~$> cd ~/repos
~$> mkdir gotham
~$> cd gotham
Once inside, we use the hm new
command to set up a fresh hive.
usage: hm new [-h] [-v] [--dir DIR] name
Set up a new hive!
positional arguments:
name The name of the new project
optional arguments:
-h, --help show this help message and exit
-v, --verbose Provide additional feedback
--dir DIR The directory to place it in (cwd by default)
~$> hm new gotham
New hive at: /home/jdoe/repos/gotham
If you look around there, you’ll see something akin to the following structure.
gotham/
`- gotham/
`- config/
`- __init__.py
`- hive.py
`- nodes/
`- __init__.py
`- root/
`- __init__.py
`- static/
`- templates/
`- __init__.py
config/hive.py
: The settings file for a given hivenodes/
: Where our nodes will go. More on this soon.root/__init__.py
: The controller class goes here. The controller(s) is/are the heart of the hive. By default, it’s just a transparent wrapper around theRootController
class.static/
: For web-side utilities, static files that we’ll serve outtemplates/
:TaskYaml
templates for plugin-style task fun! We’ll get more into this later on too.
Tip
While not specifically required, the additional top level directory is to contain all the python parts to one location to avoid crowding the root or your new repo.
With just that one command, we have a functional web server that can be navigated to.
~$> cd gotham
~$> hm dev
The dev
command should boot up your hive and start listening.
Tip
While this will be described better in the logging documentation, you should be able to find the output log of your hive wherever you’re config/hive.py -> LOG_LOCATION
is set.
Now, simply navigate to http://127.0.0.1:9476
and you should be greeted with a simple (but noteworthy!) page.
A Quick Recap¶
The
hm new
command created a “blank” hive with some defaults.The
hm dev
command starts the hive environmentA basic web server is run and we were able to see the page!
Note
The web server you’re seeing is actually your hive’s RootController
listening and responding to changes in your network. We’ll describe this in greater detail later. This is a vital piece of the puzzle so remember the name!
Create a Node¶
Okay, we have a network. Time to put some nodes on it! Enter the hm create_node
command.
usage: hm create_node [-h] [-v] name
Generate a new node within a hive
positional arguments:
name The name of this node
optional arguments:
-h, --help show this help message and exit
-v, --verbose Provide additional feedback
~$> hm create_node BatSignal
Once run, you should see the following in your hive.
nodes/
`- __init__.py
`- batsignal
`- __init__.py
`- batsignal.py
And batsignal.py
should look something like:
"""
BatSignal node for gotham
"""
from hivemind import _Node
class BatSignal(_Node):
"""
BatSignal Implementation
"""
def services(self) -> None:
"""
Register any default services
:return: None
"""
return
def subscriptions(self) -> None:
"""
Register any default subscriptions
:return: None
"""
return
if __name__ == '__main__': # pragma: no cover
# An initialization command.
BatSignal.exec_(
name="mynode",
logging='verbose'
)
That’s a self contained node that can be run but, at the moment, it doesn’t do anything.
Enable The Node¶
If we run hm dev
right now, nothing will have changed. We need to tell the hive to load the BatSignal
by default.
In the nodes/__init__.py
file you should see the following:
# from .batsignal import batsignal
Simply uncommenting that grants access to the components powering your hive. This is a pretty brute force way to (dis|en)able nodes.
Add a Service¶
Tip
What is a Service?
A service is how we communicate observed data to our network. A service executes on it’s own thread and, philosophically, relies on nothing but itself. This doesn’t mean it shouldn’t interact with other data but, at all costs, we avoid synchronous waiting patterns.
With the node enabled, running hm dev
will start up the controller and our node, but until we add services or subscriptions, our node doesn’t serve any purpose. To get the Dark Knight to save us, we’ll need to send him a message whenever a crisis occurs. A perfect situation for a service.
class BatSignal(_Node):
# ...
def services(self) -> None:
"""
Add the bat signal service!
"""
self._bat_signal_service = self.add_service(
name='bat-signal-main',
function=self._bat_signal
)
def _bat_signal(self, service) -> int:
"""
Run the service! By default, this is run on a
loop forever!
:param service: The service instance we created above
:return: int (0 means continue, otherwise abort)
"""
# Send a signal to the big man himself. (JSON compliant payload)
service.send('Batman! We need help!')
# As lowly cityfolk, we can do nothing but wait until
# the next crisis.
service.sleep_for(5.0)
return 0
A few important things in there.
- We defined our first
_Service
with theself.add_service
function. name
: A name for our service (unique to the node class)function
: The callback that gets run in a loop for the rest of the _Node’s life
- We defined our first
Within the callback function, we sent a message through our service to alert anyone that’s listening
- To avoid spamming the controller and subsequent subscriptions to this service, we have the service sleep
We do this with
service.sleep_for
as it has the ability to wake up gracefully when shutting down
Warning
Avoid time.sleep
in a service callback! There’s no benefit over _Serivce.sleep_for
and can
cause your program to stall out for a length of time.
Logs¶
With the service looking good, we can start our development environment with hm dev
. At the moment, nothing new will appear to happen. Our service should be transmitting a signal every 5 seconds, but we don’t have anything to really look at. To see what’s happening at a log level, navigate to where your config/hive.py -> LOG_LOCATION
points you. (This will be different on different platforms).
You should see two logs. One called root.log
and another called batsignal.log
(or whatever you called the node class). The logging utilities within hivemind route to the corresponding log file to keep them from all becoming one giant mess. In the future hm
may gain the ability to merge the log based on the timestamps to help improve with time debugging.
Tip
Verbose To enable verbose output of the nodes, use the -v
flag. This will be reflected in the logs, not on the standard output
~$> hm dev -v
Add a Subscription¶
The Batman is always vigilant to save the day. A subscription, is no different.
Now, we could add a subscription to our BatSignal
node that listens for the "bat-signal-main"
command and responds to the crisis however we can scale the number of Bat Signals and Bat…men(?) to any extent if we separate them. Herein lies one of the cornerstones of hivemind
.
First, let’s create the node to host our subscription.
~$> hm create_node batman
Now, we should have the following:
nodes/
`- __init__.py
`- batman/
`- __init__.py
`- batman.py
`- batsignal/
` - ...
Within the node definition (nodes/batman/batman.py
) we can set up the subscription.
class Batman(_Node):
# ...
def subscriptions(self) -> None:
"""
Stay vigilant caped crusader
"""
self._bat_signal_subscription = self.add_subscription(
name='bat-signal-*',
function=self._go_save_the_day
)
def _go_save_the_day(self, payload) -> int:
"""
Here, we can save the day.
:param payload: The message data sent from a service who's name matches
the subscription name pattern. In this case, anything
matching "bat-signal-*"
"""
if not isinstance(payload, str):
self.log_warning(f'Unknown payload type: {type(payload)}')
return 0
if payload == "Batman! We need help!":
print ('Batman has saved the day!') # To print to our stdout
else:
print ('Oh no! Batman doesn\'t know what to do!')
return 0