Skip to content

Commit fa040df

Browse files
authored
For ThingsDB v1.x (#31)
* For compatibility with ThingsDB v1.x
1 parent 034649b commit fa040df

31 files changed

Lines changed: 752 additions & 2068 deletions

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,4 @@ dist/
1313
# Temporary test files
1414
api_test.py
1515
sample*.py
16-
17-
socket_test.py
16+
socket_test.py

README.md

Lines changed: 18 additions & 259 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,19 @@
99
* [Client module](#client-module)
1010
* [Client()](#Client)
1111
* [authenticate](#authenticate)
12-
* [add_event_handler](#add_event_handler)
1312
* [close](#close)
1413
* [connect](#connect)
1514
* [connect_pool](#connect_pool)
1615
* [get_default_scope](#get_default_scope)
1716
* [get_event_loop](#get_event_loop)
17+
* [get_rooms](#get_rooms)
1818
* [is_connected](#is_connected)
1919
* [query](#query)
2020
* [reconnect](#reconnect)
2121
* [run](#run)
2222
* [set_default_scope](#set_default_scope)
23-
* [unwatch](#unwatch)
2423
* [wait_closed](#wait_closed)
25-
* [watch](#watch)
26-
* [Model](#model)
27-
* [Collection](#collection)
28-
* [Thing](#thing)
24+
* [Room](#room)
2925

3026
---------------------------------------
3127

@@ -97,9 +93,9 @@ Initialize a ThingsDB client
9793
When set to `True`, the client will automatically
9894
reconnect when a connection is lost. If set to `False` and the
9995
connection gets lost, one may call the [reconnect()](#reconnect) method to
100-
make a new connection. The auto-reconnect option can act on
101-
node changes and does so automatically if the connected user
102-
has the required `WATCH` privileges on the `@node` scope.
96+
make a new connection. The auto-reconnect option will listen to
97+
node changes and automatically start a reconnect loop if the
98+
*shutting-down* status is received from the node.
10399
Defaults to `True`.
104100
- *ssl (SSLContext or bool, optional)*:
105101
Accepts an ssl.SSLContext for creating a secure connection
@@ -134,21 +130,6 @@ Authenticate a ThingsDB connection.
134130
The timeout may be set to `None` in which case the client will
135131
wait forever on a response. Defaults to 5.
136132

137-
### add_event_handler
138-
139-
```python
140-
Client().add_event_handler(event_handler: Events) -> None
141-
```
142-
143-
Add an event handler.
144-
145-
Event handlers will called in the order they are added.
146-
147-
#### Args
148-
149-
- *event_handler (Events)*:
150-
An instance of Events (see `thingsdb.client.abc.events`).
151-
152133
### close
153134

154135
```python
@@ -272,6 +253,18 @@ Can be used to get the event loop.
272253

273254
The event loop used by the client.
274255

256+
### get_rooms
257+
258+
```python
259+
Client().get_rooms() -> tuple
260+
```
261+
262+
Can be used to get the rooms which are joined by this client.
263+
264+
#### Returns
265+
266+
A `tuple` with unique `Room` instances.
267+
275268
### is_connected
276269

277270
```python
@@ -290,7 +283,6 @@ Client().query(
290283
code: str,
291284
scope: Optional[str] = None,
292285
timeout: Optional[int] = None,
293-
convert_vars: bool = True,
294286
**kwargs: Any
295287
) -> asyncio.Future
296288
```
@@ -311,13 +303,6 @@ Use this method to run `code` in a scope.
311303
Raise a time-out exception if no response is received within X
312304
seconds. If no time-out is given, the client will wait forever.
313305
Defaults to `None`.
314-
- *convert_vars (bool, optional)*:
315-
Only applicable if `**kwargs` are given. If set to `True`, then
316-
the provided `**kwargs` values will be converted so ThingsDB can
317-
understand them. For example, a thing should be given just by
318-
it's ID and with conversion the `#` will be extracted. When
319-
this argument is `False`, the `**kwargs` stay untouched.
320-
Defaults to `True`.
321306
- *\*\*kwargs (any, optional)*:
322307
Can be used to inject variable into the ThingsDB code.
323308

@@ -364,7 +349,6 @@ Client().run(
364349
*args: Optional[Any],
365350
scope: Optional[str] = None,
366351
timeout: Optional[int] = None,
367-
convert_args: bool = True,
368352
**kwargs: Any
369353
) -> asyncio.Future
370354
```
@@ -390,13 +374,6 @@ Use this method to run a stored procedure in a scope.
390374
Raise a time-out exception if no response is received within X
391375
seconds. If no time-out is given, the client will wait forever.
392376
Defaults to `None`.
393-
- *convert_args (bool, optional)*:
394-
Only applicable if `*args` are given. If set to `True`, then
395-
the provided `*args` values will be converted so ThingsDB can
396-
understand them. For example, a thing should be given just by
397-
it's ID and with conversion the `#` will be extracted. When
398-
this argument is `False`, the `*args` stay untouched.
399-
Defaults to `True`.
400377
- *\*\*kwargs (any, optional)*:
401378
Arguments which are injected as the procedure arguments.
402379
Instead of by name, the arguments may also be parsed using
@@ -429,37 +406,6 @@ Can be used to change the default scope which is initially set to `@t`.
429406
Set the default scope. A scope may start with either the `/`
430407
character, or `@`. Examples: `"//stuff"`, `"@:stuff"`, `"/node"`
431408

432-
### unwatch
433-
434-
```python
435-
Client().unwatch(
436-
*ids: int,
437-
scope: Optional[str] = None
438-
) -> asyncio.Future
439-
```
440-
441-
Unsubscribe for changes on given things.
442-
443-
Stop receiving events for the things given by one or more ids. It is
444-
possible that the client receives an event shortly after calling the
445-
unsubscribe method because the event was queued.
446-
447-
#### Args
448-
- *\*ids (int)*:
449-
Thing IDs to unsubscribe. No error is returned in case one of
450-
the given things are not found within the collection or if the
451-
thing was not being watched.
452-
- *scope (str, optional)*:
453-
Unsubscribe for things in this scope. If not specified, the
454-
default scope will be used. Only collection scopes may contain
455-
things so only collection scopes can be used.
456-
See https://docs.thingsdb.net/v0/overview/scopes/ for how to
457-
format a scope.
458-
459-
#### Returns
460-
461-
Future which result will be set to `None` if successful.
462-
463409

464410
### wait_closed
465411

@@ -473,193 +419,6 @@ Can be used after calling the `close()` method to determine when the
473419
connection is actually closed.
474420

475421

476-
### watch
477-
478-
```python
479-
Client().watch(self, *ids: int, scope: Optional[str] = None) -> asyncio.Future
480-
```
481-
482-
Subscribe for changes on given things.
483-
484-
This method accepts one or more thing ids to subscribe to. This
485-
method will simply return None as soon as the subscribe request is
486-
successful handled by ThingsDB. After the response, the client will
487-
receive `INIT` events for all subscribed ids. After that, ThingsDB
488-
will continue to provide the client with `UPDATE` events which contain
489-
changes to the subscribed thing. A `DELETE` event might be received
490-
if, and only if the thing is removed and garbage collected from the
491-
collection.
492-
493-
#### Args
494-
495-
- *\*ids (int)*:
496-
Thing IDs to subscribe to. No error is returned in case one of
497-
the given things are not found within the collection, instead a
498-
`WARN` event will be send to the client.
499-
- *scope (str, optional)*:
500-
Subscribe on things in this scope. If not specified, the
501-
default scope will be used. Only collection scopes may contain
502-
things so only collection scopes can be used.
503-
See https://docs.thingsdb.net/v0/overview/scopes/ for how to
504-
format a scope.
505-
506-
#### Returns
507-
508-
Future which result will be set to `None` if successful.
509-
510-
## Model
511-
512-
It is possible to create a model which will map to data in ThingsDB.
513-
The model will be kept up-to-date be the client. It is possible to break
514-
anywhere you want in the model. What is not provided, will not be watched.
515-
516-
### Collection
517-
518-
A collection is always required, even you do not plan to watch anything in the
519-
root of the collection. In the latter case you can just create an empty
520-
collection which can be used when initializing individual things.
521-
522-
```python
523-
import asyncio
524-
from thingsdb.client import Client
525-
from thingsdb.model import Collection
526-
527-
class Foo(Collection):
528-
name = 'str'
529-
```
530-
531-
In the example above, the ThingsDB collection name must be equal to the Python Class name, `Foo` in this case.
532-
It may be useful to use a different Python Class name than the ThingsDB collection
533-
name. This can be achieved by initializing the collection with a name attribute, for example `foo = Foo(name='foo')`.
534-
As an alternative, the magic attribute `__COLLECTION_NAME__` can be used , for example:
535-
536-
```python
537-
class Stuff(Collection):
538-
# the ThingsDB collection name is `stuff`, all lower case characters
539-
__COLLECTION_NAME__ = 'stuff'
540-
```
541-
542-
If both a `name` argument and the magic attribute `__COLLECTION_NAME__` are used, the `name` argument wins.
543-
544-
545-
### Thing
546-
547-
```python
548-
import asyncio
549-
from thingsdb.client import Client
550-
from thingsdb.model import Collection, Thing
551-
552-
class Bar(Thing):
553-
name = 'str'
554-
other = 'Bar?', lambda: Bar
555-
556-
class Foo(Collection):
557-
bar = 'Bar', Bar
558-
559-
async def example():
560-
client = Client()
561-
foo = Foo()
562-
await client.connect('localhost')
563-
try:
564-
await client.authenticate('admin', 'pass')
565-
await foo.load(client)
566-
567-
# ... now the collection will be watched
568-
569-
finally:
570-
client.close()
571-
await client.wait_closed()
572-
```
573-
574-
Suppose you have an ID and want to watch that single thing, then
575-
you can initialize the thing and call `watch()` manually. For example,
576-
consider we have an `#5` for a `Bar` type in collection `Foo`:
577-
578-
```python
579-
bar = Bar(foo, 5)
580-
await bar.watch()
581-
```
582-
583-
### Enum
584-
585-
The Python ThingsDB model has it's own Enum implementation which should not
586-
be confused with the default Python Enum class.
587-
588-
```python
589-
import asyncio
590-
from thingsdb.client import Client
591-
from thingsdb.model import Collection, Thing, Enum
592-
593-
594-
class Color(Enum):
595-
RED = "#f00"
596-
BLUE = "#0f0"
597-
GREEN = "#00f"
598-
599-
600-
class Brick(Thing):
601-
color = 'Color', Color
602-
603-
def on_init(self, *args, **kwars):
604-
super().on_init(*args, **kwars)
605-
print(f'''
606-
Init Brick:
607-
id: {self.id()}
608-
color name: {self.color.name}
609-
color value: {self.color.value}
610-
''')
611-
612-
class Lego(Collection):
613-
bricks = '[Brick]', Brick
614-
615-
616-
async def example():
617-
client = Client()
618-
lego = Lego()
619-
await client.connect('localhost')
620-
try:
621-
await client.authenticate('admin', 'pass')
622-
try:
623-
await lego.build(
624-
client,
625-
scripts=['.bricks = [];'],
626-
delete_if_exists=False)
627-
except KeyError:
628-
pass
629-
await lego.load(client)
630-
631-
# ... now the collection will be watched for 100 seconds
632-
await asyncio.sleep(100)
422+
## Room
633423

634-
finally:
635-
client.close()
636-
await client.wait_closed()
637-
638-
if __name__ == '__main__':
639-
loop = asyncio.get_event_loop()
640-
loop.run_until_complete(example())
641-
```
642-
643-
When adding a new brick, for example using the following code:
644424

645-
```
646-
.bricks.push(Brick{
647-
color: Color{RED}
648-
});
649-
```
650-
651-
...then the `on_init` function will be called, printing the following output: *(the id might be different since this is auto-generated)*
652-
653-
```text
654-
Init Brick:
655-
id: 123
656-
color name: RED
657-
color value: #f00
658-
```
659-
660-
If you do not care about the whole `Color` class, then you can just create an empty class like this:
661-
662-
```python
663-
class Color(Enum):
664-
pass
665-
```

0 commit comments

Comments
 (0)