diff options
-rw-r--r-- | python/Makefile | 2 | ||||
-rwxr-xr-x | python/examples/api/spout | 3 | ||||
-rw-r--r-- | python/qpid/address.py | 13 | ||||
-rw-r--r-- | python/qpid/messaging.py | 178 | ||||
-rw-r--r-- | python/qpid/tests/address.py | 20 |
5 files changed, 200 insertions, 16 deletions
diff --git a/python/Makefile b/python/Makefile index 7f475adc09..2c7f9b8de0 100644 --- a/python/Makefile +++ b/python/Makefile @@ -50,7 +50,7 @@ build: $(TARGETS) doc: @mkdir -p $(BUILD) - PYTHONPATH=. epydoc qpid.messaging -o $(BUILD)/doc --no-private --no-sourcecode --include-log + PYTHONPATH=. epydoc -v qpid.messaging -o $(BUILD)/doc --no-private --no-sourcecode --include-log install: build install -d $(PYTHON_LIB) diff --git a/python/examples/api/spout b/python/examples/api/spout index 6a9b2b6e3d..1928303e43 100755 --- a/python/examples/api/spout +++ b/python/examples/api/spout @@ -43,8 +43,9 @@ parser.add_option("-t", "--timeout", type=float, default=None, parser.add_option("-i", "--id", help="use the supplied id instead of generating one") parser.add_option("-r", "--reply-to", help="specify reply-to address") parser.add_option("-P", "--property", dest="properties", action="append", default=[], - help="specify message property") + metavar="NAME=VALUE", help="specify message property") parser.add_option("-M", "--map", dest="entries", action="append", default=[], + metavar="KEY=VALUE", help="specify map entry for message body") opts, args = parser.parse_args() diff --git a/python/qpid/address.py b/python/qpid/address.py index 6228ac757b..bda7157a5f 100644 --- a/python/qpid/address.py +++ b/python/qpid/address.py @@ -102,8 +102,8 @@ class AddressParser(Parser): result = {} while True: - if self.matches(ID): - n, v = self.nameval() + if self.matches(NUMBER, STRING, ID, LBRACE, LBRACK): + n, v = self.keyval() result[n] = v if self.matches(COMMA): self.eat(COMMA) @@ -114,16 +114,17 @@ class AddressParser(Parser): elif self.matches(RBRACE): break else: - raise ParseError(self.next(), ID, RBRACE) + raise ParseError(self.next(), NUMBER, STRING, ID, LBRACE, LBRACK, + RBRACE) self.eat(RBRACE) return result - def nameval(self): - name = self.eat(ID).value + def keyval(self): + key = self.value() self.eat(COLON) val = self.value() - return (name, val) + return (key, val) def value(self): if self.matches(NUMBER, STRING, ID): diff --git a/python/qpid/messaging.py b/python/qpid/messaging.py index 4f2c190ce2..ca8483eef0 100644 --- a/python/qpid/messaging.py +++ b/python/qpid/messaging.py @@ -22,8 +22,6 @@ A candidate high level messaging API for python. Areas that still need work: - - asynchronous send - - asynchronous error notification - definition of the arguments for L{Session.sender} and L{Session.receiver} - standard L{Message} properties - L{Message} content encoding @@ -247,8 +245,180 @@ class Session: """ Sessions provide a linear context for sending and receiving - messages, and manage various Senders and Receivers. - """ + L{Messages<Message>}. L{Messages<Message>} are sent and received + using the L{Sender.send} and L{Receiver.fetch} methods of the + L{Sender} and L{Receiver} objects associated with a Session. + + Each L{Sender} and L{Receiver} is created by supplying either a + target or source address to the L{sender} and L{receiver} methods of + the Session. The address is supplied via a string syntax documented + below. + + Addresses + ========= + + An address identifies a source or target for messages. In its + simplest form this is just a name. In general a target address may + also be used as a source address, however not all source addresses + may be used as a target, e.g. a source might additionally have some + filtering criteria that would not be present in a target. + + A subject may optionally be specified along with the name. When an + address is used as a target, any subject specified in the address is + used as the default subject of outgoing messages for that target. + When an address is used as a source, any subject specified in the + address is pattern matched against the subject of available messages + as a filter for incoming messages from that source. + + The options map contains additional information about the address + including: + + - policies for automatically creating, and deleting the node to + which an address refers + + - policies for asserting facts about the node to which an address + refers + + - extension points that can be used for sender/receiver + configuration + + Mapping to AMQP 0-10 + -------------------- + The name is resolved to either an exchange or a queue by querying + the broker. + + The subject is set as a property on the message. Additionally, if + the name refers to an exchange, the routing key is set to the + subject. + + Syntax + ------ + The following regular expressions define the tokens used to parse + addresses:: + LBRACE: \\{ + RBRACE: \\} + LBRACK: \\[ + RBRACK: \\] + COLON: : + SEMI: ; + SLASH: / + COMMA: , + NUMBER: [+-]?[0-9]*\\.?[0-9]+ + ID: [a-zA-Z_](?:[a-zA-Z0-9_-]*[a-zA-Z0-9_])? + STRING: "(?:[^\\\\"]|\\\\.)*"|\'(?:[^\\\\\']|\\\\.)*\' + ESC: \\\\[^ux]|\\\\x[0-9a-fA-F][0-9a-fA-F]|\\\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] + SYM: [.#*%@$^!+-] + WSPACE: [ \\n\\r\\t]+ + + The formal grammar for addresses is given below:: + address = name [ "/" subject ] [ ";" options ] + name = ( part | quoted )+ + subject = ( part | quoted | "/" )* + quoted = STRING / ESC + part = LBRACE / RBRACE / COLON / COMMA / NUMBER / ID / SYM + options = map + map = "{" ( keyval ( "," keyval )* )? "}" + keyval = ID ":" value + value = NUMBER / STRING / ID / map / list + list = "[" ( value ( "," value )* )? "]" + + This grammar resuls in the following informal syntax:: + + <name> [ / <subject> ] [ ; <options> ] + + Where options is:: + + { <key> : <value>, ... } + + And values may be: + - numbers + - single, double, or non quoted strings + - maps (dictionaries) + - lists + + Options + ------- + The options map permits the following parameters:: + + <name> [ / <subject> ] { + create: <create-policy>, + delete: <delete-policy>, + assert: <assert-policy>, + node-properties: { + type: <node-type>, + durable: <node-durability>, + x-properties: { + bindings: ["<exchange>/<key>", ...], + <passthrough-key>: <passthrough-value> + } + } + } + + The create, delete, and assert policies specify who should perfom + the associated action: + + - I{always}: the action will always be performed + - I{sender}: the action will only be performed by the sender + - I{receiver}: the action will only be performed by the receiver + - I{never}: the action will never be performed (this is the default) + + The node-type is one of: + + - I{topic}: a topic node will default to the topic exchange, + x-properties may be used to specify other exchange types + - I{queue}: this is the default node-type + + The x-properties map permits arbitrary additional keys and values to + be specified. These keys and values are passed through when creating + a node or asserting facts about an existing node. Any passthrough + keys and values that do not match a standard field of the underlying + exchange or queue declare command will be sent in the arguments map. + + Examples + -------- + A simple name resolves to any named node, usually a queue or a + topic:: + + my-queue-or-topic + + A simple name with a subject will also resolve to a node, but the + presence of the subject will cause a sender using this address to + set the subject on outgoing messages, and receivers to filter based + on the subject:: + + my-queue-or-topic/my-subject + + A subject pattern can be used and will cause filtering if used by + the receiver. If used for a sender, the literal value gets set as + the subject:: + + my-queue-or-topic/my-* + + In all the above cases, the address is resolved to an existing node. + If you want the node to be auto-created, then you can do the + following. By default nonexistent nodes are assumed to be queues:: + + my-queue; {create: always} + + You can customize the properties of the queue:: + + my-queue; {create: always, node-properties: {durable: True}} + + You can create a topic instead if you want:: + + my-queue; {create: always, node-properties: {type: topic}} + + You can assert that the address resolves to a node with particular + properties:: + + my-transient-topic; { + assert: always, + node-properties: { + type: topic, + durable: False + } + } + """ def __init__(self, connection, name, transactional): self.connection = connection diff --git a/python/qpid/tests/address.py b/python/qpid/tests/address.py index 7c101eee5e..8a1a8dcc63 100644 --- a/python/qpid/tests/address.py +++ b/python/qpid/tests/address.py @@ -136,12 +136,13 @@ class AddressTests(ParserBase, Test): def testBadOptions1(self): self.invalid("name/subject; {", - "expecting (ID, RBRACE), got EOF line:1,15:name/subject; {") + "expecting (NUMBER, STRING, ID, LBRACE, LBRACK, RBRACE), " + "got EOF line:1,15:name/subject; {") def testBadOptions2(self): self.invalid("name/subject; { 3", - "expecting (ID, RBRACE), got NUMBER('3') " - "line:1,16:name/subject; { 3") + "expecting COLON, got EOF " + "line:1,17:name/subject; { 3") def testBadOptions3(self): self.invalid("name/subject; { key:", @@ -160,7 +161,7 @@ class AddressTests(ParserBase, Test): def testBadOptions6(self): self.invalid("name/subject; { key: value,", - "expecting (ID, RBRACE), got EOF " + "expecting (NUMBER, STRING, ID, LBRACE, LBRACK, RBRACE), got EOF " "line:1,27:name/subject; { key: value,") def testBadOptions7(self): @@ -197,3 +198,14 @@ class AddressTests(ParserBase, Test): def testBadList4(self): self.invalid("name/subject; { key: [ 1 2 ] }", "expecting (COMMA, RBRACK), " "got NUMBER('2') line:1,25:name/subject; { key: [ 1 2 ] }") + + def testMap1(self): + self.valid("name/subject; { 'key': value }", + "name", "subject", {"key": "value"}) + + def testMap2(self): + self.valid("name/subject; { 1: value }", "name", "subject", {1: "value"}) + + def testMap3(self): + self.valid('name/subject; { "foo.bar": value }', + "name", "subject", {"foo.bar": "value"}) |