- Install, Service Config and Test Run
Steps:
- Preparation
- Register an account on Moobius (you have to have an email and password to use this SDK. Third-party login not supported).
- Create a band with your account and copy the
band_id
(referred below aschannel_id
).
- Install the SDK on your computer/server
- Use
git clone
or download the source code from this repository (and unzip it) - Change current directory to
src/
on your terminal - Run
pip install -e .
(for Mac OS, it could bepip3 install -e .
). This command will install the packagemoobius
to yourPYTHONPATH
.- Note:
-e
means editable mode. With this option your changes to the source code will take effect immediately.
- Note:
- Run a demo (test) service
- Change current directory on your terminal to
projects/demo/
- Copy
projects/service.json
toprojects/demo/
, and edit the copied json file. Fill in youremail
,password
and a list ofchannels
you want to run on. If you have aservice_id
, just fill in the field, otherwise please use"service_id": ""
and the SDK will create a newservice_id
for you and write back toservice.json
for future use.- Note: A channel previously binded to a
service_id
could not be binded again to a differentservice_id
, unless the original service unbinds first.
- Note: A channel previously binded to a
- Run
python main.py test
(For Mac OS, it could bepython3 main.py test
). The config file will automatically update with aservice_id
so that you can just use it next time you start the program. You can see from your browser (after refresh) and expect an actively functional service in your band, that
- A Character named "Wand" (not in the character list) would begin to send "SYNC", "ASYNC", "BOMB" and "SURVIVE" messages in order automatically (after 10 seconds).
- The "Wand" will send "Check in every minute" with a timestamp every minute (as it is).
- Has two Keys ("Do Some Magic" and "Swap Stage"). Something obvious will happen in the band when you click them.
- If you type "moobius" in the text input area in your browser, recipients will receive "Moobius is Great!" instead. You can see this when your intended recipients include yourself, and you can also check this with another account joining the same band (be sure to use different browsers if you try multiple accounts on the same machine).
- If you type other text messages, all your intended recipients would receive your messages. You will hear an "echo" from yourself if your intended recipients include yourself (which is the default option of our Web UI where the intended recipients is "All").
- If you send an image, all your intended recipients EXCEPT YOURSELF would receive the image, even if your intended recipients include yourself (you may already be used to this behavior and did not even think it is a thing!).
- If you send the following commands to Service(∞), the band would respond in some way.
hide
: the keys (buttons) would disappearshow
: the keys would reappearreset
: clear all Mickeys in your character list.
- Next time you can run
python main.py
orpython3 main.py
without thetest
argument. The test messages beginning with "WAND: " won't be sent on start.
Congratulations! Now you have your first Moobius Service.
- Tutorial
- Writing a service can be fun and challenging, and we are trying to write tutorials for it to be easy and smooth. Right now we don't have that kind of detailed documentation, and we suggest you read (and modify) the code of the demo service as a starting point. You can use some of the code and "template" for your own service (for example, the
on_join_channel
andon_leave_channel
methods).
- Service
- To be brief, your own service should be a subclass of
MoobiusService
and you should implement all theon_xxx()
methods defined inMoobiusBasicService
that would be triggered by user-generated events (msg_up
,join_channel
,feature_call
, etc). There are a bunch of helper methods likesend_xxx()
for you to use in your handler methods.
- Database
- A service could be quiet simple, or dazzlingly complicated. You have the power to control every user's experience in your band -- everything they see on the stage and character list, and every consequence of their messages and key clicks. Each user's view could be independent (for example, each user would see different number of Mickeys), so that it is vital to have a database doing this. We have implemented a
MoobiusStorage
class for you to use with minimum extra effort. It magically turns a key-value pair database into a pythondict
that automatically synchronizes with the database under the hood (could be json file or redis service now, but new implementations are welcome!). See below for details.
- Logging
- We use loguru for a colorful, comprehensive logging system. You can see the logs on your console and in the log files. All you need to do is to use functions like
logging.info()
orlogging.error()
instead ofprint()
in your code. For advanced usage, please see the documentation of loguru
- Wand
-
Here comes the most delicious part. The
MoobiusWand
is not only a loader of your service, but a remote controller of your services that allows you to "manually" control your service AFTER it starts. A wand can controll more than one background services (background=True
) and you can callwand.spell()
orawait wand.aspell()
in another (independent) script to use you service as an platform of output (say, you wrote another weather forecast program and notify your bands whenever it seems to start raining besides the original service logic). You just need to implementon_spell()
and write a protocol between your band and the wand. -
If you use background service, please avoid initializing of complex data types (such as a scheduler or an OpenAI client) in
__init__()
, which may cause pickling issues. In most cases, these attributes could be initialized inon_start()
method instead. -
You may need to Press
Ctrl + C
multiple times if you want to stop your service (it could have multiple process plusasyncio
loops and tasks). In some cases you may see a latentKeyboardInterrupt
Error with stacktrace printed even after you already started your service again. That's normal! It's just the aftermath of your lastCtrl + C
(and we are working on fixing this).
- Database Config:
config/db.json
inprojects/<your_project>
. You can put anyname
that is a valid python identifier as the name the attribute of aMoobiusStorage
instance to store your data (in case your program need to restart). Currently two types of configurations are supported: json and redis. For json you need to specifyimplementation: "json"
androot_dir
as your root directory to save the database files. For redis you need to specifyimplementation: "redis"
andpassword
for your redis password (you should have a redis service already running on your machine, of course). See the Database part for more info. Each database can be configured independently. Ifclear
is set, everything in the database would be erased during the initialization of the service. Ifload
is set, the service will load a copy from the database to the memory during initialization.
- Data types supported are built-in types (list, dict, int, str, etc) and all dataclass types defined in
types.py
- PITFALL! Be careful when you modify a mutable object in your database record, the memory wouldn't be updated. You have to reassign it to the band or call
save()
method explicitly. (all abstractions are leaky!)
For example:
self.bands[channel_id].data = {'key': 1} # memory: 1, disk: 1
self.bands[channel_id].data = {'key': 2} # memory: 2, disk: 2
But
self.bands[channel_id].data = {'key': 1} # memory: 1, disk: 1
self.bands[channel_id].data['key'] = 2 # memory: 2, disk: 1
# Not saved!
# The change will not be detected as the id of the dict did not change
Hence you should
self.bands[channel_id].data = {'key': 1} # memory: 1, disk: 1
self.bands[channel_id].data['key'] = 2 # memory: 2, disk: 1 (not saved!)
self.bands[channel_id].data = self.bands[channel_id].data # Weird, right? Know about setState() in React.js?
or, alternatively
self.bands[channel_id].data = {'key': 1} # memory: 1, disk: 1
self.bands[channel_id].data['key'] = 2 # memory: 2, disk: 1 (not saved!)
self.bands[channel_id].save('data') # A more graceful way to do this.
projects/
: Example services. Each subfolder contains a separate service.
src/moobius
: The source code of the moobius
package, which includes:
-
core/
: All core features and logic of a Moobius Serivce.basic_service.py
:MoobiusBasicService
that defines allon_xxx()
triggers andsend_xxx()
helper methods (for you to send payloads through websockets.)service.py
:MoobiusService
that integrates database and high-level commonly used helper methods. It is the base class of a Service. It has a minimal but complete implementation of a fully functional Moobius Service instance (so that it is runnable!), including authentication, automatic heartbeat and a trivial handler to payloads (print and noops). It is highly recommended that your custom class inheritMoobiusService
defined here.- Note: A service instance has
http_api
and a_ws_client
attribute that handles low-level network communications. In most cases it is not necessary to use their methods directly. Also there is ascheduler
attribute that is an instance of class APScheduler after the service starts. There are three jobs already scheduled on start (to handle automatic heartbeat and token refresh), and you can add your new jobs(DO NOT do this in your__init__()
method!)
- Note: A service instance has
storage.py
:MoobiusStorage
that acts as a container of backed-up dictionaries (CachedDict
instances)wand.py
:MoobiusWand
class that handles all the multiprocessing and remote control magic.
-
database/
: The infrastructure ofMoobiusStorage
.database_interface.py
: An abstract class definition of database implementations. As long as the methods defined in the interface are implemented (for a key-value pair), the instance of the concrete class can be used to initialize aCachedDict
instance.null_database.py
: A trivial implementation.json_database.py
: A json file based implementation (simple but useful and intuitive -- you can see the data records easily!)redis_database.py
: A redis implementation (which requires a redis service on your machine). You are welcome to make MySQL, SQLite or other implementations.magical_storage.py
:CachedDict
is a dictionary with a database under the hood, and the changes in the dict will be automatically synchronized to the database.MagicalStorage
is built onCachedDict
, which supports customized on-the-flyCachedDict
containers (put()
) which can automatically load (load=True
) from a database on initialization, or automatically clear the database (clear=True
) to clean out garbage records.
-
network/
: All basic network communicationshttp_api_wrapper.py
: a pure implementation of low-level HTTP APIs.MoobiusService
instances have an attributeself.http_api
of this class.ws_payload_builder.py
: a pure builder of websocket API payloads.ws_client.py
: a websocket client based onwebsockets
that facilitates exception handling, asynchronous event handling and automatic reconnection attempts on error.MoobiusService
instances have an attributeself._ws_client
of this class.
-
types.py
: All relevant dataclasses of a Moobius event (websocket package) payload. -
utils.py
: Basic utilities likeEnhancedJSONEncoder
which can recognize dataclasses.
- Async http and database
- HTTP Data Types refactor
- Further Documentation
- Tutorials and better examples