kopia lustrzana https://github.com/dsblank/activitypub
Refactor database; added MongoDatabase
rodzic
a0d6b39a40
commit
851af921fa
|
@ -9,7 +9,8 @@ This module is designed to be a generally useful ActivityPub library in Python.
|
|||
The first two levels can be used indpendently, or together. They can best be used toegether using a Manager:
|
||||
|
||||
```python
|
||||
>>> from activitypub import DummyDatabase, Manager
|
||||
>>> from activitypub import Manager
|
||||
>>> from activitypub.database import DummyDatabase
|
||||
>>> db = DummyDatabase()
|
||||
>>> manager = Manager(database=db)
|
||||
>>> p = manager.Person(id="alyssa")
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .classes import Manager
|
||||
from .database import Database, ListDatabase
|
||||
from .manager import Manager
|
||||
from .classes import *
|
||||
|
|
|
@ -147,8 +147,9 @@ class ObjectId(object):
|
|||
>>> import datetime
|
||||
>>> gen_time = datetime.datetime(2010, 1, 1)
|
||||
>>> early = ObjectId.from_datetime(gen_time)
|
||||
>>> from activitypub import Manager, ListDatabase
|
||||
>>> m = Manager(database=ListDatabase())
|
||||
>>> from activitypub import Manager
|
||||
>>> from activitypub.database import DummyDatabase
|
||||
>>> m = Manager(database=DummyDatabase())
|
||||
>>> n = m.Note(_id=early, attributedTo="alyssa")
|
||||
>>> m.database.activities.insert_one(n.to_dict())
|
||||
>>> gen_time = datetime.datetime(2011, 1, 1)
|
||||
|
|
|
@ -1,113 +1,5 @@
|
|||
import uuid
|
||||
import mimetypes
|
||||
|
||||
class Manager():
|
||||
"""
|
||||
Manager class that ties together ActivityPub objects, defaults,
|
||||
and a database.
|
||||
|
||||
>>> from activitypub import ListDatabase, Manager
|
||||
>>> db = ListDatabase()
|
||||
>>> manager = Manager(database=db)
|
||||
>>>
|
||||
"""
|
||||
def __init__(self, context=None, defaults=None, database=None):
|
||||
self.callback = lambda box, activity_id: None
|
||||
self.context = context
|
||||
self.defaults = defaults or self.make_defaults()
|
||||
self.defaults["$UUID"] = lambda: str(uuid.uuid4())
|
||||
self.database = database
|
||||
|
||||
def make_wrapper(manager, class_):
|
||||
def wrapper(*args, **kwargs):
|
||||
return ActivityPubBase.CLASSES[class_](manager, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
for class_ in ActivityPubBase.CLASSES:
|
||||
setattr(self, class_, make_wrapper(self, class_))
|
||||
|
||||
def make_defaults(self):
|
||||
"""
|
||||
A default field can refer to itself, which means that it needs a
|
||||
value to begin with.
|
||||
|
||||
>>> m = Manager()
|
||||
>>> n = m.Note(attributedTo="alyssa", id="23")
|
||||
>>> n.to_dict()
|
||||
{'@context': 'https://www.w3.org/ns/activitystreams', 'attributedTo': 'alyssa', 'id': 'alyssa/note/23', 'type': 'Note'}
|
||||
|
||||
A default can be a $-variable, or the name of a "Class.field_name".
|
||||
"""
|
||||
return {
|
||||
"$SCHEME": "https",
|
||||
"$HOST": "example.com",
|
||||
"Person.id": "$SCHEME://$HOST/$id",
|
||||
"Person.likes": "$id/likes",
|
||||
"Person.following": "$id/following",
|
||||
"Person.followers": "$id/followers",
|
||||
"Person.liked": "$id/liked",
|
||||
"Person.inbox": "$id/inbox",
|
||||
"Person.outbox": "$id/outbox",
|
||||
"Person.url": "$id",
|
||||
"Note.id": "$attributedTo/note/$id",
|
||||
}
|
||||
|
||||
def from_dict(self, data):
|
||||
return ActivityPubBase.from_dict(data)
|
||||
|
||||
def to_list(self, item):
|
||||
if isinstance(item, list):
|
||||
return item
|
||||
return [item]
|
||||
|
||||
def on_post_to_box(self, box, activity):
|
||||
"""
|
||||
manager.on_post_to_box("inbox", activity)
|
||||
"""
|
||||
self.database.activities.insert_one(
|
||||
{
|
||||
"box": box,
|
||||
"activity": activity.to_dict(),
|
||||
"type": self.to_list(activity.type),
|
||||
"remote_id": activity.id,
|
||||
"meta": {
|
||||
"undo": False,
|
||||
"deleted": False
|
||||
},
|
||||
}
|
||||
)
|
||||
self.callback(box, activity.id)
|
||||
|
||||
def delete_reply(self, actor, note):
|
||||
if note.inReplyTo:
|
||||
self.database.activities.update_one(
|
||||
{"activity.object.id": note.inReplyTo},
|
||||
{"$inc": {"meta.count_reply": -1, "meta.count_direct_reply": -1}},
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
self.callback = callback
|
||||
|
||||
def get_followers(self, remote_id):
|
||||
q = {
|
||||
"remote_id": remote_id,
|
||||
"box": "inbox",
|
||||
"type": "follow",
|
||||
"meta.undo": False,
|
||||
}
|
||||
return [doc["activity"]["actor"]
|
||||
for doc in self.database.activities.find(q)]
|
||||
|
||||
def get_following(self, remote_id):
|
||||
q = {
|
||||
"remote_id": remote_id,
|
||||
"box": "outbox",
|
||||
"type": "follow",
|
||||
"meta.undo": False,
|
||||
}
|
||||
return [doc["activity"]["object"]
|
||||
for doc in self.database.activities.find(q)]
|
||||
|
||||
class ActivityPubBase():
|
||||
CLASSES = {}
|
||||
|
||||
|
@ -159,6 +51,7 @@ class ActivityPubBase():
|
|||
def topological_sort(self, data):
|
||||
"""
|
||||
|
||||
>>> from activitypub import Manager
|
||||
>>> manager = Manager()
|
||||
>>> manager.Person(id="alyssa").to_dict()
|
||||
{'@context': 'https://www.w3.org/ns/activitystreams', 'endpoints': {}, 'followers': 'https://example.com/alyssa/followers', 'following': 'https://example.com/alyssa/following', 'id': 'https://example.com/alyssa', 'inbox': 'https://example.com/alyssa/inbox', 'liked': 'https://example.com/alyssa/liked', 'likes': 'https://example.com/alyssa/likes', 'outbox': 'https://example.com/alyssa/outbox', 'type': 'Person', 'url': 'https://example.com/alyssa'}
|
||||
|
@ -182,6 +75,7 @@ class ActivityPubBase():
|
|||
"""
|
||||
Parse a string delimited by non-alpha, non-$ symbols.
|
||||
|
||||
>>> from activitypub import Manager
|
||||
>>> m = Manager()
|
||||
>>> p = m.Person()
|
||||
>>> p.parse("apple/banana/$variable")
|
||||
|
@ -333,6 +227,7 @@ class Organization(Actor):
|
|||
|
||||
class Person(Actor):
|
||||
"""
|
||||
>>> from activitypub import Manager
|
||||
>>> m = Manager()
|
||||
>>> p = m.Person()
|
||||
>>> p.icon = "image.svg"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
from .base import Database, Table
|
||||
from .dummy import DummyDatabase
|
||||
from .mongodb import MongoDatabase
|
|
@ -0,0 +1,22 @@
|
|||
class Database():
|
||||
"""
|
||||
Base database class.
|
||||
"""
|
||||
def __init__(self, Table):
|
||||
self.activities = Table(self, "activities")
|
||||
self.u2f = Table(self, "u2f")
|
||||
self.instances = Table(self, "instances")
|
||||
self.actors = Table(self, "actors")
|
||||
self.indieauth = Table(self, "indieauth")
|
||||
|
||||
## TODO: put required methods to override here
|
||||
|
||||
class Table():
|
||||
"""
|
||||
Base table class.
|
||||
"""
|
||||
def __init__(self, database=None, name=None):
|
||||
self.database = database
|
||||
self.name = name
|
||||
|
||||
## TODO: put required methods to override here
|
|
@ -2,15 +2,8 @@ import re
|
|||
import ast
|
||||
import json
|
||||
|
||||
from .bson import ObjectId
|
||||
|
||||
class Table():
|
||||
"""
|
||||
Base table class.
|
||||
"""
|
||||
def __init__(self, database=None, name=None):
|
||||
self.database = database
|
||||
self.name = name
|
||||
from ..bson import ObjectId
|
||||
from .base import Database, Table
|
||||
|
||||
def get_item_in_dict(dictionary, item):
|
||||
"""
|
||||
|
@ -145,7 +138,7 @@ def match(doc, query):
|
|||
return False
|
||||
return True
|
||||
|
||||
class ListTable(Table):
|
||||
class DummyTable(Table):
|
||||
def __init__(self, database=None, name=None, data=None):
|
||||
super().__init__(database, name)
|
||||
self.data = data or []
|
||||
|
@ -168,14 +161,14 @@ class ListTable(Table):
|
|||
def sort(self, sort_key, sort_order):
|
||||
# sort_key = "_id"
|
||||
# sort_order = 1 or -1
|
||||
return ListTable(data=sorted(
|
||||
return DummyTable(data=sorted(
|
||||
self.data,
|
||||
key=lambda row: get_item_in_dict(row, sort_key),
|
||||
reverse=(sort_order == -1)))
|
||||
|
||||
def insert_one(self, row):
|
||||
"""
|
||||
>>> table = ListTable()
|
||||
>>> table = DummyTable()
|
||||
>>> table.count()
|
||||
0
|
||||
>>> len(table.data)
|
||||
|
@ -206,7 +199,7 @@ class ListTable(Table):
|
|||
|
||||
def find(self, query=None, limit=None):
|
||||
"""
|
||||
>>> table = ListTable()
|
||||
>>> table = DummyTable()
|
||||
>>> table.insert_one({"a": 1, "b": 2})
|
||||
>>> table.find({"a": 1}) # doctest: +ELLIPSIS
|
||||
[{'a': 1, 'b': 2, '_id': ObjectId('...')}]
|
||||
|
@ -215,17 +208,17 @@ class ListTable(Table):
|
|||
"""
|
||||
if query is not None:
|
||||
if limit is not None:
|
||||
return ListTable(data=[doc for doc in self.data if match(doc, query)][:limit])
|
||||
return DummyTable(data=[doc for doc in self.data if match(doc, query)][:limit])
|
||||
else:
|
||||
return ListTable(data=[doc for doc in self.data if match(doc, query)])
|
||||
return DummyTable(data=[doc for doc in self.data if match(doc, query)])
|
||||
elif limit is not None:
|
||||
return ListTable(data=self.data[:limit])
|
||||
return DummyTable(data=self.data[:limit])
|
||||
else:
|
||||
return self
|
||||
|
||||
def remove(self, query=None):
|
||||
"""
|
||||
>>> table = ListTable()
|
||||
>>> table = DummyTable()
|
||||
>>> table.insert_one({"a": 1, "b": 2})
|
||||
>>> table.insert_one({"c": 3, "d": 4})
|
||||
>>> table.find({"a": 1}) # doctest: +ELLIPSIS
|
||||
|
@ -272,14 +265,6 @@ class ListTable(Table):
|
|||
|
||||
count_documents = count
|
||||
|
||||
class Database():
|
||||
def __init__(self, Table):
|
||||
self.activities = Table(self, "activities")
|
||||
self.u2f = Table(self, "u2f")
|
||||
self.instances = Table(self, "instances")
|
||||
self.actors = Table(self, "actors")
|
||||
self.indieauth = Table(self, "indieauth")
|
||||
|
||||
class ListDatabase(Database):
|
||||
class DummyDatabase(Database):
|
||||
def __init__(self):
|
||||
super().__init__(ListTable)
|
||||
super().__init__(DummyTable)
|
|
@ -0,0 +1,30 @@
|
|||
from pymongo import MongoClient
|
||||
|
||||
from .base import Database, Table
|
||||
|
||||
class MongoTable(Table):
|
||||
"""
|
||||
"""
|
||||
def __init__(self, database, name):
|
||||
super().__init__(database, name)
|
||||
self.collection = getattr(database.DB, name)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if "collection" in self.__dict__:
|
||||
return getattr(self.collection, attr)
|
||||
else:
|
||||
raise AttributeError("no such attribute: %s" % attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if "collection" in self.__dict__ and hasattr(self.collection, attr):
|
||||
setattr(self.collection, attr, value)
|
||||
else:
|
||||
self.__dict__[attr] = value
|
||||
|
||||
class MongoDatabase(Database):
|
||||
def __init__(self, uri, db_name):
|
||||
self.uri = uri
|
||||
self.client = MongoClient(self.uri)
|
||||
self.db_name = db_name
|
||||
self.DB = self.client[self.db_name]
|
||||
super().__init__(MongoTable)
|
|
@ -0,0 +1,112 @@
|
|||
import uuid
|
||||
|
||||
class Manager():
|
||||
"""
|
||||
Manager class that ties together ActivityPub objects, defaults,
|
||||
and a database.
|
||||
|
||||
>>> from activitypub import Manager
|
||||
>>> from activitypub.database import DummyDatabase
|
||||
>>> db = DummyDatabase()
|
||||
>>> manager = Manager(database=db)
|
||||
>>>
|
||||
"""
|
||||
def __init__(self, context=None, defaults=None, database=None):
|
||||
from .classes import ActivityPubBase
|
||||
self.callback = lambda box, activity_id: None
|
||||
self.context = context
|
||||
self.defaults = defaults or self.make_defaults()
|
||||
self.defaults["$UUID"] = lambda: str(uuid.uuid4())
|
||||
self.database = database
|
||||
|
||||
def make_wrapper(manager, class_):
|
||||
def wrapper(*args, **kwargs):
|
||||
return ActivityPubBase.CLASSES[class_](manager, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
for class_ in ActivityPubBase.CLASSES:
|
||||
setattr(self, class_, make_wrapper(self, class_))
|
||||
|
||||
def make_defaults(self):
|
||||
"""
|
||||
A default field can refer to itself, which means that it needs a
|
||||
value to begin with.
|
||||
|
||||
>>> m = Manager()
|
||||
>>> n = m.Note(attributedTo="alyssa", id="23")
|
||||
>>> n.to_dict()
|
||||
{'@context': 'https://www.w3.org/ns/activitystreams', 'attributedTo': 'alyssa', 'id': 'alyssa/note/23', 'type': 'Note'}
|
||||
|
||||
A default can be a $-variable, or the name of a "Class.field_name".
|
||||
"""
|
||||
return {
|
||||
"$SCHEME": "https",
|
||||
"$HOST": "example.com",
|
||||
"Person.id": "$SCHEME://$HOST/$id",
|
||||
"Person.likes": "$id/likes",
|
||||
"Person.following": "$id/following",
|
||||
"Person.followers": "$id/followers",
|
||||
"Person.liked": "$id/liked",
|
||||
"Person.inbox": "$id/inbox",
|
||||
"Person.outbox": "$id/outbox",
|
||||
"Person.url": "$id",
|
||||
"Note.id": "$attributedTo/note/$id",
|
||||
}
|
||||
|
||||
def from_dict(self, data):
|
||||
from .classes import ActivityPubBase
|
||||
return ActivityPubBase.from_dict(data)
|
||||
|
||||
def to_list(self, item):
|
||||
if isinstance(item, list):
|
||||
return item
|
||||
return [item]
|
||||
|
||||
def on_post_to_box(self, box, activity):
|
||||
"""
|
||||
manager.on_post_to_box("inbox", activity)
|
||||
"""
|
||||
self.database.activities.insert_one(
|
||||
{
|
||||
"box": box,
|
||||
"activity": activity.to_dict(),
|
||||
"type": self.to_list(activity.type),
|
||||
"remote_id": activity.id,
|
||||
"meta": {
|
||||
"undo": False,
|
||||
"deleted": False
|
||||
},
|
||||
}
|
||||
)
|
||||
self.callback(box, activity.id)
|
||||
|
||||
def delete_reply(self, actor, note):
|
||||
if note.inReplyTo:
|
||||
self.database.activities.update_one(
|
||||
{"activity.object.id": note.inReplyTo},
|
||||
{"$inc": {"meta.count_reply": -1, "meta.count_direct_reply": -1}},
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
self.callback = callback
|
||||
|
||||
def get_followers(self, remote_id):
|
||||
q = {
|
||||
"remote_id": remote_id,
|
||||
"box": "inbox",
|
||||
"type": "follow",
|
||||
"meta.undo": False,
|
||||
}
|
||||
return [doc["activity"]["actor"]
|
||||
for doc in self.database.activities.find(q)]
|
||||
|
||||
def get_following(self, remote_id):
|
||||
q = {
|
||||
"remote_id": remote_id,
|
||||
"box": "outbox",
|
||||
"type": "follow",
|
||||
"meta.undo": False,
|
||||
}
|
||||
return [doc["activity"]["object"]
|
||||
for doc in self.database.activities.find(q)]
|
||||
|
Ładowanie…
Reference in New Issue