Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions docs/cells.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
### Cells ###

Cells represents an abstraction of the web, tightly coupled with object database, which allows you to create "reactive" interface in python....


### Preamble ###

In order to get ourselves up and running we'll need a few things:

* Object Database installed (see the [installation docs](../INSTALLATION.md) for more details)
* an object_database engine service instance (see [here](./object_engine.md) for more information)
* build the bundle for the services landing page
* `cd object_database/object_database/web/content`
* create a node environment and source it
* run `npm install && npm run build`
* a configured and installed web service instance, which will be responsible for building and serving the web application
* and an installed instance of the service we'd like to run

After this we can start up our web server and service.

Lets walk through it (as in the other examples we'll use "TOKEN" for our special token):

In a python virtual environment boot up Object Database engine like so:
```
object_database_service_manager \
localhost \
localhost \
8000 \
Master \
--run_db \
--service-token TOKEN \
--source ./odb/source \
--storage ./odb/storage
```

In another python virtual environment instance run the following:
```
# export our special Object Database token
export ODB_AUTH_TOKEN=TOKEN

# install the ActiveWebService
object_database_service_config install \
--class object_database.web.ActiveWebService.ActiveWebService \
--placement Master

# configure ActiveWebService
object_database_service_config configure ActiveWebService \
--port 8080 --hostname localhost --internal-port 8081

# check to make sure it is listed
object_database_service_config list

# start it up
object_database_service_config start ActiveWebService

# check to see that it is running
object_database_service_config instances
```

NOTE: you can always open [http://localhost:8080/](http://localhost:8080/) in your browser to see the running services and click to see what they are. Also, if you get tired of running the above commands, there is a small bash script found [here](./examples/aws_start.sh).

#### Running a boring web app ####

Run the following in your virtual environment:

```
object_database_service_config install --class object_database.service_manager.ServiceBase.ServiceBase --placement Master
# check to see it is installed
object_database_service_config list
# start
object_database_service_config start ServiceBase
# check to see it is running
object_database_service_config instances
```

(NOTE: you might need to change the paths to the [ServiceBase](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py) file depending on the directory you are running this from.)

If you head to [http://localhost:8080/services/ServiceBase](http://localhost:8080/services/ServiceBase) you will see our really boring service. The simple text holder card is what [ServiceBase.serviceDisplay](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/service_manager/ServiceBase.py#L67) returns. In the next example, we'll subclass `ServiceBase` and change this method to do something more interesting.


#### Running a more interesting web app ####

Take a look at [cells.py]('./examples/cells.py'). You'll see we made a subclass of `ServiceBase` and overrode its `.serviceDisplay` method. There is card with a header and some buttons which all route you to the corresponding URI (in our case the list of services we have running), plus a little bit of styling.

Lets install our more interesting app like above:
```
object_database_service_config install --class docs.examples.cells.SomethingMoreInteresting --placement Master
object_database_service_config start SomethingMoreInteresting
```

Note: when you make changes to `SomethingMoreInteresting` you need to reinstall it, with the above command. If you see a message like `Cannot set codebase of locked service 'SomethingMoreInteresting'` then click the "Locked" icon in [http://localhost:8080/services]([http://localhost:8080/services) to unlock it, then reinstall.


You can learn more about cells by perusing the [cells](https://github.com/APrioriInvestments/object_database/tree/dev/object_database/web/cells) directory or taking a look at the [cells test example](https://github.com/APrioriInvestments/object_database/blob/dev/object_database/web/CellsTestService.py#L63)

#### Running an ODB web app ####

But before we wrap up, we should really build a cells examples which works with Object Database, since that's the main point here.

I am going to skip over details of how ODB works. Please here [here](https://github.com/APrioriInvestments/object_database/blob/dev/docs/object_engine.md) for an introduction.

We'll be building an `AnODBService` which you can find [here](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py). You'll see we need to define
* a [schema](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L12)
* how our app will [interact with ODB](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L23)
* and the [UI](https://github.com/APrioriInvestments/object_database/blob/daniel-examples/docs/examples/cells_odb.py#L40) for the app itself.

The app will send and recieve messages from the database, and update the UI which consists largely of a Panel and Table cell. The key departure here from the previous examples is the lambda functions passed to the cells. Instead of returning something like a string (which then tells the service to route to the correponding URI), these interact with the DB via the `Message` class.

As before we'll need to install and start the service:
Lets install our more interesting app like above:
```
object_database_service_config install --class docs.examples.cells_db.AnODBService --placement Master
object_database_service_config start AnODBService
```

We'll learn more about cells and how to develop them in the upcoming `cells_dev.md` doc.
Empty file added docs/examples/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions docs/examples/aws_start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Active Web Service startup script

# export our special Object Database token
export ODB_AUTH_TOKEN=TOKEN

# install the ActiveWebService
object_database_service_config install \
--class object_database.web.ActiveWebService.ActiveWebService \
--placement Master

# configure ActiveWebService
object_database_service_config configure ActiveWebService \
--port 8080 --hostname localhost --internal-port 8081

# check to make sure it is listed
object_database_service_config list

# start it up
object_database_service_config start ActiveWebService

# check to see that it is running
object_database_service_config instances
20 changes: 20 additions & 0 deletions docs/examples/cells.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import object_database.web.cells as cells
from object_database import ServiceBase


class SomethingMoreInteresting(ServiceBase):
def initialize(self):
self.buttonName = "click me"
return

@staticmethod
def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None):
return cells.Card(
cells.Panel(
cells.Button("Reload", lambda: "") +
cells.Button("Service Base", lambda: "ServiceBase") +
cells.Button("Active Web Service", lambda: "ActiveWebService")
),
header="This is a 'card' cell with some buttons",
padding="10px"
)
81 changes: 81 additions & 0 deletions docs/examples/cells_odb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from object_database import Schema, ServiceBase
import object_database.web.cells as cells
import time
import random

# define a 'schema', which is a collection of classes we can subscribe to as a group
schema = Schema("cells_obd")

# define a type of entry in odb. We'll have one instance of this class for each
# message in the database
@schema.define
class Message:
timestamp = float
message = str
lifetime = float


class AnODBService(ServiceBase):
def initialize(self):
# make sure we're subscribed to all objects in our schema.
self.db.subscribeToSchema(schema)

def doWork(self, shouldStop):
# this is the main entrypoint for the service - it gets to do work here.
while not shouldStop.is_set():
#wake up every 100ms and look at the objects in the ODB.
time.sleep(.1)

# delete any messages more than 10 seconds old
with self.db.transaction():
# get all the messages
messages = Message.lookupAll()

for m in messages:
if m.timestamp < time.time() - m.lifetime:
# this will actually delete the object from the ODB.
m.delete()

@staticmethod
def serviceDisplay(serviceObject, instance=None, objType=None, queryArgs=None):
# make sure cells has loaded these classes in the database and subscribed
# to all the objects.
cells.ensureSubscribedSchema(schema)

def newMessage():
# calling the constructor creates a new message object. Even though we
# orphan it immediately, we can always get it back by calling
# Message.lookupAll()
# because ODB objects have an explicit lifetime (they have to be destroyed)
Message(timestamp=time.time(), message=editBox.currentText.get(), lifetime=20)

# reset our edit box so we can type again
editBox.currentText.set("")

# define an 'edit box' cell. The user can type into this.
editBox = cells.SingleLineTextBox(onEnter=lambda newText: newMessage())

return cells.Panel(
editBox >> cells.Button(
"New Message",
newMessage
)
) + (
cells.Table(
colFun=lambda: ['timestamp', 'lifetime', 'message'],
rowFun=lambda: sorted(Message.lookupAll(), key=lambda m: -m.timestamp),
headerFun=lambda x: x,
rendererFun=lambda m, col: cells.Subscribed(
lambda:
cells.Timestamp(m.timestamp) if col == 'timestamp' else
m.message if col == 'message' else
cells.Dropdown(
m.lifetime,
[1, 2, 5, 10, 20, 60, 300],
lambda val: setattr(m, 'lifetime', val)
)
),
maxRowsPerPage=100,
fillHeight=True
)
)
7 changes: 3 additions & 4 deletions docs/examples/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ class Message:
timestamp = float
message = str


db = connect('localhost', 8000, 'TOKEN')

db.subscribeToSchema(schema)

ts = 0
while True:
with db.view():
Message(timestamp=time.time(),
message='message_5')
with db.transaction():
Message(timestamp=time.time(), message="message_5")
messages = Message.lookupAll()
for m in messages:
if m.timestamp > ts:
Expand Down
11 changes: 7 additions & 4 deletions quickstart/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
# Using 3.8 because typed_python doesn't seem to
# compile with 3.9 (as of 2022-04-27)
FROM python:3.8-slim as builder
FROM python:3.9-slim as builder

ADD . /opt/object_database
ADD ./object_database /opt/object_database
ADD ./typed_python /opt/typed_python

RUN apt update -y -qq \
&& apt upgrade -y -qq \
&& apt-get update \
&& apt install -y -qq curl libssl-dev build-essential git npm \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN pip install --upgrade pip \
&& pip install typed_python \
&& cd /opt/typed_python \
&& pip install -e . \
&& cd /opt/object_database \
&& pip install -e .

RUN cd /opt/object_database/object_database/web/content \
&& npm install \
&& npm run build

CMD ["object_database_webtest"]
CMD ["object_database_webtest"]