diff --git a/activitypub/classes.py b/activitypub/classes.py index 1616c95..3376d66 100644 --- a/activitypub/classes.py +++ b/activitypub/classes.py @@ -33,7 +33,7 @@ class ActivityPubBase(): if attr_name.startswith("ap_"): attr = getattr(self, attr_name) if isinstance(attr, str) and "$" in attr: - dependencies[attr_name[3:]] = {x[1:] for x in self.parse(attr) + dependencies[attr_name[3:]] = {x[1:] for x in self.manager.parse(attr) if x.startswith("$") and x[1:] != attr_name[3:]} ## Now, replace them in order: for attr_name in self.topological_sort(dependencies): @@ -46,7 +46,7 @@ class ActivityPubBase(): if attr is None: raise Exception("variable depends on field that is empty: %s" % attr_name) if isinstance(attr, str) and "$" in attr: - setattr(self, attr_name, self.expand_defaults(attr)) + setattr(self, attr_name, self.manager.expand_defaults(attr)) def topological_sort(self, data): """ @@ -71,49 +71,6 @@ class ActivityPubBase(): for item, dep in data.items() if item not in ordered} - def parse(self, string): - """ - Parse a string delimited by non-alpha, non-$ symbols. - - >>> from activitypub import Manager - >>> m = Manager() - >>> p = m.Person() - >>> p.parse("apple/banana/$variable") - ['apple', 'banana', '$variable'] - """ - retval = [] - current = [] - for s in string: - if s.isalpha() or (s in ["$"] and len(current) == 0): - current.append(s) - else: - if current: - retval.append("".join(current)) - if s == "$": - current = ["$"] - else: - current = [] - if current: - retval.append("".join(current)) - return retval - - def expand_defaults(self, string): - """ - Expand a string with defaults. - """ - for key in self.manager.defaults: - if key.startswith("$"): - if callable(self.manager.defaults[key]): - string = string.replace(key, self.manager.defaults[key]()) - else: - string = string.replace(key, self.manager.defaults[key]) - for key in self.parse(string): - if key.startswith("$"): - if getattr(self, "ap_" + key[1:]) is None: - raise Exception("expansion requires %s" % key[1:]) - string = string.replace(key, getattr(self, "ap_" + key[1:])) - return string - def to_dict(self): """ Convert object to JSON format. diff --git a/activitypub/database/dummy.py b/activitypub/database/dummy.py index 6991685..5f78471 100644 --- a/activitypub/database/dummy.py +++ b/activitypub/database/dummy.py @@ -194,9 +194,10 @@ class DummyTable(Table): >>> table.find({"c": 1, "d": 2}).count() 2 """ + row = copy.deepcopy(row) # expensive, but prevents errors if row.get("_id", None) is None: row["_id"] = ObjectId() - self.data.append(copy.deepcopy(row)) + self.data.append(row) def find(self, query=None, limit=None): """ diff --git a/activitypub/manager.py b/activitypub/manager.py index 15323d2..dbaee1a 100644 --- a/activitypub/manager.py +++ b/activitypub/manager.py @@ -11,6 +11,8 @@ class Manager(): >>> manager = Manager(database=db) >>> """ + app_name = "activitypub" + version = "1.0.0" def __init__(self, context=None, defaults=None, database=None): from .classes import ActivityPubBase self.callback = lambda box, activity_id: None @@ -53,6 +55,54 @@ class Manager(): "Note.id": "$attributedTo/note/$id", } + def user_agent(self): + return "%s (%s/%s; +%s)" % (requests.utils.default_user_agent(), + self.app_name, + self.version, + self.expand_defaults("$SCHEME/$HOST")) + + def expand_defaults(self, obj, string): + """ + Expand a string with defaults. + """ + for key in self.defaults: + if key.startswith("$"): + if callable(self.defaults[key]): + string = string.replace(key, self.defaults[key]()) + else: + string = string.replace(key, self.defaults[key]) + for key in self.parse(string): + if key.startswith("$"): + if getattr(obj, "ap_" + key[1:]) is None: + raise Exception("expansion requires %s" % key[1:]) + string = string.replace(key, getattr(obj, "ap_" + key[1:])) + return string + + def parse(self, string): + """ + Parse a string delimited by non-alpha, non-$ symbols. + + >>> from activitypub import Manager + >>> m = Manager() + >>> m.parse("apple/banana/$variable") + ['apple', 'banana', '$variable'] + """ + retval = [] + current = [] + for s in string: + if s.isalpha() or (s in ["$"] and len(current) == 0): + current.append(s) + else: + if current: + retval.append("".join(current)) + if s == "$": + current = ["$"] + else: + current = [] + if current: + retval.append("".join(current)) + return retval + def from_dict(self, data): from .classes import ActivityPubBase return ActivityPubBase.from_dict(data) @@ -65,6 +115,8 @@ class Manager(): def on_post_to_box(self, box, activity): """ manager.on_post_to_box("inbox", activity) + manager.on_post_to_box("outbox", activity) + manager.on_post_to_box("replies", reply) """ self.database.activities.insert_one( { @@ -110,3 +162,34 @@ class Manager(): return [doc["activity"]["object"] for doc in self.database.activities.find(q)] + def get_iri(self, iri): + if iri.startswith(self.expand_defaults("$SCHEME/$HOST")): + ## get from table + ## self.database.activity.find() + ## TODO: WIP + pass + else: + try: + response = requests.get( + iri, + headers={ + "User-Agent": self.user_agent(), + "Accept": "application/activity+json", + }, + timeout=10, + allow_redirects=False, + **kwargs) + except: + raise Exception("unable to fetch uri") + return self.handle(response) + + def handle_response(self, response): + if response.status_code == 404: + raise Exception("iri is not found") + elif response.status_code == 410: + raise Exception("iri is gone") + elif response.status_code in [500, 502, 503]: + raise Exception("unable to fetch; server error") + response.raise_for_status() + return response.json() +