FandangoParty Reference#
All communication between Fandango and its parties is established via FandangoParty classes.
Added in version 1.1: These features are available in Fandango 1.1 and later.
The following diagram illustrates the classes and methods discussed in this chapter:
classDiagram
class FandangoParty {
<<abstract>>
FandangoParty(connection_mode: ConnectionMode, party_name: str | None)
send(message: DerivationTree | str | bytes, recipient: str | None)
receive(message: str | bytes, sender: str | None)
start()
stop()
}
FandangoParty <|-- NetworkParty
class NetworkParty{
NetworkParty(uri: str, connection_mode: ConnectionMode)
ip: str
port: int
}
NetworkParty <|-- Server
NetworkParty <|-- Client
NetworkParty <|.. `(Own Classes)`
FandangoParty <|-- In
FandangoParty <|-- Out
FandangoParty <|-- StdIn
FandangoParty <|-- StdOut
class ConnectionMode {
<<enumeration>>
OPEN
CONNECT
EXTERNAL
}
click FandangoParty href "#the-fandangoparty-class" "Base class for communicating with a party"
click NetworkParty href "#the-networkparty-class" "Connect to an Internet party"
click In href "#in-and-out" "Connect to standard input of an external program"
click Out href "#in-and-out" "Connect to standard output of an external program"
click StdIn href "#in-and-out" "Connect to standard input of Fandango"
click StdOut href "#in-and-out" "Connect to standard output of Fandango"
click Client href "#client-and-server" "Connect to a client"
click Server href "#client-and-server" "Connect to a server"
click ConnectionMode href "#the-fandangoparty-class" "Connection mode for a FandangoParty"
All classes are predefined within .fan files; they need not be imported.
The FandangoParty class#
FandangoParty is an abstract base class. It is meant to serve as base class for subclasses and cannot be directly instantiated.
Constructor#
FandangoParty(connection_mode: ConnectionMode, party_name: str | None = None)
connection_mode: Can be one of three values:ConnectionMode.OPEN- Fandango behaves as a server. It opens a port and waits for incoming connections.ConnectionMode.CONNECT- Fandango behaves as a client. It uses an existing port and connects to it.ConnectionMode.EXTERNAL- The party is an external party; no connection is made by Fandango. Messages annotated with this party are not produced by Fandango, but are expected to be received from an external party.
party_name: The name of the party. IfNone, the class name is used.
The send() method#
On a FandangoParty object, the send() method is used to send a message (as a DerivationTree object) to a party.
send(message: DerivationTree | str | bytes, recipient: Optional[str]) -> None
message: DerivationTree | str | bytes: The message to send.recipient: str: The recipient of the message. Only present if the grammar specifies a recipient.
This method is typically overloaded and extended in subclasses.
For instance, to apply a compress() method to every message sent, write
class CompressedNetworkParty(NetworkParty):
def send(self, message: DerivationTree | str | bytes, recipient: Optional[str]) -> None:
super().send(compress(message), recipient)
Important
Fandango itself will always invoke send() with a DerivationTree as argument.
However, the FandangoParty classes all support sending DerivationTree, str, and bytes types, so you do not have to re-create a DerivationTree object when calling send().
The receive() method#
On a FandangoParty object, the receive() method is invoked when data has been received by the party.
receive(message: str | bytes | None), sender: Optional[str]) -> None
message: str | bytes | None: The message received.recipient: str: The sender of the message.
This method is typically overloaded and extended in subclasses.
For instance, to apply a decompress() method to every message received, write
class CompressedNetworkParty(NetworkParty):
def receive(self, message: str | bytes | None, sender: Optional[str]) -> None:
super().receive(decompress(message), sender)
Note that a party can also receive None-type messages.
These signal the party that its communication partner has closed the connection.
Fandango does assume that a party only closes the connection when the protocol execution is complete.
The party specification throws an exception if its communication partner closes the connection during the protocol execution.
If you want to allow for connection closures during the protocol execution, you have to overload the receive() function to handle None-type messages.
The start() and stop() methods#
On a FandangoParty object, the start() and stop() methods are invoked to establish or shut down a connection.
start() -> None
stop() -> None
These methods can be used in subclasses to add additional behavior.
The instance() class method#
FandangoParty provides an instance() class method that retrieves the last created instance of the given class.
instance(party_name: str | None = None) -> FandangoParty
instance() is typically invoked prefixed with the respective class, as in
Client.instance().stop() # Stop the client
One can also invoke it with a party name; in that case, the class is ignored (use FandangoParty), and instance() retrieves the instance created with that name.
FandangoParty.instance("Client").stop() # Equivalent to the above.
The instance() method is not thread-safe and cannot identify a party implementation from a thread different from the main thread.
The receive-method of a party implementation is often not invoked from the main thread, as listen loops usually run asynchronously.
Use the local reference to self.io_instance.parties["PartyName"] in those cases to access the party implementation.
Generators and Converters run on the main thread and can safely access the party implementation.
The NetworkParty class#
The Fandango NetworkParty class (a subclass of FandangoParty) implements an Internet connection to a communication party.
It implements the FandangoParty methods, as described above, plus the following methods:
Constructor#
NetworkParty(uri: str, connection_mode: ConnectionMode = ConnectionMode.CONNECT)
uri(string): The party specification of the party to connect to. Format:[NAME=][PROTOCOL:][//][HOST:]PORT.NAME: Party name. Default: the class name.PROTOCOL:tcp(default) orudp.HOST: host name; use[...]for IPv6 host names. Default: 127.0.0.1PORT: the port to connect to (0-65535)
connection_mode: Can be one of three values:ConnectionMode.OPEN- Fandango behaves as a server. It opens the given URI and waits for incoming connections.ConnectionMode.CONNECT- Fandango behaves as a client. It connects to the given URI.ConnectionMode.EXTERNAL- The party is an external party; no connection is made by Fandango. Messages annotated with this party are not produced by Fandango, but are expected to be received from an external party.
Properties#
ip: str
The IP address or host used. Can be assigned to.
port: int
The port. Can be assigned to.
Predefined Parties#
The following parties are predefined in Fandango:
In and Out#
In and Out are FandangoParty classes connecting to the standard input and the standard output of an invoked program.
In()
Constructor.
Out()
Constructor.
StdIn and StdOut#
StdIn and StdOut are FandangoParty classes connecting to the standard input and the standard output of Fandango.
StdIn()
Constructor.
StdOut()
Constructor.
Client and Server#
Server are NetworkParty classes created with the --server option on the fandango executable command line.
If --client URI is given, fandango becomes a Client, and the classes are created as
class Client(NetworkParty):
def __init__(self):
super().__init__(
URI, # the argument in `--client URI`
connection_mode=ConnectionMode.CONNECT,
)
self.start()
class Server(NetworkParty):
def __init__(self):
super().__init__(
URI, # the argument in `--client URI`
connection_mode=ConnectionMode.EXTERNAL,
)
self.start()
If --server URI is given, fandango becomes a Server, and the classes are created as
class Client(NetworkParty):
def __init__(self):
super().__init__(
URI, # the argument in `--server URI`
connection_mode=ConnectionMode.EXTERNAL,
)
self.start()
class Server(NetworkParty):
def __init__(self):
super().__init__(
URI, # the argument in `--server URI`
connection_mode=ConnectionMode.OPEN,
)
self.start()