pax_global_header00006660000000000000000000000064126161506640014521gustar00rootroot0000000000000052 comment=68a31a78f4f997e3623dfe5606146ae2398cc805 activipy-0.1/000077500000000000000000000000001261615066400132115ustar00rootroot00000000000000activipy-0.1/.gitignore000066400000000000000000000003111261615066400151740ustar00rootroot00000000000000/dist/ /bin/ /develop-eggs/ /build/ /eggs/ /lib/ /lib64 /local/ /include/ /parts/ /share/ /activipy*.egg/ *.egg-info # File extensions *.pyc *.pyo *~ *.swp *.mo __pycache__ *.pyc .cache docs/build/ activipy-0.1/COPYING000066400000000000000000000027511261615066400142510ustar00rootroot00000000000000All code here dual licensed under Apache License v2.0 and GPL v3 or later, as published by the Free Software Foundation. (Why both when Apache v2 and GPLv3 is effectively just Apache v2? Well, future compatibility is a minor concern. Also, the founder of this package thinks that being in a camp on one or the other and shaking fists at each other is an annoying and self-defeating pasttime, so there's a bit of community commentary here: both approaches are great, choose strategically!) You ought to find some copies of those licenses in this directory, as long as nobody mucked things up! When adding new files, please add the following: ## Activipy --- ActivityStreams 2.0 implementation and testing for Python ## Copyright © 20XX Your Name ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2... ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. activipy-0.1/MANIFEST.in000066400000000000000000000000611261615066400147440ustar00rootroot00000000000000include activipy/activitystreams2-context.jsonld activipy-0.1/README.org000066400000000000000000000006731261615066400146650ustar00rootroot00000000000000#+TITLE: Activipy: ActivityStreams 2.0 stuff for Python Read the [[http://activipy.readthedocs.org/en/latest/index.html][documentation!]] * What is this? Activipy is an implementation of ActivityStreams 2.0 for Python. It also, hopefully soon, will serve as an ActivityStreams validator. More things will go here in the future, in the meanwhile maybe see [[file:./activipy.org][activipy.org]] in this directory for braindumps and tasks. activipy-0.1/activipy.org000066400000000000000000000700541261615066400155600ustar00rootroot00000000000000* Braindump Python is an imperative language; we won't try to be "purely functional" but "hidden state" could really screw us, so we'll try to reduce that as much as possible. We need: - Use a gdbm backed activitystreams object storage - For extensions, pyld - verifiers - method dispatch DMD store should store in a dictionary like: ... with the key being the "@id" of the toplevel activitystreams object. #+BEGIN_SRC python {"asobj": {"@type": "Object", "@id": "uuid:d773cb99-078b-496b-b3f0-012d3ade5930", "blah": "blah"}, "private": {"blah": "blah"}} #+END_SRC We are going to have to handle inheritance manually, because there can be multiple types. We can't use python's inheritance system. We need an "ASVocab" system to operate within. This one should have a memoized version of the json-ld expansion of the default activitystreams vocabulary, but it should also have a mapping of type URIs to ASClass objects. The store will be used separately, should provide simple store and retrieve mechanisms. Some complexity comes from the fact that in a "real world" system, we don't just store and receive what's been given to us. We need a way to trigger application-specific hooks. * Tasks ** TODO Add optional thunk to ASType constructor: get_default_env Should return the default vocabulary if none specified. ** TODO Handle all @context edges *** Brainstorming Probably what we ought to do is enforce that everything in an ASObj share a common @context setup. This means, it doesn't matter what's on an @context coming into ASObj(), we slice it off and replace it with @context after the deepcopy_in (and all child objects, we don't need an @context at all). But we do need to be able to "ingest foreign material", which means we should provide an Environment.ingest() method (or .ingest_foreign()) As for how to combine multiple contexts, it can be done via an array; see the [[http://www.w3.org/TR/json-ld/#dfn-local-context][local context]] part of the docs. So that solves how to do things! (Though there may be some minor question as to what to do if the application's @context is also an array... do we merge them? Will it work as-is?) *** DONE Add extra_context field to Environment CLOSED: [2015-10-29 Thu 13:42] **** DONE Add to init structure CLOSED: [2015-10-29 Thu 13:20] **** DONE Add on expansion CLOSED: [2015-10-29 Thu 13:20] **** DONE Oh wait remove on expansion CLOSED: [2015-10-29 Thu 13:42] That didn't make sense, because the extra context gets added to the asobj, so it doesn't need to be implicit. *** TODO Handling when creating new ASObj() Basically, strip off the existing @context recursively with deepcopy_in, and add our context instead, if any exists. *** TODO Environment.ingest(jsonld, imply_asvocab=True) This should expand out a json-ld document then compact it to the Environment's own context. The question is, can we use the "grafted-on @context" required in the above description, or do we have *** TODO breaking off a child ASObj Basically attach an @context to the json we have of it (though I guess this will happen automatically) ** TODO Maybe rename MethodId to MethodSpec ** TODO Switch from method "object symbols" to strings? Currently we require setting up "object symbols" which self-document what a method does and etc, but they're also slightly unwieldy to set up. It will be less precise, but maybe easier, to just use strings to represent what a method is. ** TODO Add memoization *** TODO General memoization function *** TODO Property memoization ** TODO Switch ASObj.__getitem__ to use deepcopy_jsobj_out Or rather, we should specify both a deepcopy_jsobj_in and a deepcopy_jsobj_out :) So, if we're accessing a key value pair where the value is a list of activitystreams objects, we'd like the activitystreams objects converted to ASObj objects as well. *** DONE Add deepcopy_jsobj_out CLOSED: [2015-10-29 Thu 14:07] *** DONE Use it in ASObj.__getitem__ CLOSED: [2015-10-29 Thu 14:07] *** DONE Rename deepcopy_jsobj -> deepcopy_jsobj_in CLOSED: [2015-10-29 Thu 14:07] *** TODO Add tests ** TODO Add ASProp ** TODO Add demos section *** DONE Vocab demo CLOSED: [2015-11-01 Sun 10:30] RoyalCheckin or CheckUp - checkup:CheckIn - checkup:RoyalStatus - checkup:Coupon *** TODO linter/validator We can use the method dispatch system to handle this. *** Archive :ARCHIVE: **** DONE Easy GDBM based storage system CLOSED: [2015-10-28 Wed 17:17] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:21 :END: ** TODO Documentation basics *** TODO Tutorial *** TODO Document basic "types" structure *** Archive :ARCHIVE: **** DONE Add sphinx basic structure CLOSED: [2015-10-22 Thu 13:01] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:21 :END: **** DONE Documentation structure CLOSED: [2015-10-28 Wed 17:17] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:21 :END: - Intro - About ActiviPy - Tutorial - Core types - Vocabulary - Extending the environment - Advanced Examples ** TODO Rename CheckUp demo to VisitIt everywhere *** TODO code *** TODO docs ** TODO Make ASVocab more useful How to do this? We want to: - probably preload a json-ld context - Somehow make ASVocab objects useful for a - make ourself more useful to ASObj objects ** TODO Tests *** TODO Test all types.py stuff **** TODO ASVocab **** TODO ASObj **** TODO ASEnvironment **** Archive :ARCHIVE: ***** DONE ASType CLOSED: [2015-10-12 Mon 16:37] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:21 :END: *** TODO Basic vocabs stuff *** Archive :ARCHIVE: **** DONE Basic test infrastructure CLOSED: [2015-10-12 Mon 16:37] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:21 :END: ** TODO Consider rename to Pydraulics? After all, I'm the one who started that project, and it's abandoned... ** TODO Investigate restructuring ASType instances via metaclassing Basically, the main reason is that we'd like to be able to do: #+BEGIN_SRC python help(CollectionPage) #+END_SRC and get the appropriate useful info. However, it's still true that calling CollectionPage() should return a ASObj object, not a CollectionPage() object. Reason being that ActivityStreams objects can have multiple "@type" fields. ** Archive :ARCHIVE: *** DONE Add license stuff CLOSED: [2015-10-07 Wed 15:01] :PROPERTIES: :ARCHIVE_TIME: 2015-10-07 Wed 15:01 :END: **** DONE Add license files CLOSED: [2015-10-07 Wed 14:29] **** DONE Add note on why both apache v2 and gplv3 to COPYING CLOSED: [2015-10-07 Wed 14:33] **** DONE Add copyright headers and a note on convention CLOSED: [2015-10-07 Wed 15:01] *** DONE Fill in complete vocabulary CLOSED: [2015-10-12 Mon 15:36] :PROPERTIES: :ARCHIVE_TIME: 2015-10-12 Mon 15:41 :END: *** CANCELED Switch to pyrsistent for ASObj structures? CLOSED: [2015-10-12 Mon 15:35] :PROPERTIES: :ARCHIVE_TIME: 2015-10-12 Mon 15:41 :END: https://github.com/tobgu/pyrsistent We more or less force/fake immutability right now, and maybe it makes more sense to just use something that *is* immutable *UPDATE:* Canceled. [[https://gist.github.com/datagrok/2199506][More info]] on why Pyrsistent has a promising future, but can't work for now. *** CANCELED Command line test suite CLOSED: [2015-10-12 Mon 22:45] :PROPERTIES: :ARCHIVE_TIME: 2015-10-15 Thu 21:05 :END: This is [[https://github.com/evanp/a2test][its own project]] now. See [[https://github.com/w3c-social/activipy/issues/1][this issue]]. **** Relevant parts of convo paroneayea: so, a couple of questions on that Does having a single package that is a producer and a consumer make sense? Or multiple packages? [12:18] evanpro: my first goal is to make a library for the purpose of tests, basically along the lines of how you suggested... it'll just store @id's to a gdbm store. But I'll design it in a way that afterwards, it can be used for something like pypump, and for using as2 stuff but my first goal is: fulfill the test requirements Whoa! while working towards something more general gdbm is oldschool I know Wait what's the GDBM for? I don't understand what you need persistence for [12:19] well it could also just be a dictionary Wouldn't an AS2 library do something like I was going along with your suggestion that you have a command-line submission tool JSON -> native language object and native language object -> JSON evanpro: yes evanpro: ok well maybe it can be in-memory only [12:20] evanpro: my main concern is get the thing working 1s So I was thinking that a test command-line app might look like this https://gist.github.com/evanp/b49c3fc37caa21a323a1 hey, would it be useful if I created next week's meeting page and filled it with the stuff on the agenda that we didn't get to? e.g. we missed branching models strugee: YES! [12:23] Nice evanpro: that might work nicely will do evanpro: okay, I will probably do something like that [12:24] paroneayea: and then a test driver would work like this https://gist.github.com/evanp/5d80c0aa3f168465d84d So that way you could call "testdriver.py dumpactivitytype.py" [12:25] as well as "testdriver.py dumpactivitytype.rb" evanpro: ok evanpro: I see evanpro: we also want a way to show mutations [12:26] evanpro: and side effects eg update verbs should actually update the thing in store That might be too much for a data format to deal with evanpro: I mean, for the test suite Yes, that's what I'm saying we want to be sure that activities can actually do the things they promise What I'm saying is that no we don't [12:27] When we're testing the social API, definitely evanpro: this is why I was saying that there's not much to do as in terms of a test suite But I think an activity streams library should just parse from JSON and export to JSON the only thing your example checks really is that it's valid right? that it's json, has the right fields, in the right types It checks that the activitystreams implementation library (the one that the dumpactivitytype.py script imports) can find the type of an activity [12:28] I realize that it appears to be really trivial But you'd need dozens of such test scripts [12:29] dumpactivityactortype.py dumpactivityactorid.py That kind of thing evanpro: okay, so I'll definitely support this. Another possibility is using command-line arguments evanpro: though, one of the things is, the activitystreams vocabulary *does* describe things with side effects I might test for that too, but I won't make it so complex that you can't do the simple tsts you ahve [12:30] That's probably a fair point I would really, really strongly recommend that you first publish your intentions for the test format evanpro: to the list? And that you concentrate on the bare minimum first Yes evanpro: okay I'll do that to the list [12:31] evanpro: I was planning on working on deployment stuff this week, but it seems like this has become really urgent so I'll make it priority #1 So, one thing we can do when we have even a rudimentary test suite Is that we can start testing libraries And so we can start writing libraries [12:32] evanpro: right We could even have a hackathon to implement in a lot of different languages And push implementations to npm, Ruby gems, pypi, etc. evanpro: anyway, maybe now you can see why I was looking at gdbm; if we do have a command line test thing and we *do* promise to deliver tests on side effects we need some way to persist things but I agree there are tests that don't need that Right, I hear you focus on the other stuff first. They seem trivial but they are so important [12:33] Probably the big thing is defining what the interface between testdriver script and the tested script is (and the reason why gdbm is even though it's oldschool, it's also dead easy to get working because it's so "dumb") evanpro: right. Oh, yeah, GDBM is fine there I might suggest using command-line args, too [12:34] evanpro: I get why you had a "don't engineer this, chris!" reaction though :) maybe something like this er overengineer --activity-part actor --part-property id --activity-part=actor --part-property=id [12:35] Those are crummy names but :shrug: That way implementers don't have to write 50 different testing shims evanpro: I hear you evanpro: well, it may even be easier [12:36] It may also be worthwhile to have a producer test --extract ["actor"]["@id"] That takes in some parameters and outputs some JSON Sure I'd be a little worried about defining a query language But yeah evanpro: it's probably equally complex to define a billion arguments So a producer script might take arguments like this for the different components [12:37] agreed! --actor-id=urn:test:whatever --actor-name="Evan Prodromou" --activity-type="Like" --object-id=urn:test:whatever2 --object-name="This terrible test" [12:38] But yeah pretty nightmarish evanpro: so is the idea that this should spit out a success/failure code or Oh, no! It should spit out JSON! just extract the right part? okay evanpro: and it should validate, right? [12:39] dumpscript == take JSON, just spit out some extracted part of it buildscript = take params, spit out JSON oh I see. okay that makes much more sense. echoscript == take json, dump out json sorry ;) dumpscript and buildscript are provided by the implementer to test the implementation [12:40] and there's a test driver to run them so "testdriver dumpscript.py buildscript.py" Would run all the tests Or something like that hm ok.... evanpro: I don't understand testdriver [12:41] what does it do? Something like https://gist.github.com/evanp/5d80c0aa3f168465d84d **** CANCELED dumpscript CLOSED: [2015-10-12 Mon 22:45] dumpscript == take JSON, just spit out some extracted part of it #+BEGIN_SRC python import activitystreams json = parseCommandLineFileArgument() activity = Activity.fromJSON(json) print activity.type #+END_SRC --activity-part=actor --part-property=id --activity-part=actor --part-property=id Those are crummy names but :shrug: That way implementers don't have to write 50 different testing shims evanpro: I hear you evanpro: well, it may even be easier [12:36] It may also be worthwhile to have a producer test --extract ["actor"]["@id"] That takes in some parameters and outputs some JSON Sure I'd be a little worried about defining a query language But yeah evanpro: it's probably equally complex to define a billion arguments So a producer script might take arguments like this for the different components [12:37] agreed! --actor-id=urn:test:whatever --actor-name="Evan Prodromou" --activity-type="Like" --object-id=urn:test:whatever2 --object-name="This terrible test" [12:38] But yeah pretty nightmarish **** CANCELED buildscript CLOSED: [2015-10-12 Mon 22:45] buildscript = take params, spit out JSON **** CANCELED testdriver CLOSED: [2015-10-12 Mon 22:45] so "testdriver dumpscript.py buildscript.py" *** DONE Hook up pyld CLOSED: [2015-10-20 Tue 15:56] :PROPERTIES: :ARCHIVE_TIME: 2015-10-20 Tue 15:58 :END: **** Brainstorm Okay, so what do we want to do here? - Vocabularies might provide an "implied context". That's the biggest issue, because otherwise it can be inferred unambiguously from expanding the document. - Mostly, we might not want to re-read things? This last one is a good goal but maybe we shouldn't worry about it immediately. Here's the options from the JsonLdProcessor code: #+BEGIN_SRC python class JsonLdProcessor(object): """ A JSON-LD processor. """ # [...] def expand(self, input_, options): """ Performs JSON-LD expansion. :param input_: the JSON-LD input to expand. :param options: the options to use. [base] the base IRI to use. [expandContext] a context to expand with. [keepFreeFloatingNodes] True to keep free-floating nodes, False not to (default: False). [documentLoader(url)] the document loader (default: _default_document_loader). :return: the expanded JSON-LD output. """ #+END_SRC - we probably want to be able to set expandContext. - the documentLoader could thus possibly come with some context preloaded. But that's kind of an optimization. At least we know the two main steps now? *Update:* It turns out the first of these is much simpler than we originally were thinking! There's only one implied context in ActivityStreams, so we can hardcode the expandContext. **** DONE Handle the implied context CLOSED: [2015-10-19 Mon 21:26] Should be passed into the environment, but possibly built out of the vocabulary. **** DONE cache things in the documentLoader CLOSED: [2015-10-20 Tue 15:55] The documentLoader seems to just be a function accepting a URI, and raising JsonLdError if something goes badly. #+BEGIN_SRC python { 'contextUrl': None, 'documentUrl': url, 'document': data.decode('utf8') } #+END_SRC So we could write a factory function that takes a mapping of {url: document} #+BEGIN_SRC python def make_simple_loader(url_map, load_unknown_urls=True): def loader(url): # foo return loaded_url return loader #+END_SRC **** DONE Provide a side-effect free environment option CLOSED: [2015-10-20 Tue 15:55] **** DONE Easily build expandContext and documentLoader based on supplied vocabulary? CLOSED: [2015-10-20 Tue 15:56] One way or another we want to reduce the amount of data duplicated from the building of the Environment *** DONE Maybe rename types.py to core.py CLOSED: [2015-10-22 Thu 09:34] :PROPERTIES: :ARCHIVE_TIME: 2015-10-22 Thu 09:35 :END: *** DONE Fix how ASType.__call__() handles long vs short URIs CLOSED: [2015-10-21 Wed 17:39] :PROPERTIES: :ARCHIVE_TIME: 2015-10-22 Thu 09:35 :END: *** DONE ActivityStreams "classes" CLOSED: [2015-10-22 Thu 09:36] :PROPERTIES: :ARCHIVE_TIME: 2015-10-22 Thu 09:36 :END: Note that normal python classes can't work here. **** DONE ASObj CLOSED: [2015-10-22 Thu 09:35] ***** DONE Finish all those TODO methods CLOSED: [2015-10-22 Thu 09:35] ***** Archive :ARCHIVE: ****** DONE Construction: Do deep copy of asjson manually CLOSED: [2015-10-11 Sun 11:33] :PROPERTIES: :ARCHIVE_TIME: 2015-10-12 Mon 15:41 :END: This way we can catch any asobj types ****** DONE Better inheritance order CLOSED: [2015-10-17 Sat 14:05] :PROPERTIES: :ARCHIVE_TIME: 2015-10-17 Sat 14:05 :END: We should do this like in the ANSI Common Lisp book, where we remove duplicates, but we remove duplictes but keep the *last* appearance of a "class" **** Archive :ARCHIVE: ***** DONE Add inheritance / method dispatch system CLOSED: [2015-10-10 Sat 18:49] :PROPERTIES: :ARCHIVE_TIME: 2015-10-10 Sat 18:49 :END: This is trickier than one may think; we can't do Python style method resolution because an activity may have multiple types. ***** DONE Easy ASType->ASObj constructor interface CLOSED: [2015-10-12 Mon 15:14] :PROPERTIES: :ARCHIVE_TIME: 2015-10-12 Mon 15:41 :END: Something like: #+BEGIN_SRC python from activipy import vocab root_beer_note = vocab.Create( actor=vocab.Person( "http://tsyesika.co.uk", displayName="Jessica Tallon"), to=["acct:cwebber@identi.ca"], object=vocab.Note( "http://tsyesika.co.uk/chat/sup-yo/", content="Up for some root beer floats?")) #+END_SRC This should be able to flow pretty naturally out of our types.py interface. *** DONE "environment" w/ method dispatch and object sugar CLOSED: [2015-10-26 Mon 13:49] :PROPERTIES: :ARCHIVE_TIME: 2015-10-26 Mon 13:49 :END: **** Brainstorm So here's how this thing works. There's an environment, which has a mapping between tuples of (method_symbol, Vocab) and method_to_call. #+BEGIN_SRC python # method name description invocation method save = Method("save", "Save things", handle_one) gather_something = Method("gather_something", "Accrues some info", handle_map) myenv = Enviroment( mapping={ (save, Note): note_save, (save, Object): basic_save, }) handle_one(myobj, save, db) #+END_SRC This way, using the inheritance_chain() method, we can handle various types of method handling: - handle_one - handle_map - handle_fold However, we have enough metadata here to provide some sugar. #+BEGIN_SRC python myenv = Environment( mapping={bla bla}, vocab=vocab) activity = Environment.c.Activity("http://oh/snap") activity.m.save(db) # or maybe even just activity.save() #+END_SRC This would have to mean that ASObj gets a method dispatch keyword option on construction, which might be a-ok. I think this is a pretty good approach. **** DONE Add Environment and method dispatch CLOSED: [2015-10-26 Mon 13:48] **** DONE Add vocabulary + method-class sugar CLOSED: [2015-10-26 Mon 13:49] **** Archive :ARCHIVE: ***** DONE Clean up method dispatch plan based on convo w/ steve CLOSED: [2015-10-15 Thu 13:29] :PROPERTIES: :ARCHIVE_TIME: 2015-10-15 Thu 13:31 :END: #+BEGIN_SRC python save_object = Method("save things", "handle_one") myenv = Enviroment( mapping={ (save_object, Note): note_save, }) handle_one(myobj, "save_object", db) handle_one(myobj, save_object, db) # more pythonic optional interface # a bit leaky though myenv = MetaEnviroment( mapping={ (save_object, Note): note_save, } vocab=[BasicVocab] ) myenv.Person("foo") Person() #+END_SRC *** CANCELED Pass environment into methods? CLOSED: [2015-10-28 Wed 17:16] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:19 :END: Should methods be able to themselves take advantage of method dispatch? If so, they will need "env" as first argument. *** DONE Add ASObj.type_astype() CLOSED: [2015-10-28 Wed 17:17] :PROPERTIES: :ARCHIVE_TIME: 2015-10-28 Wed 17:19 :END: **** Brainstorm Here's the problem. Assume we made an activity like this: #+BEGIN_SRC python ROOT_BEER_NOTE_VOCAB = vocab.Create( "http://tsyesika.co.uk/act/foo-id-here/", actor=vocab.Person( "http://tsyesika.co.uk/", displayName="Jessica Tallon"), to=["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk"], object=vocab.Note( "htp://tsyesika.co.uk/chat/sup-yo/", content="Up for some root beer floats?")) #+END_SRC Now assume we made one like this: #+BEGIN_SRC python ROOT_BEER_NOTE_JSOBJ = types.ASObj({ "@type": "Create", "@id": "http://tsyesika.co.uk/act/foo-id-here/", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk"], "object": { "@type": "Note", "@id": "htp://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}}) #+END_SRC Now even worse: #+BEGIN_SRC python ROOT_BEER_NOTE_JSOBJ = types.ASObj({ # AAAAAAAAAAA "@type": "http://www.w3.org/ns/activitystreams#Create", "@id": "http://tsyesika.co.uk/act/foo-id-here/", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk"], "object": { "@type": "Note", "@id": "htp://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}}) #+END_SRC So... - we really need to know about the whole set of vocabularies in order to do ASObj.type_astype() - Obviously, we also need to for method dispatch also - It could be then that we don't load ASObj.vocab, but ASObj.env - Also, in general you can always do env.asobj_astypes(asobj) - Thus, we should also provide env.asobj_method(asobj, method_symbol) - Which means also, more obviously, and as a precedent, we must provide Environment.asobj_astype_chain(asobj)! This also means that users should, in general, not use ASObj.type_astype(), unless they're using the "sugar" edition which comes from supplying an environment. We might want to also provide an expanded=True argument to some of those methods. OR, maybe we can do "cheapest available" determination of an ASType. What are the ways we might go about pulling down an ASType? - By short ID... but this requires this short ID be marked "safe" for short expansion - By already known URI - By json-ld examination (most expensive!) Do we really want an expand=None? Maybe that's kind of dumb **** DONE From short id CLOSED: [2015-10-28 Wed 17:17] The question is, where do we mark whether its safe to consider the short_id as a safe representation from? Is it in the environment or in the vocab? The vocab may make sense because we could do a shortids=load_from_vocabs((Vocab1, None), (GMGVocab, "gmg:")) **** DONE From known URI CLOSED: [2015-10-28 Wed 17:17] **** DONE By json-ld examination CLOSED: [2015-10-28 Wed 17:17] activipy-0.1/activipy/000077500000000000000000000000001261615066400150415ustar00rootroot00000000000000activipy-0.1/activipy/__init__.py000066400000000000000000000016051261615066400171540ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. activipy-0.1/activipy/activitystreams2-context.jsonld000066400000000000000000000156471261615066400232700ustar00rootroot00000000000000{ "@context": { "@vocab": "_:", "xsd": "http://www.w3.org/2001/XMLSchema#", "as": "http://www.w3.org/ns/activitystreams#", "Accept": "as:Accept", "Activity": "as:Activity", "IntransitiveActivity": "as:IntransitiveActivity", "Actor": "as:Actor", "Add": "as:Add", "Album": "as:Album", "Announce": "as:Announce", "Application": "as:Application", "Arrive": "as:Arrive", "Article": "as:Article", "Audio": "as:Audio", "Block": "as:Block", "Collection": "as:Collection", "CollectionPage": "as:CollectionPage", "Relationship": "as:Relationship", "Content": "as:Content", "Create": "as:Create", "Delete": "as:Delete", "Dislike": "as:Dislike", "Document": "as:Document", "Event": "as:Event", "Folder": "as:Folder", "Follow": "as:Follow", "Flag": "as:Flag", "Group": "as:Group", "Ignore": "as:Ignore", "Image": "as:Image", "Invite": "as:Invite", "Join": "as:Join", "Leave": "as:Leave", "Like": "as:Like", "Link": "as:Link", "Mention": "as:Mention", "Note": "as:Note", "Object": "as:Object", "Offer": "as:Offer", "OrderedCollection": "as:OrderedCollection", "OrderedCollectionPage": "as:OrderedCollectionPage", "Organization": "as:Organization", "Page": "as:Page", "Person": "as:Person", "Place": "as:Place", "Process": "as:Process", "Profile": "as:Profile", "Question": "as:Question", "Reject": "as:Reject", "Remove": "as:Remove", "Service": "as:Service", "Story": "as:Story", "TentativeAccept": "as:TentativeAccept", "TentativeReject": "as:TentativeReject", "Undo": "as:Undo", "Update": "as:Update", "Video": "as:Video", "Experience": "as:Experience", "View": "as:View", "Listen": "as:Listen", "Read": "as:Read", "Move": "as:Move", "Travel": "as:Travel", "subject": { "@id": "as:subject", "@type": "@id" }, "relationship": { "@id": "as:relationship", "@type": "@id" }, "actor": { "@id": "as:actor", "@type": "@id" }, "attributedTo": { "@id": "as:attributedTo", "@type": "@id" }, "attachment": { "@id": "as:attachment", "@type": "@id" }, "attachments": { "@id": "as:attachments", "@type": "@id" }, "author": { "@id": "as:author", "@type": "@id" }, "bcc": { "@id": "as:bcc", "@type": "@id" }, "bto": { "@id": "as:bto", "@type": "@id" }, "cc": { "@id": "as:cc", "@type": "@id" }, "context": { "@id": "as:context", "@type": "@id" }, "current": { "@id": "as:current", "@type": "@id" }, "first": { "@id": "as:first", "@type": "@id" }, "generator": { "@id": "as:generator", "@type": "@id" }, "icon": { "@id": "as:icon", "@type": "@id" }, "image": { "@id": "as:image", "@type": "@id" }, "inReplyTo": { "@id": "as:inReplyTo", "@type": "@id" }, "items": { "@id": "as:items", "@type": "@id" }, "instrument": { "@id": "as:instrument", "@type": "@id" }, "orderedItems": { "@id": "as:items", "@type": "@id", "@container": "@list" }, "last": { "@id": "as:last", "@type": "@id" }, "location": { "@id": "as:location", "@type": "@id" }, "next": { "@id": "as:next", "@type": "@id" }, "object": { "@id": "as:object", "@type": "@id" }, "oneOf": { "@id": "as:oneOf", "@type": "@id" }, "anyOf": { "@id": "as:anyOf", "@type": "@id" }, "origin": { "@id": "as:origin", "@type": "@id" }, "accuracy": { "@id": "as:accuracy", "@type": "xsd:float" }, "prev": { "@id": "as:prev", "@type": "@id" }, "preview": { "@id": "as:preview", "@type": "@id" }, "provider": { "@id": "as:provider", "@type": "@id" }, "replies": { "@id": "as:replies", "@type": "@id" }, "result": { "@id": "as:result", "@type": "@id" }, "scope": { "@id": "as:scope", "@type": "@id" }, "partOf": { "@id": "as:partOf", "@type": "@id" }, "tag": { "@id": "as:tag", "@type": "@id" }, "tags": { "@id": "as:tag", "@type": "@id" }, "target": { "@id": "as:target", "@type": "@id" }, "to": { "@id": "as:to", "@type": "@id" }, "url": { "@id": "as:url", "@type": "@id" }, "alias": { "@id": "as:alias", "@type": "@id" }, "altitude": { "@id": "as:altitude", "@type": "xsd:float" }, "content": "as:content", "contentMap": { "@id": "as:content", "@container": "@language" }, "displayName": "as:displayName", "displayNameMap": { "@id": "as:displayName", "@container": "@language" }, "downstreamDuplicates": "as:downStreamDuplicates", "duration": { "@id": "as:duration", "@type": "xsd:nonNegativeInteger" }, "durationIso": { "@id": "as:duration", "@type": "xsd:duration" }, "endTime": { "@id": "as:endTime", "@type": "xsd:dateTime" }, "height": { "@id": "as:height", "@type": "xsd:nonNegativeInteger" }, "href": { "@id": "as:href", "@type": "@id" }, "hreflang": "as:hreflang", "id": "as:id", "latitude": { "@id": "as:latitude", "@type": "xsd:float" }, "longitude": { "@id": "as:longitude", "@type": "xsd:float" }, "mediaType": "as:mediaType", "objectType": "as:objectType", "priority": { "@id": "as:priority", "@type": "xsd:float" }, "published": { "@id": "as:published", "@type": "xsd:dateTime" }, "radius": { "@id": "as:radius", "@type": "xsd:float" }, "rating": { "@id": "as:rating", "@type": "xsd:float" }, "rel": "as:rel", "startIndex": { "@id": "as:startIndex", "@type": "xsd:nonNegativeInteger" }, "startTime": { "@id": "as:startTime", "@type": "xsd:dateTime" }, "summary": "as:summary", "summaryMap": { "@id": "as:summary", "@container": "@language" }, "title": "as:title", "titleMap": { "@id": "as:title", "@container": "@language" }, "totalItems": { "@id": "as:totalItems", "@type": "xsd:nonNegativeInteger" }, "units": "as:units", "updated": { "@id": "as:updated", "@type": "xsd:dateTime" }, "upstreamDuplicates": "as:upstreamDuplicates", "verb": "as:verb", "width": { "@id": "as:width", "@type": "xsd:nonNegativeInteger" }, "describes": { "@id": "as:describes", "@type": "@id" } } } activipy-0.1/activipy/core.py000066400000000000000000000456761261615066400163650ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. from pkg_resources import resource_filename import copy import json from pyld import jsonld # The actual instances of these are defined in vocab.py class ASType(object): """ A @type than an ActivityStreams object might take on. BTW, you might wonder why this isn't using python class heirarchies as an abstraction. The reason is simple: ActivityStreams objects can have multiple types listed under @type. So our inheritance model is a bit different than python's. """ def __init__(self, id_uri, parents, id_short=None, notes=None): self.id_uri = id_uri self.parents = parents self.id_short = id_short self.notes = notes self._inheritance = None def validate(self, asobj): validator = self.methods.get("validate") if validator is not None: validator(asobj) def __repr__(self): return "" % (self.id_short or self.id_uri) # TODO: Use a generic memoizer? @property def inheritance_chain(self): # memoization if self._inheritance is None: self._inheritance = astype_inheritance_list(self) return self._inheritance def __call__(self, id=None, env=None, **kwargs): # @@: Is this okay? Kinda bold! if env is None: from activipy import vocab env = vocab.BasicEnv if self in env.shortids_reversemap: type_val = env.shortids_reversemap[self] else: type_val = self.id_uri jsobj = {"@type": type_val} jsobj.update(kwargs) if id: jsobj["@id"] = id if env: return ASObj(jsobj, env=env) else: return ASObj(jsobj) def astype_inheritance_list(*astypes): """ Gather the inheritance list for an ASType or multiple ASTypes We need this because unlike w/ Python classes, an individual ASObj can have composite types. """ def traverse(astype, family): family.append(astype) for parent in astype.parents: traverse(parent, family) return family # not deduped at this point family = [] for astype in astypes: family = traverse(astype, family) # okay, dedupe here, only keep the oldest instance of each family.reverse() deduped_family = [] for member in family: if member not in deduped_family: deduped_family.append(member) deduped_family.reverse() return deduped_family class ASVocab(object): """ Mapping of known type IDs to ASTypes TODO: Maybe this should include the appropriate context it's working within? """ def __init__(self, vocabs): self.vocab_map = self._map_vocabs(vocabs) def _map_vocabs(self, vocabs): return { type.id_uri: type for type in vocabs} # TODO: Add this one by default AS2_CONTEXT_FILE = resource_filename( 'activipy', 'activitystreams2-context.jsonld') AS2_CONTEXT = json.loads(open(AS2_CONTEXT_FILE, 'r').read()) AS2_CONTEXT_URI = ( "http://www.w3.org/TR/activitystreams-core/activitystreams2-context.jsonld") AS2_DEFAULT_URL_MAP = { AS2_CONTEXT_URI: AS2_CONTEXT} # Once things are cached, json-ld expansion seems to happen at about # 1250 douments / second on my laptop def make_simple_loader(url_map, load_unknown_urls=True, cache_externally_loaded=True): def _make_context(url, doc): return { "contextUrl": None, "documentUrl": url, "document": doc} # Wrap in the structure that's expected to come back from the # documentLoader _pre_url_map = {} _pre_url_map.update(AS2_DEFAULT_URL_MAP) _pre_url_map.update(url_map) _url_map = { url: _make_context(url, doc) for url, doc in _pre_url_map.items()} def loader(url): if url in _url_map: return _url_map[url] elif load_unknown_urls: doc = jsonld.load_document(url) # @@: Is this optimization safe in all cases? if isinstance(doc["document"], str): doc["document"] = json.loads(doc["document"]) if cache_externally_loaded: _url_map[url] = doc return doc else: raise jsonld.JsonLdError( "url not found and loader set to not load unknown URLs.", {'url': url}) return loader default_loader = make_simple_loader({}) # TODO: This was a good early in-comments braindump; now move to the # documentation and restructure! # So, questions for ourselves. What is this, if not merely a json # object? After all, an ActivityStreams object can be represented as # "just JSON", and be done with it. So what's *useful*? # # Here are some potentially useful properties: # - Expanded json-ld form # - Extracted types # - As short forms # - As expanded / unambiguous URIs (see json-ld) # - As ASType objects (where possible) # - Validation # - Lookup of what a property key "means" # (checking against activitystreams vocabulary) # - key-value access, including fetching any nested activitystreams # objects as ASObj types # - json serialization to string # # Of all the above, it would be nice not to have to repeat these # operations. If we've done it once, that should be good enough # forever... in other words, memoization. But memoization means # that the object should be immutable. # # ... but maybe ASObj objects *should* be immutable. # This means we copy.deepcopy() on our way in, and if users want # to change things, they either make a new ASObj or get back # entirely new ASObj objects. # # I like this idea... class ASObj(object): """ The general ActivityStreams object that a user will work with """ def __init__(self, jsobj, env=None): if not env: from activipy import vocab env = vocab.BasicEnv self.env = env self.__jsobj = deepcopy_jsobj_in(jsobj, env) assert (isinstance(self.__jsobj.get("@type"), str) or isinstance(self.__jsobj.get("@type"), list)) self.m = self.env._build_m_map(self) def __getitem__(self, key): val = self.__jsobj[key] if isinstance(val, dict) and "@type" in val: return ASObj(val, self.env) else: return deepcopy_jsobj_out(val, env=self.env) # META TODO: Convert some @property here to @memoized_property @property def types(self): type_attr = self["@type"] if isinstance(self["@type"], list): return type_attr else: return [type_attr] @property def types_expanded(self): return copy.deepcopy(self.__expanded()[0]["@type"]) # TODO: Memoize @property def types_astype(self): return self.env.asobj_astypes(self) # TODO: Memoize @property def types_inheritance(self): return self.env.asobj_astype_inheritance(self) # Don't memoize this, users might mutate def json(self): return copy.deepcopy(self.__jsobj) # TODO: Memoize def json_str(self): return json.dumps(self.json()) # TODO Memoize def __expanded(self): if self.env.document_loader: document_loader = self.env.document_loader else: document_loader = default_loader options = {"expandContext": self.env.implied_context} if document_loader: options["documentLoader"] = document_loader return jsonld.expand(self.__jsobj, options) def expanded(self): """ Note: this produces a copy of the object returned, so consumers of this method may want to keep a copy of its result rather than calling over and over. """ return copy.deepcopy(self.__expanded()) # TODO: Memoize def expanded_str(self): return json.dumps(self.expanded()) @property def id(self): return self.__jsobj.get("@id") def __repr__(self): if self.id: return "" % ( ", ".join(self.types), self.id) else: return "" % ", ".join(self.types) def deepcopy_jsobj_base(jsobj, env, going_in=True): """ Perform a deep copy of a JSON style object """ going_out = not going_in def add_context(this_dict): if env.extra_context is not None: this_dict["@context"] = env.extra_context def remove_context(this_dict): if "@context" in this_dict: del this_dict["@context"] return this_dict def copy_asobj(asobj): if going_in: return remove_context(asobj.json()) else: return asobj def copy_dict(this_dict): # Looks like an ASObj if going_out and "@type" in this_dict: return ASObj(this_dict, env) # Otherwise, just recursively copy the dict new_dict = {} for key, val in this_dict.items(): new_dict[key] = copy_main(val) return new_dict def copy_list(this_list): new_list = [] for item in this_list: new_list.append(copy_main(item)) return new_list def copy_main(jsobj): if isinstance(jsobj, dict): return copy_dict(jsobj) elif isinstance(jsobj, ASObj): return copy_asobj(jsobj) elif isinstance(jsobj, list): return copy_list(jsobj) else: # All other JSON type objects are immutable, # just copy them down. # @@: We could provide validation that it's # a valid json object here but that seems like # it would bring unnecessary performance penalties. return jsobj if going_in: # Should be a dictionary or ASObj on the way in for this assert isinstance(jsobj, dict) or isinstance(jsobj, ASObj) final_json = copy_main(jsobj) if going_in: add_context(final_json) return final_json def deepcopy_jsobj_in(jsobj, env): return deepcopy_jsobj_base(jsobj, env, going_in=True) def deepcopy_jsobj_out(jsobj, env): return deepcopy_jsobj_base(jsobj, env, going_in=False) # @@: Maybe rename to MethodSpec? class MethodId(object): # TODO: fill in """ A method identifier """ def __init__(self, name, description, handler): self.name = name self.description = description self.handler = handler def __repr__(self): return "" % self.name class NoMethodFound(Exception): pass def throw_no_method_error(asobj): raise NoMethodFound("Could not find a method for type: %s" % ( ", ".join(asobj.types))) def handle_one(astype_methods, asobj, _fallback=throw_no_method_error): if len(astype_methods) == 0: _fallback(asobj) def func(*args, **kwargs): method, astype = astype_methods[0] return method(asobj, *args, **kwargs) return func def handle_map(astype_methods, asobj): def func(*args, **kwargs): return [method(asobj, *args, **kwargs) for method, astype in astype_methods] return func class HaltIteration(object): def __init__(self, val): self.val = val def handle_fold(astype_methods, asobj): def func(initial=None, *args, **kwargs): val = initial for method, astype in astype_methods: # @@: Not sure if asobj or val coming first is a better interface... val = method(asobj, val, *args, **kwargs) # Provide a way to break out of the loop early...? # @@: Is this a good idea, or even useful for anything? if isinstance(val, HaltIteration): val = val.val break return val return func # TODO # @@: Can this be just an @property on Environment? class AttrMapper(object): def __init__(self, attrib_map): for key, val in attrib_map.items(): setattr(self, key, val) class TypeConstructor(object): def __init__(self, astype, env): self.astype = astype self.__env = env def __call__(self, *args, **kwargs): return self.astype(env=self.__env, *args, **kwargs) def __repr__(self): return "" % self.astype.__repr__() class EnvironmentMismatch(Exception): """ Raised when an ASObj calls a method through an Environment but does not have that environment bound to itself. """ pass class Environment(object): """ An environment to collect vocabularies and provide methods for activitystream types """ implied_context = AS2_CONTEXT_URI def __init__(self, vocabs=None, methods=None, # not ideal, I'd rather somehow load something # that uses the vocabs as passed in, but that # introduces its own complexities shortids=None, c_accessors=None, extra_context=None, document_loader=default_loader): self.vocabs = vocabs or [] self.methods = methods or {} # @@: Should we make all short ids mandatorily contain # the base schema? self.shortids = shortids or {} self.shortids_reversemap = { val: key for key, val in self.shortids.items()} self.extra_context = extra_context self.document_loader = document_loader self.c = self.__build_c_accessors(c_accessors or {}) self.m = self._build_m_map() self.uri_map = self.__build_uri_map() def __build_c_accessors(self, c_accessors): return AttrMapper( {name: TypeConstructor(astype, self) for name, astype in c_accessors.items()}) def _build_m_map(self, asobj=None): def make_method_dispatcher(method_id): def method_dispatcher(asobj, *args, **kwargs): method = self.asobj_get_method(asobj, method_id) return method(*args, **kwargs) if asobj is None: return method_dispatcher else: # in this variation, we already know what the # asobj is def curried_method_dispatcher(*args, **kwargs): return method_dispatcher(asobj, *args, **kwargs) return curried_method_dispatcher method_ids = set([method_id for (method_id, astype) in self.methods.keys()]) m_mapping = { method_id.name: make_method_dispatcher(method_id) for method_id in method_ids} return AttrMapper(m_mapping) def __build_uri_map(self): uri_map = {} for vocab in self.vocabs: uri_map.update(vocab.vocab_map) return uri_map def _process_type_simple(self, type_id): # Try by short ID (in short IDs marked as acceptable for this) if type_id in self.shortids: return self.shortids[type_id] # Try by URI elif type_id in self.uri_map: return self.uri_map[type_id] else: # this would happen anyway, but might as well be explicit # about what's happening here in the code flow return None def asobj_astypes(self, asobj): final_types = [] process_as_jsonld = False for type_id in asobj.types: processed_type = self._process_type_simple(type_id) if processed_type is not None: final_types.append(processed_type) else: # We have to bail out process_as_jsonld = True break # Are there any remaining types to process here? if process_as_jsonld: # @@: We could do a version of this which didn't # throw away the information we already had, # maybe. But it would be tricky. final_types = [] asobj_jsonld = asobj.expanded() for type_uri in asobj_jsonld[0]["@type"]: processed_type = self._process_type_simple(type_uri) if processed_type is not None: final_types.append(processed_type) return final_types def asobj_astype_inheritance(self, asobj): return astype_inheritance_list( *self.asobj_astypes(asobj)) def is_astype(self, asobj, astype, inherit=True): """ Check to see if an ASObj is of ASType; check full inheritance chain """ if not isinstance(asobj, ASObj): return False if inherit: return astype in self.asobj_astype_inheritance(asobj) else: return astype in self.asobj_astypes(asobj) # @@: Should we drop the asobj_ from these method names? def asobj_get_method(self, asobj, method): if asobj.env is not self: raise EnvironmentMismatch( "ASObj attempted to call method with an Environment " "it was not bound to!") # get all types for this asobj astypes = self.asobj_astype_inheritance(asobj) # get a map of all relevant {method_proc: astype} return method.handler( [(self.methods[(method, astype)], astype) for astype in astypes if (method, astype) in self.methods], asobj) def asobj_run_method(self, asobj, method, *args, **kwargs): # make note of why arguments make this slightly lossy # when passing on; eg, can't use asobj/method in the # arguments to this function return self.asobj_get_method(asobj, method)(*args, **kwargs) def shortids_from_vocab(vocab, prefix=None): """ Get a mapping of all short ids to their ASType objects in a vocab Useful for mapping shortids to ASType objects! """ def maybe_add_prefix(id_short): if prefix: return "%s:%s" % (prefix, id_short) else: return id_short return { maybe_add_prefix(v.id_short): v for v in vocab.vocab_map.values()} def chain_dicts(*dicts): """ Chain together a series of dictionaries into one """ final_dict = {} for this_dict in dicts: final_dict.update(this_dict) return final_dict activipy-0.1/activipy/demos/000077500000000000000000000000001261615066400161505ustar00rootroot00000000000000activipy-0.1/activipy/demos/__init__.py000066400000000000000000000000001261615066400202470ustar00rootroot00000000000000activipy-0.1/activipy/demos/checkup.py000066400000000000000000000063661261615066400201570ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. from activipy.core import ( ASType, ASVocab, Environment, shortids_from_vocab, chain_dicts, make_simple_loader) from activipy import vocab def checkup_uri(identifier): return "http://checkup.example/ns#" + identifier CheckIn = ASType( checkup_uri("CheckIn"), [vocab.Arrive], "CheckIn", notes=( "Check in to a location.")) Coupon = ASType( checkup_uri("Coupon"), [vocab.Object], "Coupon", notes=( "A redeemable voucher (by redeem_url) for hopefully " "something exciting!")) RoyalStatus = ASType( checkup_uri("RoyalStatus"), [vocab.Object], "RoyalStatus", notes=( "How royal you are at a given location!")) CheckUpVocab = ASVocab([CheckIn, Coupon, RoyalStatus]) CHECKUP_EXTRA_CONTEXT_NAMESPACED = {"CheckUp": "http://checkup.example/ns#"} CheckUpNSEnv = Environment( vocabs=[vocab.CoreVocab], shortids=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab, "CheckUp")), c_accessors=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab)), extra_context=CHECKUP_EXTRA_CONTEXT_NAMESPACED) CHECKUP_EXTRA_CONTEXT_VERBOSE = { "CheckIn": { "@id": CheckIn.id_uri, "@type": "@id"}, "Coupon": { "@id": Coupon.id_uri, "@type": "@id"}, "RoyalStatus": { "@id": RoyalStatus.id_uri, "@type": "@id"}} CheckUpVerboseEnv = Environment( vocabs=[vocab.CoreVocab], shortids=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab)), c_accessors=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab)), extra_context=CHECKUP_EXTRA_CONTEXT_VERBOSE) CHECKUP_EXTRA_CONTEXT_URI = "http://checkup.example/context.jld" CHECKUP_JSONLD_LOADER = make_simple_loader( {CHECKUP_EXTRA_CONTEXT_URI: {"@context": CHECKUP_EXTRA_CONTEXT_VERBOSE}}, # @@: Do we want this in the demo? load_unknown_urls=False) CheckUpEnv = Environment( vocabs=[vocab.CoreVocab], shortids=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab)), c_accessors=chain_dicts( shortids_from_vocab(vocab.CoreVocab), shortids_from_vocab(CheckUpVocab)), extra_context=CHECKUP_EXTRA_CONTEXT_URI, document_loader=CHECKUP_JSONLD_LOADER) activipy-0.1/activipy/demos/dbm.py000066400000000000000000000121551261615066400172700ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. import json import dbm from activipy import core, vocab class JsonDBM(object): """ json wrapper around a gdbm database """ def __init__(self, db): self.db = db def __getitem__(self, key): return json.loads(self.db[key.encode('utf-8')].decode('utf-8')) def __setitem__(self, key, value): self.db[key.encode('utf-8')] = json.dumps(value) def __delitem__(self, key): del self.db[key.encode('utf-8')] def __contains__(self, key): return key in self.db @classmethod def open(cls, filename): return cls(dbm.open(filename, 'c')) def close(self): self.db.close() def get(self, key, default=None): if key in self.db: return self[key] else: return default def fetch_asobj(self, env): return core.ASObj(self[id], env) # Each of these returns the full object inserted into dbm def dbm_fetch(id, db, env): return core.ASObj(db[id], env) def dbm_save(asobj, db): assert asobj.id is not None new_val = asobj.json() db[asobj.id] = new_val return new_val def dbm_delete(asobj, db): assert asobj.id is not None del db[asobj.id] dbm_save_method = core.MethodId( "save", "Save object to the DBM store.", core.handle_one) dbm_delete_method = core.MethodId( "delete", "Delete object from the DBM store.", core.handle_one) DbmEnv = core.Environment( vocabs=[vocab.CoreVocab], methods={ (dbm_save_method, vocab.Object): dbm_save, (dbm_delete_method, vocab.Object): dbm_delete}, shortids=core.shortids_from_vocab(vocab.CoreVocab), c_accessors=core.shortids_from_vocab(vocab.CoreVocab)) def dbm_activity_normalized_save(asobj, db): assert asobj.id is not None as_json = asobj.json() def maybe_normalize(key): val = as_json.get(key) # Skip if not a dictionary with a "@type" if not isinstance(val, dict) or not "@type" in val: return val_asobj = core.ASObj(val, asobj.env) # yup, time to normalize if asobj.env.is_astype(val_asobj, vocab.Object, inherit=True): # If there's no id, then okay, don't normalize if val_asobj.id is None: return if val_asobj.id not in db: # save to the database asobj.env.asobj_run_method(val_asobj, dbm_save_method, db) # and set the key to be the .id as_json[key] = val_asobj.id maybe_normalize("actor") maybe_normalize("object") maybe_normalize("target") db[asobj.id] = as_json return as_json dbm_denormalize_method = core.MethodId( "denormalize", "Expand out an activitystreams object recursively", # @@: Should this be a handle_fold? core.handle_one) def dbm_denormalize_object(asobj, db): # For now, on any standard object, just return that as-is return asobj def dbm_denormalize_activity(asobj, db): as_json = asobj.json() def maybe_denormalize(key): val = as_json.get(key) # If there's no specific val, # it's not a string, or it's not in the database, # just leave it! if val is None or not isinstance(val, str) or val not in db: return # Otherwise, looks like that value *is* in the database... hey! # Let's pull it out and set it as the key. as_json[key] = db[val] maybe_denormalize("actor") maybe_denormalize("object") maybe_denormalize("target") return core.ASObj(as_json, asobj.env) DbmNormalizedEnv = core.Environment( vocabs=[vocab.CoreVocab], methods={ (dbm_save_method, vocab.Object): dbm_save, (dbm_save_method, vocab.Activity): dbm_activity_normalized_save, (dbm_delete_method, vocab.Object): dbm_delete, (dbm_denormalize_method, vocab.Object): dbm_denormalize_object, (dbm_denormalize_method, vocab.Activity): dbm_denormalize_activity}, shortids=core.shortids_from_vocab(vocab.CoreVocab), c_accessors=core.shortids_from_vocab(vocab.CoreVocab)) def dbm_fetch_denormalized(id, db, env): """ Fetch a fully denormalized ASObj from the database. """ return env.asobj_run_method( dbm_fetch(id, db, env), dbm_denormalize_method, db) activipy-0.1/activipy/testcli.py000066400000000000000000000056701261615066400170720ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. import json import sys import argparse from collections import namedtuple, OrderedDict from .core import Activity, InvalidActivity class UserError(Exception): pass class InvalidInput(UserError): pass # Dump command # ============ def dump_cli(args): try: asobj = json.loads(args.asobj) except ValueError: raise InvalidInput( "Not valid json: %s" % args.asobj) activity = Activity.from_json(asobj) try: activity.validate() except InvalidActivity as error: raise InvalidInput(str(error)) def dump_setup_subparser(subparser): subparser.add_argument( "asobj", help="ActivityStreams object, as json") # Build command # ============== def build_cli(): pass def build_setup_subparser(subparser): pass # Testdriver command # ================== def testdriver_cli(): pass def testdriver_setup_subparser(subparser): pass # Build CLI # ========= Command = namedtuple("command", ["cli_proc", "setup_subparser"]) SUBCOMMANDS_MAP = OrderedDict([ ("dump", Command( dump_cli, dump_setup_subparser)), ("build", Command( build_cli, build_setup_subparser)), ("testdriver", Command( testdriver_cli, testdriver_setup_subparser))]) def main(): parser = argparse.ArgumentParser( # @@: this sucks as a description description="Test for activitystreams correctness") subparsers = parser.add_subparsers(dest="subparser_name") for subcommand_key, subcommand_cmd in SUBCOMMANDS_MAP.items(): subcmd_parser = subparsers.add_parser(subcommand_key) subcommand_cmd.setup_subparser(subcmd_parser) args = parser.parse_args() if not args.subparser_name: parser.print_help() sys.exit(1) try: subcmd_proc = SUBCOMMANDS_MAP[args.subparser_name].cli_proc subcmd_proc(args) except UserError as error: print(error) # it's only half evil that the user made this mistake, # is my guess sys.exit(333) if __name__ == "__main__": main() activipy-0.1/activipy/tests/000077500000000000000000000000001261615066400162035ustar00rootroot00000000000000activipy-0.1/activipy/tests/test_core.py000066400000000000000000000377141261615066400205600ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and validator for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. import copy import pytest from activipy import core, vocab # Fake inheritance tree below def fake_type_uri(type_name): return "http://example.org/ns#" + type_name ASObject = core.ASType(fake_type_uri("object"), [], "Object") ASLink = core.ASType(fake_type_uri("link"), [], "Link") ASActivity = core.ASType(fake_type_uri("activity"), [ASObject], "Activity") ASPost = core.ASType(fake_type_uri("post"), [ASActivity], "Post") ASDelete = core.ASType(fake_type_uri("delete"), [ASActivity], "Delete") ASCollection = core.ASType( fake_type_uri("collection"), [ASObject], "Collection") ASOrderedCollection = core.ASType( fake_type_uri("orderedcollection"), [ASCollection], "OrderedCollection") ASCollectionPage = core.ASType( fake_type_uri("collectionpage"), [ASCollection], "CollectionPage") ASOrderedCollectionPage = core.ASType( fake_type_uri("orderedcollectionpage"), [ASOrderedCollection, ASCollectionPage], "OrderedCollectionPage") # BS testing widget ASWidget = core.ASType( fake_type_uri("widget"), [ASObject], "Widget") ASFancyWidget = core.ASType( fake_type_uri("fancywidget"), [ASWidget], "FancyWidget") # Example vocab and environment ExampleVocab = core.ASVocab( [ASObject, ASLink, ASActivity, ASPost, ASDelete, ASCollection, ASOrderedCollection, ASCollectionPage, ASOrderedCollectionPage, ASWidget, ASFancyWidget]) ExampleEnv = core.Environment( vocabs=[ExampleVocab], shortids=core.shortids_from_vocab(ExampleVocab), c_accessors=core.shortids_from_vocab(ExampleVocab)) # Basic tests def test_inheritance_list(): # Should just be itself assert core.astype_inheritance_list(ASObject) == \ [ASObject] assert core.astype_inheritance_list(ASLink) == \ [ASLink] # Should be itself, then its parent assert core.astype_inheritance_list(ASActivity) == \ [ASActivity, ASObject] assert core.astype_inheritance_list(ASCollection) == \ [ASCollection, ASObject] # A slightly longer inheritance chain assert core.astype_inheritance_list(ASPost) == \ [ASPost, ASActivity, ASObject] assert core.astype_inheritance_list(ASDelete) == \ [ASDelete, ASActivity, ASObject] assert core.astype_inheritance_list(ASOrderedCollection) == \ [ASOrderedCollection, ASCollection, ASObject] assert core.astype_inheritance_list(ASCollectionPage) == \ [ASCollectionPage, ASCollection, ASObject] # Multiple inheritance! Egads. # ... this clearly demonstrates our present depth-first # traversal. A breadth-first traversal would mean # ASCollectionPage would go before ASCollection, which may be more # to a user's expectations. assert core.astype_inheritance_list(ASOrderedCollectionPage) == \ [ASOrderedCollectionPage, ASOrderedCollection, ASCollectionPage, ASCollection, ASObject,] # does the property version also work? assert ASOrderedCollectionPage.inheritance_chain == \ [ASOrderedCollectionPage, ASOrderedCollection, ASCollectionPage, ASCollection, ASObject] # What about composite core assert core.astype_inheritance_list( ASFancyWidget, ASOrderedCollectionPage) == [ ASFancyWidget, ASWidget, ASOrderedCollectionPage, ASOrderedCollection, ASCollectionPage, ASCollection, ASObject] ROOT_BEER_NOTE_JSOBJ = { "@type": "Create", "@id": "http://tsyesika.co.uk/act/foo-id-here/", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], "object": { "@type": "Note", "@id": "http://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}} ROOT_BEER_NOTE_MIXED_ASOBJ = { "@type": "Create", "@id": "http://tsyesika.co.uk/act/foo-id-here/", "actor": core.ASObj({ "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}), "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], "object": core.ASObj({ "@type": "Note", "@id": "http://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"})} ROOT_BEER_NOTE_VOCAB = vocab.Create( "http://tsyesika.co.uk/act/foo-id-here/", actor=vocab.Person( "http://tsyesika.co.uk/", displayName="Jessica Tallon"), to=["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], object=vocab.Note( "http://tsyesika.co.uk/chat/sup-yo/", content="Up for some root beer floats?")) def _looks_like_root_beer_note(jsobj): return ( len(jsobj) == 5 and jsobj["@type"] == "Create" and jsobj["@id"] == "http://tsyesika.co.uk/act/foo-id-here/" and isinstance(jsobj["actor"], dict) and len(jsobj["actor"]) == 3 and jsobj["actor"]["@type"] == "Person" and jsobj["actor"]["@id"] == "http://tsyesika.co.uk/" and jsobj["actor"]["displayName"] == "Jessica Tallon" and isinstance(jsobj["to"], list) and jsobj["to"] == ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"] and isinstance(jsobj["object"], dict) and len(jsobj["object"]) == 3 and jsobj["object"]["@type"] == "Note" and jsobj["object"]["@id"] == "http://tsyesika.co.uk/chat/sup-yo/" and jsobj["object"]["content"] == "Up for some root beer floats?") def test_deepcopy_jsobj_in(): # We'll mutate later, so let's make a copy of this root_beer_note = copy.deepcopy(ROOT_BEER_NOTE_JSOBJ) assert _looks_like_root_beer_note(root_beer_note) # Test copying a compicated datastructure copied_root_beer_note = core.deepcopy_jsobj_in( root_beer_note, vocab.BasicEnv) assert _looks_like_root_beer_note(copied_root_beer_note) # Mutate root_beer_note["to"].append("sneaky@mcsneakers.example") assert not _looks_like_root_beer_note(root_beer_note) # but things are still as they were in our copy right?? assert _looks_like_root_beer_note(copied_root_beer_note) # Test nested asobj copying assert _looks_like_root_beer_note( core.deepcopy_jsobj_in( ROOT_BEER_NOTE_MIXED_ASOBJ, vocab.BasicEnv)) # TODO: test_deepcopy_jsobj_out(): def test_vocab_constructor(): assert isinstance( ROOT_BEER_NOTE_VOCAB, core.ASObj) assert _looks_like_root_beer_note( ROOT_BEER_NOTE_VOCAB.json()) ROOT_BEER_NOTE_ASOBJ = core.ASObj({ "@type": "Create", "@id": "http://tsyesika.co.uk/act/foo-id-here/", "actor": core.ASObj({ "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}), "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk"], "object": core.ASObj({ "@type": "Note", "@id": "http://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"})}) def test_asobj_keyaccess(): assert ROOT_BEER_NOTE_ASOBJ["@type"] == "Create" assert ROOT_BEER_NOTE_ASOBJ["@id"] == \ "http://tsyesika.co.uk/act/foo-id-here/" # Accessing things that look like asobjects # will return asobjects assert isinstance( ROOT_BEER_NOTE_ASOBJ["object"], core.ASObj) # However, we should still be able to get the # dictionary edition by pulling down .json() assert isinstance( ROOT_BEER_NOTE_ASOBJ.json()["object"], dict) # Traversal of traversal should work assert ROOT_BEER_NOTE_ASOBJ["object"]["content"] == \ "Up for some root beer floats?" def test_handle_one(): # Fake value boxes received_args = [] received_kwargs = [] def test_method1(*args, **kwargs): received_args.append(args) received_kwargs.append(kwargs) return "Got it!" def test_method2(*args, **kwargs): # we should never hit this assert False return "skip me!" # well if we got this far we never hit test_method2, which is good # did we run test_method1 then? our_jsobj = ASWidget(foo="bar") handler = core.handle_one( [(test_method1, ASWidget), (test_method2, ASObject)], our_jsobj) assert handler(1, 2, foo="bar") == "Got it!" assert received_args[0] == (our_jsobj, 1, 2) assert received_kwargs[0] == {"foo": "bar"} # Okay, let's try running with no methods available with pytest.raises(core.NoMethodFound): core.handle_one([], our_jsobj) # Let's try running something with a custom fallback... # ... this fallback drinks the half empty glass glass = ["half_empty"] def drink_glass(asobj): # down the hatch glass.pop() core.handle_one([], our_jsobj, _fallback=drink_glass) assert len(glass) == 0 # if this passes, the pessimists win def test_handle_map(): def test_one(*args, **kwargs): return (1, args, kwargs) def test_two(*args, **kwargs): return (2, args, kwargs) def test_three(*args, **kwargs): return (3, args, kwargs) our_asobj = ASWidget(snorf="snizzle") handler = core.handle_map([(test_one, ASFancyWidget), (test_two, ASWidget), (test_three, ASObject)], our_asobj) result = handler("one", "two", "three", lets="go!") assert result[0][0] == 1 assert result[1][0] == 2 assert result[2][0] == 3 for r in result: assert r[1] == (our_asobj, "one", "two", "three") assert r[2] == {"lets": "go!"} def test_handle_fold(): def test_one(asobj, val, location): return val + "one %s... " % location def test_two(asobj, val, location): return val + "two %s... " % location def test_three(asobj, val, location): return val + "three %s... " % location our_asobj = ASWidget(snorf="snizzle") handler = core.handle_fold([(test_one, ASFancyWidget), (test_two, ASWidget), (test_three, ASObject)], our_asobj) result = handler("Counting down! ", "mississippi") assert result == ( "Counting down! one mississippi... " "two mississippi... three mississippi... ") # Now test breaking out early def test_two_breaks_out(asobj, val, location): return core.HaltIteration(val + "two %s... " % location) handler = core.handle_fold([(test_one, ASFancyWidget), (test_two_breaks_out, ASWidget), (test_three, ASObject)], our_asobj) result = handler("Counting down! ", "mississippi") # we never get to three in this version assert result == ( "Counting down! one mississippi... " "two mississippi... ") # Methods testing # =============== ### begin testing methods ### save = core.MethodId("save", "Save things", core.handle_one) get_things = core.MethodId("get_things", "Build up a map of stuff", core.handle_map) # This one's name intentionally different than its variable name # for testing reasons combine_things = core.MethodId("combine", "combine things", core.handle_fold) def _object_save(asobj, db): db[asobj["@id"]] = ("saved as object", asobj) def _widget_save(asobj, db): db[asobj["@id"]] = ("saved as widget", asobj) def _object_get_things(asobj): return "objects are fun" def _activity_get_things(asobj): return "activities are neat" def _post_get_things(asobj): return "posts are cool" def _collection_combine_things(asobj, val, chant): return val + chant + ", my friend, and remember us collected\n" def _ordered_collection_combine_things(asobj, val, chant): return val + chant + ", my dear, and cherish the order\n" def _ordered_collection_page_combine_things(asobj, val, chant): return val + chant + ", my sweet, a new page is turning\n" ### end testing methods ### MethodEnv = core.Environment( vocabs=[ExampleVocab], methods={ (save, ASObject): _object_save, (save, ASWidget): _widget_save, (get_things, ASObject): _object_get_things, (get_things, ASActivity): _activity_get_things, (get_things, ASPost): _post_get_things, (combine_things, ASCollection): _collection_combine_things, (combine_things, ASOrderedCollection): ( _ordered_collection_combine_things), (combine_things, ASOrderedCollectionPage): ( _ordered_collection_page_combine_things)}, shortids=core.shortids_from_vocab(ExampleVocab), c_accessors=core.shortids_from_vocab(ExampleVocab)) def test_environment_c_access(): widget = MethodEnv.c.Widget(foo="bar") assert widget["foo"] == "bar" assert widget.types_astype == [ASWidget] assert widget.env == MethodEnv assert MethodEnv.c.Widget.astype == ASWidget def test_environment_m_access_handle_one(): db = {} widget = MethodEnv.c.Widget("fooid:12345") MethodEnv.m.save(widget, db) assert db["fooid:12345"] == ("saved as widget", widget) foo_object = MethodEnv.c.Object("fooid:00001") MethodEnv.m.save(foo_object, db) assert db["fooid:00001"] == ("saved as object", foo_object) collection = MethodEnv.c.Collection("fooid:8888") MethodEnv.m.save(collection, db) assert db["fooid:8888"] == ("saved as object", collection) def test_environment_m_access_handle_map(): result = MethodEnv.m.get_things(MethodEnv.c.Widget("fooid:12345")) assert result == ["objects are fun"] result = MethodEnv.m.get_things(MethodEnv.c.Activity("fooid:0202")) assert result == ["activities are neat", "objects are fun"] result = MethodEnv.m.get_things(MethodEnv.c.Delete("fooid:0303")) assert result == ["activities are neat", "objects are fun"] result = MethodEnv.m.get_things(MethodEnv.c.Post("fooid:0808")) assert result == ["posts are cool", "activities are neat", "objects are fun"] def test_environment_m_access_handle_fold(): # This test function is kinda disturbing result = MethodEnv.m.combine(MethodEnv.c.Collection("fooid:1"), "", "Huzzah") assert result == "Huzzah, my friend, and remember us collected\n" result = MethodEnv.m.combine(MethodEnv.c.OrderedCollection("fooid:2"), "", "Huzzah") assert result == ( "Huzzah, my dear, and cherish the order\n" "Huzzah, my friend, and remember us collected\n") result = MethodEnv.m.combine(MethodEnv.c.OrderedCollectionPage("fooid:2"), "", "Rejoice") assert result == ( "Rejoice, my sweet, a new page is turning\n" "Rejoice, my dear, and cherish the order\n" "Rejoice, my friend, and remember us collected\n") # nothing for this one result = MethodEnv.m.combine(MethodEnv.c.Widget("fooid:12345"), "", "Huzzah") assert result == "" activipy-0.1/activipy/vocab.py000066400000000000000000000457371261615066400165250ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and testing for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2 here's those headers ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. ## ## In addition, this page copies huge swaths of documentation from the ## ActivityStreams 2.0 Vocabulary document, ## http://www.w3.org/TR/activitystreams-vocabulary/ ## ## Copyright © 2015 Activity Streams Working Group, IBM & W3C® (MIT, ## ERCIM, Keio, Beihang). W3C liability, trademark and permissive ## document license rules apply. ## ## which is released under the ## "W3C Software and Document Notice and License": ## ## This work is being provided by the copyright holders under the ## following license. ## ## License ## ------- ## ## By obtaining and/or copying this work, you (the licensee) agree ## that you have read, understood, and will comply with the ## following terms and conditions. ## ## Permission to copy, modify, and distribute this work, with or ## without modification, for any purpose and without fee or royalty ## is hereby granted, provided that you include the following on ## ALL copies of the work or portions thereof, including ## modifications: ## ## - The full text of this NOTICE in a location viewable to users ## of the redistributed or derivative work. ## - Any pre-existing intellectual property disclaimers, notices, ## or terms and conditions. If none exist, the W3C Software and ## Document Short Notice should be included. ## - Notice of any changes or modifications, through a copyright ## statement on the new code or document such as "This software ## or document includes material copied from or derived from ## [title and URI of the W3C document]. Copyright © [YEAR] W3C® ## (MIT, ERCIM, Keio, Beihang)." ## ## Disclaimers ## ----------- ## ## THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO ## REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT ## NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY ## PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT ## WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, ## TRADEMARKS OR OTHER RIGHTS. ## ## COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, ## SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE ## SOFTWARE OR DOCUMENT. ## ## The name and trademarks of copyright holders may NOT be used in ## advertising or publicity pertaining to the work without ## specific, written prior permission. Title to copyright in this ## work will at all times remain with copyright holders. from .core import ASType from .core import ASVocab, Environment, shortids_from_vocab def as_uri(identifier): return "http://www.w3.org/ns/activitystreams#" + identifier # Core classes # ============ Object = ASType( as_uri("Object"), [], "Object", notes=( "Describes an object of any kind. " "The Object class serves as the base class for most of the " "other kinds of objects defined in the Activity Vocabulary, " "include other Core classes such as Activity, " "IntransitiveActivity, Actor, Collection and OrderedCollection.")) Link = ASType( as_uri("Link"), [], "Link", notes=( "A Link is an indirect, qualified reference to a resource identified by" "a URL. The fundamental model for links is established by [RFC5988]. " "Many of the properties defined by the Activity Vocabulary allow " "values that are either instances of Object or Link. When a Link is " "used, it establishes a qualified relation connecting the subject " "(the containing object) to the resource identified by the href.")) Activity = ASType( as_uri("Activity"), [Object], "Activity", notes=( "An Activity is a subclass of Object that describes some form of " "action that may happen, is currently happening, or has already " "happened. The Activity class itself serves as an abstract base " "class for all types of activities. It is important to note that " "the Activity class itself does not carry any specific semantics " "about the kind of action being taken.")) IntransitiveActivity = ASType( as_uri("IntransitiveActivity"), [Activity], "IntransitiveActivity", notes=( "Instances of IntransitiveActivity are a subclass of Activity whose " "actor property identifies the direct object of the action as opposed " "to using the object property.")) Actor = ASType( as_uri("Actor"), [Object], "Actor", notes=( "An Actor is any entity that is capable of being the primary actor " "for an Activity.")) Collection = ASType( as_uri("Collection"), [Object], "Collection", notes=( "A Collection is a subclass of Object that represents ordered or " "unordered sets of Object or Link instances.\n\n" "Refer to the Activity Streams 2.0 Core specification for a complete" "description of the Collection type.")) OrderedCollection = ASType( as_uri("OrderedCollection"), [Collection], "OrderedCollection", notes=( "A subclass of Collection in which members of the logical collection " "are assumed to always be strictly ordered.")) CollectionPage = ASType( as_uri("CollectionPage"), [Collection], "CollectionPage", notes=( "Used to represent distinct subsets of items from a Collection. " "Refer to the Activity Streams 2.0 Core for a complete description of " "the CollectionPage object.")) OrderedCollectionPage = ASType( as_uri("OrderedCollectionPage"), [OrderedCollection, CollectionPage], "OrderedCollectionPage", notes=( "Used to represent ordered subsets of items from an OrderedCollection. " "Refer to the Activity Streams 2.0 Core for a complete description of " "the OrderedCollectionPage object.")) # Extended Classes: Activity Types # ================================ Accept = ASType( as_uri("Accept"), [Activity], "Accept", notes=( "Indicates that the actor accepts the object. " "The target property can be used in certain circumstances to indicate " "the context into which the object has been accepted. For instance, " "when expressing the activity, \"Sally accepted Joe into the Club\", " "the \"target\" would identify the \"Club\".")) TentativeAccept = ASType( as_uri("TentativeAccept"), [Accept], "TentativeAccept", notes=( "A specialization of Accept indicating that the acceptance is " "tentative.")) Add = ASType( as_uri("Add"), [Activity], "Add", notes=( "Indicates that the actor has added the object to the target. If the " "target property is not explicitly specified, the target would need " "to be determined implicitly by context. The origin can be used to " "identify the context from which the object originated.")) Arrive = ASType( as_uri("Arrive"), [IntransitiveActivity], "Arrive", notes=( "An IntransitiveActivity that indicates that the actor has arrived " "at the location. The origin can be used to identify the context " "from which the actor originated. The target typically has no defined " "meaning.")) Create = ASType( as_uri("Create"), [Activity], "Create", notes=( "Indicates that the actor has created the object.")) Delete = ASType( as_uri("Delete"), [Activity], "Delete", notes=( "Indicates that the actor has deleted the object. If specified, " "the origin indicates the context from which the object was " "deleted.")) Follow = ASType( as_uri("Follow"), [Activity], "Follow", notes=( "Indicates that the actor is \"following\" the object. Following is " "defined in the sense typically used within Social systems in which " "the actor is interested in any activity performed by or on the " "object. The target and origin typically have no defined meaning.")) Ignore = ASType( as_uri("Ignore"), [Activity], "Ignore", notes=( "Indicates that the actor is ignoring the object. " "The target and origin typically have no defined meaning.")) Join = ASType( as_uri("Join"), [Activity], "Join", notes=( "Indicates that the actor has joined the object. The target and " "origin typically have no defined meaning.")) Leave = ASType( as_uri("Leave"), [Activity], "Leave", notes=( "Indicates that the actor has left the object. The target and origin " "typically have no meaning.")) Like = ASType( as_uri("Like"), [Activity], "Like", notes=( "Indicates that the actor likes, recommends or endorses the object. " "The target and origin typically have no defined meaning.")) Offer = ASType( as_uri("Offer"), [Activity], "Offer", notes=( "Indicates that the actor is offering the object. If specified, the " "target indicates the entity to which the object is being offered.")) Invite = ASType( as_uri("Invite"), [Offer], "Invite", notes=( "A specialization of Offer in which the actor is extending an " "invitation for the object to the target.")) Reject = ASType( as_uri("Reject"), [Activity], "Reject", notes=( "Indicates that the actor is rejecting the object. The target and " "origin typically have no defined meaning.")) TentativeReject = ASType( as_uri("TentativeReject"), [Reject], "TentativeReject", notes=( "A specialization of Reject in which the rejection is considered " "tentative.")) Remove = ASType( as_uri("Remove"), [Activity], "Remove", notes=( "Indicates that the actor is removing the object. If specified, the " "origin indicates the context from which the object is being removed.")) Undo = ASType( as_uri("Undo"), [Activity], "Undo", notes=( "Indicates that the actor is undoing the object. In most cases, " "the object will be an Activity describing some previously performed " "action (for instance, a person may have previously \"liked\" " "an article but, for whatever reason, might choose to undo that " "like at some later point in time).\n\n" "The target and origin typically have no defined meaning.")) Update = ASType( as_uri("Update"), [Activity], "Update", notes=( "Indicates that the actor has updated the object. Note, however, that " "this vocabulary does not define a mechanism for describing the " "actual set of modifications made to object.\n\n" "The target and origin typically have no defined meaning.")) Experience = ASType( as_uri("Experience"), [Activity], "Experience", notes=( "Indicates that the actor has experienced the object. The type of " "experience is not specified.")) View = ASType( as_uri("View"), [Experience], "View", notes=( "Indicates that the actor has viewed the object. Viewing is a " "specialization of Experience.")) Listen = ASType( as_uri("Listen"), [Experience], "Listen", notes=( "Indicates that the actor has listened to the object. Listening is a " "specialization of Experience.")) Read = ASType( as_uri("Read"), [Experience], "Read", notes=( "Indicates that the actor has read the object. Reading is a " "specialization of Experience.")) Move = ASType( as_uri("Move"), [Activity], "Move", notes=( "Indicates that the actor has moved object from origin to target. If " "the origin or target are not specified, either can be determined by " "context.")) Travel = ASType( as_uri("Travel"), [IntransitiveActivity], "Travel", notes=( "Indicates that the actor is traveling to target from origin. " "Travel is an IntransitiveObject whose actor specifies the direct " "object. If the target or origin are not specified, either can be " "determined by context.")) Announce = ASType( as_uri("Announce"), [Activity], "Announce", notes=( "Indicates that the actor is calling the target's attention the object." "\n\n" "The origin typically has no defined meaning.")) Block = ASType( as_uri("Block"), [Ignore], "Block", notes=( "Indicates that the actor is blocking the object. Blocking is a " "stronger form of Ignore. The typical use is to support social systems " "that allow one user to block activities or content of other users. " "The target and origin typically have no defined meaning.")) Flag = ASType( as_uri("Flag"), [Activity], "Flag", notes=( "Indicates that the actor is \"flagging\" the object. Flagging is " "defined in the sense common to many social platforms as reporting " "content as being inappropriate for any number of reasons.")) Dislike = ASType( as_uri("Dislike"), [Activity], "Dislike", notes=( "Indicates that the actor dislikes the object.")) # Extended Classes: Actor types # ============================= Application = ASType( as_uri("Application"), [Actor], "Application", notes=( "Describes a software application.")) Group = ASType( as_uri("Group"), [Actor], "Group", notes=( "Represents a formal or informal collective of Actors.")) Organization = ASType( as_uri("Organization"), [Actor], "Organization", notes=( "Represents an organization.")) Person = ASType( as_uri("Person"), [Actor], "Person", notes=( "Represents an individual person.")) Process = ASType( as_uri("Process"), [Actor], "Process", notes=( "Represents a series of actions taken to achieve a particular goal.")) Service = ASType( as_uri("Service"), [Actor], "Service", notes=( "Represents a service of any kind.")) # Extended Classes: Object Types # ============================== Relationship = ASType( as_uri("Relationship"), [Object], "Relationship", notes=( "Describes a relationship between two individuals. " "The subject and object properties are used to identify the " "connected individuals.\n\n" "See 3.3.1 [of ActivityStreams 2.0 Vocabulary document] Representing " "Relationships Between Entities for additional information.")) Content = ASType( as_uri("Content"), [Object], "Content", notes=( "Describes an entity representing any form of content. Examples " "include documents, images, etc. Content objects typically are not " "able to perform activities on their own, yet rather are usually the " "object or target of activities.")) Article = ASType( as_uri("Article"), [Content], "Article", notes=( "Represents any kind of multi-paragraph written work.")) Album = ASType( as_uri("Album"), [Collection], "Album", notes=( "A type of Collection typically used to organize Image, Video or Audio " "objects.")) Folder = ASType( as_uri("Folder"), [Collection], "Folder", notes=( "A type of Collection typically used to organize objects such as" "Documents.")) Story = ASType( as_uri("Story"), [OrderedCollection], "Story", notes=( "A type of Ordered Collection usually containing Content Items " "organized to \"tell a story\". ")) Document = ASType( as_uri("Document"), [Content], "Document", notes=( "Represents a document of any kind.")) Audio = ASType( as_uri("Audio"), [Document], "Audio", notes=( "Represents an audio document of any kind.")) Image = ASType( as_uri("Image"), [Document], "Image", notes=( "An image document of any kind")) Video = ASType( as_uri("Video"), [Content], "Video", notes=("Represents a video document of any kind.")) Note = ASType( as_uri("Note"), [Content], "Note", notes=( "Represents a short work typically less than a single " "paragraph in length.")) Page = ASType( as_uri("Page"), [Document], "Page", notes=( "Represents a Web Page.")) Question = ASType( as_uri("Question"), [Content, IntransitiveActivity], "Question", notes=( "Represents a question being asked. Question objects are unique in " "that they are an extension of both Content and IntransitiveActivity. " "That is, the Question object is an Activity but the direct object is " "the question itself.")) Event = ASType( as_uri("Event"), [Object], "Event", notes=( "Represents any kind of event.")) Place = ASType( as_uri("Place"), [Object], "Place", notes=( "Represents a logical or physical location. " "See 3.3.2 Representing Places [of ActivityStreams 2.0 Vocabulary " "document] for additional information.")) Mention = ASType( as_uri("Mention"), [Link], "Mention", notes=( "A specialized Link that represents an @mention.")) Profile = ASType( as_uri("Profile"), [Content], "Profile", notes=( "A Profile is a content object that describes another Object, " "typically used to describe Actor, objects. The describes property " "is used to reference the object being described by the profile.")) # Core definition Vocab and basic environment # =========================================== CoreVocab = ASVocab( [Object, Link, Activity, IntransitiveActivity, Actor, Collection, OrderedCollection, CollectionPage, OrderedCollectionPage, Accept, TentativeAccept, Add, Arrive, Create, Delete, Follow, Ignore, Join, Leave, Like, Offer, Invite, Reject, TentativeReject, Remove, Undo, Update, Experience, View, Listen, Read, Move, Travel, Announce, Block, Flag, Dislike, Application, Group, Organization, Person, Process, Service, Relationship, Content, Article, Album, Folder, Story, Document, Audio, Image, Video, Note, Page, Question, Event, Place, Mention, Profile]) BasicEnv = Environment( # @@: Maybe this one should be implied? vocabs=[CoreVocab], shortids=shortids_from_vocab(CoreVocab), c_accessors=shortids_from_vocab(CoreVocab)) # alias Env = BasicEnv activipy-0.1/apache-2.0.txt000066400000000000000000000261361261615066400155000ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. activipy-0.1/docs/000077500000000000000000000000001261615066400141415ustar00rootroot00000000000000activipy-0.1/docs/Makefile000066400000000000000000000164021261615066400156040ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ActiviPy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ActiviPy.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/ActiviPy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ActiviPy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." activipy-0.1/docs/source/000077500000000000000000000000001261615066400154415ustar00rootroot00000000000000activipy-0.1/docs/source/about.rst000066400000000000000000000142201261615066400173040ustar00rootroot00000000000000About Activipy ============== What is ActivityStreams? How might it help me? ----------------------------------------------- Activipy is a simple library which makes working with `ActivityStreams `_ easy. Which is a bit confusing, because ActivityStreams is already dead-easy. Activipy helps us make things a bit more robust. How easy is ActivityStreams? Here's a valid ActivityStreams document from a mad scientist type person:: {"@id": "http://drboss.example/api/objects/123415/", "@type": "Create", "actor": { "@type": "Person", "@id": "http://drboss.example/me/", "displayName": "Professor BossManager"}, "to": ["http://employeemine.example/jim/", "http://employeemine.example/sarah/"], "object": { "@type": "Note", "@id": "http://drboss.example/rants/power-productivity/", "content": "Thanks to my latest invention, productivity is up 1 million percent! MWAHAHA!!"}} Here's another: .. code-block:: python {"@id": "http://employeemine.example/sarah/blog/new-job-please/" "@type": "Note", "displayName": "New job, please.", "content": "Anyone know where I can get a job not managed by a mad scientist?"} These objects aren't complex, and anyone can read them. They're just JSON, plus some `common vocabulary `_, including a convention that "@id" is the identifier of the object and "@type" is the vocabulary type. That's pretty simple! And simple is good, because let's face it, most users of most web application APIs are like poor Billy Scripter, a kid who has some scripting language like Ruby or Python or Javascript and some JSON parser in a toolbox and that's about it. Billy Scripter knows how to parse JSON pulled down from some endpoint, and that's about all he knows how to do. Poor Billy Scripter! But it's okay, because ActivityStreams is simple enough that Billy can make it by. And because the `ActivityStreams Core `_ serialization specifies that the `ActivityStreams Vocabulary `_ is always implied and that those terms must always be available, Billy will always know what a `Like `_ object or a `Note `_ means. Horray for Billy! Meanwhile, you get the benefit of a well thought out "subject, predicate, object" type structure (or in other words, "who did what"). The `Core Classes `_ provide the basic structure, and that general structure combined with the very minimal rules of `ActivityStreams Core `_ (roughly speaking, it's just JSON with the ActivityStreams vocabulary implied, but if you need anything more, you can use `json-ld `_) means that you already have the general structure of all the "social" type activities that happen on the modern web. Throw in the `Extended Classes `_ and you've got all the right language to express what your users are doing too. Awesome. But hey, what happens when the base `ActivityStreams vocabulary `_ just isn't enough? Maybe you're running a social network that's also a game and you need some way to express that your players are beating up goblins (wait, I take that back, don't beat up poor goblins!), or you've got a highly interactive e-commerce site where users can share coupons or whatever. Look, whatever it is, you can express it in ActivityStreams! Yeah, I said it! Sure, ActivityStreams gives you almost everything you need out of the box, but if you need to get fancy and define your own terms, you can do that too. ActivityStreams is technically a `json-ld `_ document with an implied vocabulary, but you can add on new vocabularies too. Need to add Coupon objects, or RpgBeatemup activities? Yeah, you can do it! And for all you `RDF `_ fans out there, you can transform ActivityStreams objects into a full-on linked-data graph. Go wild! But you also don't *have* to. Thanks to the promises made by the ActivityStreams serialization, Billy Scripter can make it by just fine with his Ruby and JSON toolkit. And so can most of the rest of the web! How Activipy and ActivityStreams work together ---------------------------------------------- So if ActivityStreams is just JSON (and optionally json-ld), what do you need Activipy for? Good question! Activipy provides a whole suite of useful tools, including friendly and Pythonic constructors, a flexible and extensible method dispatch system, and much more. We could express the above like this:: >>> from activipy import vocab >>> vocab.Create( ... "http://drboss.example/api/objects/123415/", ... actor=vocab.Person( ... "http://drboss.example/me/", ... displayName="Professor BossManager"), ... to=["http://employeemine.example/jim/", ... "http://employeemine.example/sarah/"], ... object=vocab.Note( ... http://drboss.example/rants/power-productivity/", ... content="Thanks to my latest invention, productivity is up 1 million percent! MWAHAHA!!")) If we were writing our own diary application, we could specify an environment that knows how to post notes to it:: >>> from ourjournal import DiaryEnv >>> dear_diary = DiaryEnv.c.Note( ... displayName="New job, please.", ... content="Anyone know where I can get a job not managed by a mad scientist?") >>> dear_diary.post() Activipy provides you with the basic tools you need to map ActivityStreams to the world of your Python application. And if you're just starting out in writing a brand new social network application? You'd better believe Activipy is a good place to start! Activipy means making your networked application social is easy. And best of all, you can speak a common language with other ActivityStreams speaking applications across the net. Sound good? :ref:`Let's get started! ` activipy-0.1/docs/source/conf.py000066400000000000000000000221531261615066400167430ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # ActiviPy documentation build configuration file, created by # sphinx-quickstart on Thu Oct 22 11:39:15 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import shlex # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'ActiviPy' copyright = '2015, Christopher Allan Webber and ActiviPy contributors' author = 'Christopher Allan Webber and ActiviPy contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.1.dev' # The full version, including alpha/beta/rc tags. release = '0.1.dev' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' #html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value #html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. #html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'ActiviPydoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', # Latex figure (float) alignment #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'ActiviPy.tex', 'ActiviPy Documentation', 'Christopher Allan Webber and ActiviPy contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'activipy', 'ActiviPy Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'ActiviPy', 'ActiviPy Documentation', author, 'ActiviPy', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False activipy-0.1/docs/source/index.rst000066400000000000000000000024451261615066400173070ustar00rootroot00000000000000Activipy: Your pythonic social network toolkit ============================================== `Activipy `_ (pronounced "activi-pie") is an `ActivityStreams 2.0 `_ toolkit for Python. It helps making your application or website social while keeping things fun. ActivityStreams is a JSON (and optionally, `json-ld `_) representation of "who's doing what", simple to understand and easy to implement on your own website. ActiviPy makes things extra easy for you by including the full basic `ActivityStreams vocabulary `_, which means that you have the appropriate language to discribe common social network type interactions. This makes it easy to build a world where various servers can speak the same language about common activities. If your application does something more complicated, don't worry, ActivityStreams has an extension mechanism, and Activipy is built to help you take advantage of it. Jump on in! Contents: .. toctree:: :maxdepth: 2 about tutorial .. core types .. vocabulary .. extending the environment .. advanced examples Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` activipy-0.1/docs/source/tutorial.rst000066400000000000000000001165531261615066400200510ustar00rootroot00000000000000.. _tutorial-chapter: An Activipy tutorial ==================== .. TODO: Do we want to open up with a more "dive in" demo of the above? .. TODO: Break this up into multiple pages, so it doesn't look intimidating? This tutorial is fairly robust and is broken into sections. Each one of them builds upon each other like a ladder, but you can step off at any time. In this tutorial you'll learn: 1. How to use Activipy to express and share basic social networking features. 2. How to *do* cool things with Activipy using the method dispatch system. 3. Go beyond the basics and use extended vocabulary. 4. Why Activipy objects are "immutable" (or are intended to be) and how to use that to your advantage. .. There's also an advanced tutorial for building environments and etc You can read as much or as little as you like, but we hope this tutorial is engaging enough where you won't be able to stop reading until the end! Sweet beginnings ---------------- So say you and your friends are all conspiring to meet up for root beer floats. How could we structure that in Python? First, let's look at the ActivityStreams representation of this note, and then we'll look at how we got there. What vocabulary do we want to use? Let's look at what comes out of the box:: >>> vocab.Create.notes 'Indicates that the actor has created the object.' >>> vocab.Person.notes 'Represents an individual person.' >>> vocab.Note.notes 'Represents a short work typically less than a single paragraph in length.' Those sound like the things we mean. Great! It's nice that Activipy includes the notes from the `Activity Vocabulary `_ so it's easy for us to keep track of what things mean. So it turns out we can use these vocabulary definitions as friendly constructors:: # gives us the core vocabulary from activipy import vocab post_this = vocab.Create( "http://tsyesika.co.uk/act/foo-id-here/", actor=vocab.Person( "http://tsyesika.co.uk/", displayName="Jessica Tallon"), to=["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], object=vocab.Note( "htp://tsyesika.co.uk/chat/sup-yo/", content="Up for some root beer floats?")) Oh, okay, that's pretty easy to read! We can see that we've specified who we are, who we want to send the message to, and the actual message we're posting. What does our message look like? Let's see:: >>> post_this.json() {"@id": "http://tsyesika.co.uk/act/foo-id-here/", "@type": "Create", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], "object": { "@type": "Note", "@id": "htp://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}} Oh interesting! That looks pretty similar to the Python constructor version. In fact, we could have built this from the json itself:: >>> from activipy import core, vocab >>> post_this = core.ASObj({ ... "@type": "Create", ... "@id": "http://tsyesika.co.uk/act/foo-id-here/", ... "actor": { ... "@type": "Person", ... "@id": "http://tsyesika.co.uk/", ... "displayName": "Jessica Tallon"}, ... "to": ["acct:cwebber@identi.ca", ... "acct:justaguy@rhiaro.co.uk", ... "acct:ladyaeva@hedgehog.example"], ... "object": { ... "@type": "Note", ... "@id": "htp://tsyesika.co.uk/chat/sup-yo/", ... "content": "Up for some root beer floats?"}}, ... vocab.BasicEnv) Hm! So it's nice to have "pythonic" constructors, but this json representation isn't so complex... is it worth having a whole library just for this? Let's see what else Activipy gives us. Activipy gives simple dictionary-style access:: >>> post_this["to"] ['acct:cwebber@identi.ca', 'acct:justaguy@rhiaro.co.uk', 'acct:ladyaeva@hedgehog.example'] Helpful, but we could have gotten that from running .json() and pulling out the right values! But this is kinda nice:: >>> root_beer_note = post_this["object"] >>> root_beer_note Cool, we've extracted the actual object we were going to post, and it came back wrapped in an ASObj object. Of course, we could always get the json version of this if we wanted:: >>> root_beer_note.json() {'@id': 'http://tsyesika.co.uk/chat/sup-yo/', '@type': 'Note', 'content': 'Up for some root beer floats?'} What kind of type is our newly extracted `root_beer_note`? Let's see:: >>> root_beer_note.types ['Note'] Wait, "types", as in possibly plural? That's right, an ActivityStreams object's "type" is actually a "composite type". It turns out this is useful when handling extensions to the vocabulary, but we'll come back to that later. Strings are less fun as types than ASTypes, so can we get that back? We sure can:: >>> root_beer_note.types_astype [] But hey, what's this thing:: >>> root_beer_note.types_expanded ['http://www.w3.org/ns/activitystreams#Note'] Huh? A URL? This starts to hint at something more complicated... something to do with extensions! But we're getting ahead of ourselves. Extension stuff comes later! Right now we're itching to *do* something with these objects... so what can we do, and how do we do it? Methods for our madness ----------------------- New environments, new friends ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before we hop right into methods, a quick refresher. Remember when we said we could have built our post_this object like this? .. code-block:: pycon >>> from activipy import core, vocab >>> post_this = core.ASObj({ ... # ... json stuff here ... }, ... vocab.BasicEnv) Wait, what's that BasicEnv thing hanging off the end? That's pretty curious. What does it do? Since we're passing into the object, it's a good (and correct) guess to assume that the ASObj instance has access to it:: >>> post_this.env The environment helps us in a few ways. For one thing, it contains a set of vocabulary that our environment "knows" about. In fact, we could even do constructors that implicitly pass in the environment directly from the environment itself, using the vocabulary it's been informed of:: >>> env = vocab.BasicEnv >>> root_beer_note = env.c.Note( ... "htp://tsyesika.co.uk/chat/sup-yo/", ... content="Up for some root beer floats?") >>> root_beer_note Well that's pretty neat! It looks like the `Environment.c` accessor is a friendly way to access vocabulary classes. Cool! So you can already guess at one purpose for environments: if your application is working with an extended vocabulary, it's possible for Activipy to "know" about your vocabulary while determining types, etc. The BasicEnv is, as you would expect, the default and most minimal environment, containing the core vocabulary and nothing else. For many applications, this is all you need. If your application needs additional terminology, we will cover this later in the manual, but for now, we will only concern ourselves with the core vocabulary. Even if we stick with the core vocabulary, we may wish to use a different environment than BasicEnv. Why? Well we keep saying that we want to *do* something with our applications. Aside from mapping vocabulary, `Environment` objects can contain a mapping of methods! So, we want to try something... what would be a good demo? How about storing things! Sounds good to me! In our case we're going to simply serialize ActivityStreams objects to json and dump them in and out of a minimalist key-value `dbm database `_. (Note: this will be a lot more efficient if you install the Python bindings for `gdbm `_.) Our dbm demo module contains a JsonDBM wrapper which conveniently serializes/deserializes to/from json when pulling things in/out of the database. Let's give it a spin so we know what we're working with:: >>> from activipy.demos import dbm >>> db = dbm.JsonDBM.open("/tmp/test.db") >>> db["foo"] = {"cat": "meow", "dog": "woof"} >>> db["foo"] {'cat': 'meow', 'dog': 'woof'} >>> "foo" in db True >>> del db["foo"] >>> "foo" in db False Okay, so that's a pretty easy to use key-value store! We could clearly dump our ASObj objects to json and manually save them into here. It would be nice if there was a "save" method that could do that for us though. How could such a save method be made available? Save one for me, please ~~~~~~~~~~~~~~~~~~~~~~~ Ah, here's a use for Environments! You see, an `Environment` object not only contains information about vocabulary, it contains information about methods as well. As it turns out, we have a handy environment ready for you to play with which knows how to work with a `JsonDBM` wrapped database. Let's try it! .. code-block:: pycon >>> env = dbm.DbmEnv >>> note = env.c.Note("http://example.org/notes/cookie-time/", ... content="I really want a cookie!") >>> note.m.save(db) >>> db["http://example.org/notes/cookie-time"] {'@id': 'http://example.org/notes/cookie-time/', '@type': 'Note', 'content': 'I really want a cookie!'} Hey, it worked! That sure was handy... we got a .save() method attached right to our Note! How about a .delete()? .. code-block:: pycon >>> "http://example.org/notes/cookie-time" in db True >>> note.m.delete(db) >>> "http://example.org/notes/cookie-time" in db False How convenient! You may notice that we don't call `note.save()` or `note.delete()`; instead, we call `note.m.save()` and `note.m.delete()`! That's because the `.m` attribute is a proxy object to all the methods the `ASObj.env` knows about (in this case, DbmEnv):: >>> dbm.DbmEnv.methods {(, ): , (, ): } In fact, we could have used DbmEnv.m instead:: >>> dbm.DvmEnv.m.save(note, db) >>> # is the same as >>> note.m.save(db) But that's way more verbose! Why not just use `note.m.save(db)` instead? So convenient! What happens if we save a more complicated, nested note to the db? Remember our root beer float friend? .. code-block:: pycon >>> post_this.json() {"@id": "http://tsyesika.co.uk/act/foo-id-here/", "@type": "Create", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], "object": { "@type": "Note", "@id": "htp://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}} So we now remember that when post_this was set up, it used the general purpose environment. This means that there is no `post_this.m.save()` method for us to call, because that method is not set up in the BasicEnv environment. We can't even use `BasicEnv.m.save()`, because Activipy safeguards against this:: >>> dbm.DbmEnv.m.save(post_this, db) Traceback (most recent call last): File "", line 1, in File "/home/cwebber/devel/activipy/activipy/core.py", line 464, in method_dispatcher method = self.asobj_get_method(asobj, method_id) File "/home/cwebber/devel/activipy/activipy/core.py", line 550, in asobj_get_method "ASObj attempted to call method with an Environment " activipy.core.EnvironmentMismatch: ASObj attempted to call method with an Environment it was not bound to! This makes sense, because different environments provide different vocabularies and handle different methods, and subtle bugs could creep in if we permitted this. Normally this is no problem, one application will in general only make use of a single `Environment` tuned to that application. Even here, it is easy to correct... let's just recast `post_this` to our new environment:: >>> post_this = core.ASObj(post_this.json(), dbm.DbmEnv) Now we can save away::: >>> post_this.m.save(db) >>> db["http://tsyesika.co.uk/act/foo-id-here/"] {"@id": "http://tsyesika.co.uk/act/foo-id-here/", "@type": "Create", "actor": { "@type": "Person", "@id": "http://tsyesika.co.uk/", "displayName": "Jessica Tallon"}, "to": ["acct:cwebber@identi.ca", "acct:justaguy@rhiaro.co.uk", "acct:ladyaeva@hedgehog.example"], "object": { "@type": "Note", "@id": "htp://tsyesika.co.uk/chat/sup-yo/", "content": "Up for some root beer floats?"}} Hooray, our note is in the database! That's really nice. Save a few more ~~~~~~~~~~~~~~~ But wait, is this really how we want? Notice that this activity contains two nested ActivityStreams objects: `actor` and `object`! Wouldn't it be nice if the `.save()` method was able to be smart about this and "normalize" the data for us, saving the child ActivityStreams objects as their own database references, and pulling them out as needed? Let's think about this for a moment. We know that the root activity that we're posting here is of the type `Create`. We could look at the `ActivityStreams Vocabulary document `_ to find out the inheritance chain, but we don't even have to... Activipy can help us out here:: >>> post_this.types_inheritance [, , ] Looking at this, we know that `Create` is a type of `Activity`, which is itself a type of `Object`. Looking at the vocabulary document, it's clear to us that the `actor` and `object` fields `inherit from Activity `_. It seems fine to save a general `Object` type as-is as we already are, and indeed, you may have noticed that the save method was operating precisely on this ASType:: >>> dbm.DbmEnv.methods {(, ): , (, ): } So, since a `Create` *is* an Object, of course the basic save happens here. But it's even more an `Activity` than a mere `Object`, and if we think about it, hey! Pretty much on any `Activity` ASType (whether it's a `Create` or a `Delete` or a `Like`...) it would be really nice to normalize the `actor` and `object` fields. Is there a way to specify that we'd like to treat Activity objects a bit differently? Indeed, there is! As you've already guessed, if our `Environment` had a separate method that did something different for `save` on `Activity`, that would be really helpful. And it turns out, we've already supplied you with such an environment:: >>> dbm.DbmNormalizedEnv.methods {(, ): , (, ): , (, ): , (, ): , (, ): } Neat, this does indeed provide us with a separate method for Activity. Let's switch to using the `DbmNormalizedEnv` instead and cast `post_this` to use it (again, you wouldn't normally need to do this in an application that uses just one environment):: >>> env = dbm.DbmNormalizedEnv >>> post_this = core.ASObj(post_this.json(), dbm.DbmNormalizedEnv) Now what happens if we save the object? .. code-block:: pycon >>> post_this.m.save(db) >>> db["http://tsyesika.co.uk/act/foo-id-here/"] {'@id': 'http://tsyesika.co.uk/act/foo-id-here/', '@type': 'Create', 'actor': 'http://tsyesika.co.uk/', 'object': 'http://tsyesika.co.uk/chat/sup-yo/', 'to': ['acct:cwebber@identi.ca', 'acct:justaguy@rhiaro.co.uk', 'acct:ladyaeva@hedgehog.example']} >>> db["http://tsyesika.co.uk/"] {'@id': 'http://tsyesika.co.uk/', '@type': 'Person', 'displayName': 'Jessica Tallon'} >>> db["http://tsyesika.co.uk/chat/sup-yo/"] {'@id': 'http://tsyesika.co.uk/chat/sup-yo/', '@type': 'Note', 'content': 'Up for some root beer floats?'} Awesome... that is *exactly* what we were hoping for! There and back again ~~~~~~~~~~~~~~~~~~~~ Just to bring things full circle, here's a method that demonstrates pulling an object out of the database:: >>> def dbm_fetch(id, db, env): ... return core.ASObj(db[id], env) ... >>> normalized_post = dbm_fetch("http://tsyesika.co.uk/act/foo-id-here/", ... db, dbm.DbmNormalizedEnv) >>> normalized_post >>> normalized_post.json() {'@id': 'http://tsyesika.co.uk/act/foo-id-here/', '@type': 'Create', 'actor': 'http://tsyesika.co.uk/', 'object': 'http://tsyesika.co.uk/chat/sup-yo/', 'to': ['acct:cwebber@identi.ca', 'acct:justaguy@rhiaro.co.uk', 'acct:ladyaeva@hedgehog.example']} We could make use of the environment's denormalize method:: >>> normalized_post.m.denormalize(db) >>> normalized_post.m.denormalize(db).json() {'@id': 'http://tsyesika.co.uk/act/foo-id-here/', '@type': 'Create', 'actor': {'@id': 'http://tsyesika.co.uk/', '@type': 'Person', 'displayName': 'Jessica Tallon'}, 'object': {'@id': 'http://tsyesika.co.uk/chat/sup-yo/', '@type': 'Note', 'content': 'Up for some root beer floats?'}, 'to': ['acct:cwebber@identi.ca', 'acct:justaguy@rhiaro.co.uk', 'acct:ladyaeva@hedgehog.example']} Hey look, it's our original post back, with the `actor` and `object` filled in! This time, they were extracted from their own entries' key-value pairs in the database. Neat! And finally, we could simplify this whole thing, and write a method to pull data out of the database in a denormalized fashion, making use of our environment's denormalize methods:: >>> def dbm_fetch_denormalized(id, db, env): ... return env.m.denormalize( ... dbm_fetch(id, db, env), db) ... >>> denormalized_post = dbm_fetch_denormalized( ... "http://tsyesika.co.uk/act/foo-id-here/", ... db, dbm.DbmNormalizedEnv) >>> denormalized_post >> denormalized_post.json() {'@id': 'http://tsyesika.co.uk/act/foo-id-here/', '@type': 'Create', 'actor': {'@id': 'http://tsyesika.co.uk/', '@type': 'Person', 'displayName': 'Jessica Tallon'}, 'object': {'@id': 'http://tsyesika.co.uk/chat/sup-yo/', '@type': 'Note', 'content': 'Up for some root beer floats?'}, 'to': ['acct:cwebber@identi.ca', 'acct:justaguy@rhiaro.co.uk', 'acct:ladyaeva@hedgehog.example']} Whew, what a round trip! A word to the enwisened ~~~~~~~~~~~~~~~~~~~~~~~ This whole process above of calling the appropriate methods for the appropriate type (or in our case, ASType) is called "method dispatch". You may have noticed that we do things fairly differently from most Python libraries, which usually use Python's native classes as an inheritance chain, something like this:: class Object(ASClass): class_id = "http://www.w3.org/ns/activitystreams#Object" # bla bla def save(self, db): # save thing here pass class Activity(Object): class_id = "http://www.w3.org/ns/activitystreams#Activity" # more bla bla def save(self, db): # save a bit differently pass class Create(Activity): class_id = "http://www.w3.org/ns/activitystreams#Create" # also define Note, etc here We aren't doing that... we're using this intermediate `Environment` thing instead, and ASObj instances are all just instances of ASObj. Why? Why not just use Python's normal class hierarchy? Why have an `Environment` at all? There are a few reasons: 1. ActivityStreams technically has "composite types"... an "@type" can actually have *multiple* values set here, and the functionality provided by the ASObj will be a union of those types. Because of this, Python's classes really don't work at all to track inheritance. Luckily, there are other benefits of going with an `Environment`.... 2. Different applications need to do different things. It's useful to have a general way of handling method dispatch that appropriately pays respect to the inheritance system of the ActivityStreams base vocabulary, and it's nice to make that as Pythonic as possible, but you might never save ActivityStreams objects to a DBM store (very few production applications would). You very well may store objects to an SQL database, or some object store, or who knows what. You may wish to use Activipy for a desktop client or a server application, and those might do very different things. What methods you specify are up to you, but Environments are built in such a way that sharing methods between them, picking and choosing the ones useful to you, and defining entirely new methods is easy. 3. The core vocabulary is good enough for most social web applications, but not for all. You may well need to define entirely new vocabulary, and Activipy allows you to do this. Allowing a user to define their own Environment means that this is not difficult to do, and how to transfer to those to the appropriate ASType representations (and then to know how to operate upon them) is very possible. 4. We didn't get into it here, but fancier method handling can also be done than just the traditional "dispatch hands off to a single procedure" approach. You can also set up a method with a handler which maps or folds over the methods provided through the method mapping and inheritence chain. (Cool, but advanced, stuff! You can imagine how this might be helpful for writing a validator, or etc.) There's more to say on these subjects, but hopefully this section helped put some of this into perspective. Hopefully the rest of this will become clear shortly, including how to expand our vocabulary without tripping over each others' definitions between applications. Expanding our vocabulary ------------------------ Setting the stage ~~~~~~~~~~~~~~~~~ The default `ActivityStreams vocabulary `_ is fairly comprehensive, and for most social networking applications, probably has everything you need. But what happens if it doesn't? In this section we'll explore adding vocabulary for an imaginary social network application that's a "check-in" application (somewhere between Foursquare and Groupon in design). Users check in on their phones or other mobile devices when they arrive somewhere, and their frequency of attendance is somewhat gameified. Frequent attendance increases a "royalty" status, and some stores or other consumer outlets may reward users with one-time use coupons for frequent attendance. This is an intereresting application, but the default vocabulary doesn't give us everything we need. We'd like to define new types like "CheckIn", but if someone comes up with a hospital application that also defines CheckIn, we don't want our applications to get confused while talking to each other. How can we do this? Luckily, Activipy has you covered... read on! Some new terms ~~~~~~~~~~~~~~ Let's start out easy, and worry about the details later. So let's say that we're a user of a CheckUp vocabulary using service. We have a nice little demo for this, so let's import that, and we'll start using the CheckUpEnv:: >>> from activipy.demos import checkup # contains vocab and environment >>> from activipy import vocab # we'll use some of these too >>> env = checkup.CheckUpEnv # for convenience of tutorial We'll also already assume that we've got a user setup in this system. Even though we're working with new vocabulary, the Core vocabulary is also set up in the CheckUpEnv:: >>> me = env.c.Person( ... "http://social.example/u/sugartooth/", ... displayName="Sarah Sugartooth") We're also going to set up an imaginary connection to the CheckUp server we're using, just for demonstration purposes:: .. TODO: Provide an actual FakeConnection() thing eventually >>> conn = FakeConnection() So we know there's a CheckIn vocabulary available through CheckUpEnv... let's say we just arrived at the "Sweet Expressions" ice cream parlor. Time to check in! .. code-block:: pycon >>> check_in = env.c.CheckIn( ... actor=me, ... location=env.c.Place( ... "http://sweetexpressions.example/", ... displayName="Sweet Expressions")) >>> check_in What does that check_in object look like in json form? .. code-block:: pycon >>> check_in.json() {'@context': 'http://checkup.example/context.jld', '@type': 'CheckIn', 'actor': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'location': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} Huh, so this is kind of interesting. The `@type` looks nice and simple as "CheckIn", but there's also this `@context` thing. We won't worry too much about what that is yet, but a brief preview is given by checking the types:: >>> check_in.types_astype [] >>> check_in.types_expanded ['http://checkup.example/ns#CheckIn'] Hm, so this is pretty cool! Something in that @context has helped clarify exactly what "CheckIn" we're talking about. We'll get into this more later, but if in the future there was ever a DoctorVisit vocabulary, we'd never mistake "http://checkup.example/ns#CheckIn" for a "http://doctoroffice.example/terms#CheckIn". Our doctor might be fairly confused if we sent her a note telling her that we're out to get ice cream, but now we can be sure that that mistake won't happen. We'll get into this more later, but that's already good to know! Okay, so we've made the `check_in` object, but we haven't *done* anything with it yet. We have this connection to our CheckUp service, why not post it there! .. code-block:: pycon >>> check_in.m.post(conn) Well that was easy... what can we do now? How about check out our inbox? .. code-block:: pycon >>> inbox_contents = me.m.inbox(conn) >>> inbox_contents.json() {'@context': 'http://checkup.example/context.jld', '@type': 'Collection', 'items': [ {'@type': ['Coupon', 'Note'], 'content': 'Thanks for 40 visits to Sweet Expressions!', 'recipient': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'redeem_uri': 'http://sweetexpressions.example/coupon/9ae37630/', 'vendor': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} {'@type': 'http://checkup.example/ns#RoyalStatus', 'displayName': "Sarah Sugartooth's been upgraded to Queen status!", 'recipient': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'status': 'Queen', 'vendor': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}}]} Huh... that's interesting, so this is a collection... it has two items in it. We could pull out that first item individually and take a look at it in detail:: >>> coupon = inbox_contents["items"][0] >>> coupon Huh, that's interesting, it says it's a Coupon *and* a Note? It sure is:: >>> coupon.json() {'@context': 'http://checkup.example/context.jld', '@type': ['Coupon', 'Note'], 'content': 'Thanks for visiting Sweet Expressions!', 'recipient': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'redeem_uri': 'http://sweetexpressions.example/coupon/9ae37630/', 'vendor': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} >>> coupon.types_astype [, ] >>> coupon.types_expanded ['http://checkup.example/ns#Coupon', 'http://www.w3.org/ns/activitystreams#Note'] So first of all, this makes sense. All a Coupon is to our system is something fairly functional, something by which a vendor can deliver a `redeem_uri` (which is a one-time-use URI to redeem a coupon for something special) to a recipient. But our friends at Sweet Expressions wanted to include a little thank-you along with the coupon, so this object acts as a composite type of both of these. As a user, for the most part, the details of handling composite types are mostly taken care of for you by Activipy. But as a side note, it's interesting to look at what this means for the inheritence chain:: # inheritence chain for the Coupon ASType >>> checkup.Coupon.inheritance_chain [, ] # inheritence chain for the Coupon ASType >>> vocab.Note.inheritance_chain [, , ] # The actual types for our coupon object >>> coupon.types_astype [, ] # And the inheritence chain built by the composite type >>> coupon.types_inheritance [, , , ] You don't necessarily need to make use of composite types in your system, but we can now see one possible reason you might want to: some of your activities might be a union of actions, and composite types are a great way to express them. (As will be covered in the "advanced tutorial", you can use the mapping/folding method features to maximize composite types with your method handling, too.) There's another reason that we might see composite types used... let's look at our outbox from our connection:: >>> me.m.outbox(conn).json() {'@context': 'http://checkup.example/context.jld', '@type': 'Collection', 'items': [ {'@type': ['CheckIn', 'Arrive'], 'actor': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'location': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}}]} Huh... that's interesting. So we've retreived from the connection our recent activities, but whatever server is on the other end is showing the CheckIn we just did as a composite type of both `CheckIn` and `Arrive`! This seems kind of stange, because `CheckIn` is just a subclass of `Arrive` anyway:: >>> checkup.CheckIn.inheritance_chain ... [, ... , ... , ... , ... ] So if a `CheckIn` is technically part of an `Arrive`, why might our server do this? Well, not all servers might have the `CheckIn` vocabulary (or know that it's a parent to `Arrive`), so since `CheckIn` can technically be displayed as an `Arrive`, so if a recipient of this activity knows how to display an `Arrive` but not a `Checkin`, they could do that. So anyway! Now you've seen how to work with new types. Now you're maybe starting to wonder, what's the theoretical model of all this under the hood? (What, you weren't wondering that before? Okay, now you are!) Let's take a look! Expanding into json-ld ~~~~~~~~~~~~~~~~~~~~~~ Remember when we did this? .. code-block:: pycon >>> root_beer_note.types_expanded ['http://www.w3.org/ns/activitystreams#Note'] >>> coupon.types_expanded ['http://checkup.example/ns#Coupon', 'http://www.w3.org/ns/activitystreams#Note'] This starts to make more sense when we think about naming conflicts... if you send me a message about "running a mile", and I send you a message about "running a program", those are obviously two very different definitions of "running", and it might create a lot of problems if they become confused. There should be an unambiguous way to represent things, and that's exactly where `json-ld `_ comes in. In json-ld, json objects can be "expanded" to an unambiguous format, and then "compacted" to the right definitions for our own local server, so we'll never get confused between two different definitions of "running" again. Here's a brief hint towards that right now:: >>> post_this.expanded() [{'@id': 'http://tsyesika.co.uk/act/foo-id-here/', '@type': ['http://www.w3.org/ns/activitystreams#Create'], 'http://www.w3.org/ns/activitystreams#actor': [ {'@id': 'http://tsyesika.co.uk/', '@type': ['http://www.w3.org/ns/activitystreams#Person'], 'http://www.w3.org/ns/activitystreams#displayName': [ {'@value': 'Jessica Tallon'}]}], 'http://www.w3.org/ns/activitystreams#object': [ {'@id': 'htp://tsyesika.co.uk/chat/sup-yo/', '@type': ['http://www.w3.org/ns/activitystreams#Note'], 'http://www.w3.org/ns/activitystreams#content': [ {'@value': 'Up for some root beer floats?'}]}], 'http://www.w3.org/ns/activitystreams#to': [ {'@id': 'acct:cwebber@identi.ca'}, {'@id': 'acct:justaguy@rhiaro.co.uk'}, {'@id': 'acct:ladyaeva@hedgehog.example'}]}] That might look a bit complicated, but normally you wouldn't work in an expanded document, you'd compact to your local context. If this seems confusing, you don't really need to worry about; Activipy uses json-ld under the hood but you usually won't need to interact with it. One nice feature though is that ActivityStreams 2.0 documents have an "implied context" of `the core ActivityStreams vocabulary `_. This means that a "Note" will always mean the ActivityStreams version of a Note, even if you don't do any fancy context things and are using just plain old json. Even when you get into extension land, Activipy makes things so that you can think as in terms of pythonic constructors rather than json-ld, so your code will look like simple Python, just like at the very beginning of our tutorial. So if you nearly never need to work with this super-extensible version of things, what's the point of us showing you it? Well the interesting here is, since that's the unambiguous "expanded" version, we can now understand how we can get information from another source and clearly understand its meaning. We can also now begin to understand what "compacted" means: the simple JSON representations we've been showing for most of this post! So we can take data from the outside world, expand it into an unambiguous format, and then compact it down to the terminology we actually know. Once compacted, it's in a format that's so simple even poor Billy Scripter with his json and ruby toolbox can use it. And since we've compacted it to a context *we* know, we know that an `Activity` is an `Activity `_ and we'll never confuse "run a program" with someone else's "run a mile" again. Horray! But... the `CheckIn` / `Coupon` examples above included this `@context` key. What is that thing? Let's take a look. It's all contextual ~~~~~~~~~~~~~~~~~~~ As a refresher, some of our early examples looked like this:: >>> root_beer_note {'@id': 'http://tsyesika.co.uk/chat/sup-yo/', '@type': 'Note', 'content': 'Up for some root beer floats?'} >>> root_beer_note.types_expanded ['http://www.w3.org/ns/activitystreams#Note'] No `@context` there. And yet just recently we saw this:: >>> check_in.json() {'@context': 'http://checkup.example/context.jld', '@type': 'CheckIn', 'actor': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'location': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} >>> check_in.types_expanded ['http://checkup.example/ns#CheckIn'] Okay, that does have an `@context`! Well, we're working with extensions, so it's obvious that this maps our vocabulary to the unambiguous definitions we saw in the expanded version. So that's kind of cool, we can imagine that `'http://checkup.example/context.jld'` somehow maps `'CheckIn'` -> `'http://checkup.example/ns#CheckIn'` in our second example. But wait, how did `'Note'` get mapped to `'http://www.w3.org/ns/activitystreams#Note'` in our first example? We didn't specify any context at all! This is because ActivityStreams has an "implied context" of its own vocabulary at `'http://www.w3.org/TR/activitystreams-core/activitystreams2-context.jsonld'`, so that vocabulary mapping context is there without us even having to specify it. You might have noticed a small amount of trickery: `http://checkup.example/context.jld` doesn't exist! You caught us, this is just a demonstration, so we overrode the `default_loader` in the environment to pretend that it knew what was at that URL already. Tricky! It turns out we could have rewritten the `CheckIn` example with the contents of what we were "pretending" was at `http://checkup.example/context.jld`, and it would have worked during expansion just as well:: {'@context': {'CheckIn': {'@id': 'http://checkup.example/ns#CheckIn', '@type': '@id'}, 'Coupon': {'@id': 'http://checkup.example/ns#Coupon', '@type': '@id'}, 'RoyalStatus': {'@id': 'http://checkup.example/ns#RoyalStatus', '@type': '@id'}}, '@type': 'CheckIn', 'actor': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'location': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} Wow, that's *quite* verbose. But that's basically dumping what would have been at `http://checkup.example/context.jld` inline. It turns out there's another kind of interesting way to specify terms in an `@context`, which is namespacing:: {'@context': {'CheckUp': 'http://checkup.example/ns#'}, '@type': 'CheckUp:CheckIn', 'actor': {'@id': 'http://social.example/u/sugartooth/', '@type': 'Person', 'displayName': 'Sarah Sugartooth'}, 'location': {'@id': 'http://sweetexpressions.example/', '@type': 'Place', 'displayName': 'Sweet Expressions'}} If you'd like to play with these, we have environments set up for them at `activipy.demos.checkup` under the varibles `CheckUpVerboseEnv` and `CheckUpNSEnv`. Consume the world ~~~~~~~~~~~~~~~~~ So now you have a solid theoretical understanding of how information can be unambiguously represented in Activipy. But what if we want to consume activities from the outside world? *TODO: Finish this section!* The more we change, the more we stay the same --------------------------------------------- .. TODO: We need functional setters for this part to work :) *TODO: Fill in this section on the immutable properties of Activipy* activipy-0.1/gpl-3.0.txt000066400000000000000000001045131261615066400150360ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . activipy-0.1/guix.scm000066400000000000000000000032171261615066400146740ustar00rootroot00000000000000;;; Activipy --- ActivityStreams 2.0 implementation and testing for Python ;;; Copyright © 2015 Christopher Allan Webber ;;; ;;; This file is part of Activipy. ;;; ;;; Activipy is free software; you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation; either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; Activipy is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with Activipy. If not, see . ;;; Commentary: ;; ;; Development environment for GNU Guix. ;; ;;; Code: (use-modules (guix packages) (guix licenses) (guix build-system python) (gnu packages) (gnu packages python)) (package (name "activipy") (version "0.0") (source #f) (build-system python-build-system) (inputs `(("python" ,python))) (synopsis "ActivityStreams 2.0 implementation and testing for Python") (description "An ActivityStreams 2.0 implementation for Python. Provides an easy API for building ActivityStreams 2.0 based applications as well as a test suite for testing ActivityStreams 2.0 libraries against.") (home-page "TBD :)") ;; technically GPLv3+ or ASL2.0, which reduces to basically ASL2.0, but ;; maybe important if there are future compatibilities with later license ;; versions (license asl2.0)) activipy-0.1/setup.py000066400000000000000000000035041261615066400147250ustar00rootroot00000000000000## Activipy --- ActivityStreams 2.0 implementation and testing for Python ## Copyright © 2015 Christopher Allan Webber ## ## This file is part of Activipy, which is GPLv3+ or Apache v2, your option ## (see COPYING); since that means effectively Apache v2... ## ## Apache v2 header: ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. ## You may obtain a copy of the License at ## ## http://www.apache.org/licenses/LICENSE-2.0 ## ## Unless required by applicable law or agreed to in writing, software ## distributed under the License is distributed on an "AS IS" BASIS, ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. from setuptools import setup, find_packages setup( name="activipy", version="0.1", packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), zip_safe=False, include_package_data=True, install_requires=[ "PyLD", "pytest", "sphinx", ], # @@: Can we reproduce this in Guix? entry_points="""\ [console_scripts] activipy_tester = activipy.testcli:main """, license="Apache v2", author="Christopher Allan Webber", author_email="cwebber@dustycloud.org", description="ActivityStreams 2.0 implementation and testing for Python", long_description="""An ActivityStreams 2.0 implementation for Python. Provides an easy API for building ActivityStreams 2.0 based applications as well as a test suite for testing ActivityStreams 2.0 libraries against.""", classifiers=[ # @@: Might need to drop v2 "License :: OSI Approved :: Apache Software License"])