def test_on_search_result_agents(self, local): """ Test that an agent can do a search for agents. """ with setup_test_agents(3, local, prefix="search_agents") as agents: for a in agents: a.connect() agent_0, agent_1, agent_2 = agents foo_attr = AttributeSchema("foo", int, False, "A foo attribute.") bar_attr = AttributeSchema("bar", str, False, "A bar attribute.") dummy_datamodel = DataModel("dummy_datamodel", [foo_attr, bar_attr]) agent_1.register_agent( 0, Description({ "foo": 15, "bar": "BAR" }, dummy_datamodel)) agent_2.register_agent( 0, Description({ "foo": 5, "bar": "ABC" }, dummy_datamodel)) agent_0.search_agents( 0, Query([Constraint("foo", Eq(0))], dummy_datamodel)) agent_0.search_agents( 0, Query([ Constraint("foo", Gt(10)), Constraint("bar", Gt("B")), ], dummy_datamodel)) agent_0.search_agents( 0, Query([ Constraint("bar", Gt("A")), ], dummy_datamodel)) asyncio.ensure_future(agent_0.async_run()) asyncio.get_event_loop().run_until_complete( asyncio.sleep(_ASYNCIO_DELAY)) agent_1.unregister_agent(0) agent_2.unregister_agent(0) expected_message_01 = (0, []) expected_message_02 = (0, [agent_1.public_key]) expected_message_03 = (0, [agent_1.public_key, agent_2.public_key]) assert 3 == len(agent_0.received_msg) assert expected_message_01 == agent_0.received_msg[0] assert expected_message_02 == agent_0.received_msg[1] assert expected_message_03 == agent_0.received_msg[2]
def test_raise_exception_when_duplicated_attribute_name(self): """Test that if we try to instantiate a DataModel with a list of attributes with not unique names we raise an exception.""" with pytest.raises( ValueError, match=r"Invalid input value for type.*duplicated attribute name" ): DataModel("bar", [ AttributeSchema("foo", bool, True), AttributeSchema("foo", str, False) ])
def test_generate_schema_longer_values(self, ): """ Test that construct_schema constructs the correct schema from longer dict of values """ values = {"foo": "bar", "number": 1234, "float": 123.4, "bool": True} schema_attributes = [ AttributeSchema("foo", str, True, ""), AttributeSchema("number", int, True, ""), AttributeSchema("float", float, True, ""), AttributeSchema("bool", bool, True, "") ] generate_schema_checker("foo", values, DataModel("foo", schema_attributes))
def test_unregister_service(self, local): """ Test that the unregistration of services works correctly. """ with setup_test_agents(2, local, prefix="unregister_service") as agents: for a in agents: a.connect() agent_0, agent_1 = agents dummy_datamodel = DataModel("dummy_datamodel", [AttributeSchema("foo", int, False)]) dummy_service_description = Description({}, dummy_datamodel) agent_1.register_service(0, dummy_service_description) agent_1.unregister_service(0, dummy_service_description) agent_0.search_services( 0, Query([Constraint("foo", Eq(0))], dummy_datamodel)) asyncio.ensure_future(agent_0.async_run()) asyncio.get_event_loop().run_until_complete( asyncio.sleep(_ASYNCIO_DELAY)) assert 1 == len(agent_0.received_msg) assert (0, []) == agent_0.received_msg[0]
def build_datamodel(good_pbks: List[str], is_supply: bool) -> DataModel: """ Build a data model for supply and demand (i.e. for offered or requested goods). :param good_pbks: the list of good public keys :param is_supply: Boolean indicating whether it is a supply or demand data model :return: the data model. """ goods_quantities_attributes = [ AttributeSchema(good_pbk, int, False) for good_pbk in good_pbks ] price_attribute = AttributeSchema("price", float, False) description = TAC_SUPPLY_DATAMODEL_NAME if is_supply else TAC_DEMAND_DATAMODEL_NAME data_model = DataModel(description, goods_quantities_attributes + [price_attribute]) return data_model
def test_serialization(self, attribute): """Test that serialization and deserialization of AttributeSchema objects work correctly.""" actual_attribute_schema = attribute actual_attribute_pb = actual_attribute_schema.to_pb( ) # type: query_pb2.Query.Attribute expected_attribute_schema = AttributeSchema.from_pb( actual_attribute_pb) assert actual_attribute_schema == expected_attribute_schema
def attributes_schema(draw): attr_name = draw(text()) attr_type = draw(attribute_schema_types) attr_required = draw(booleans()) attr_description = draw(text()) return AttributeSchema(attr_name, attr_type, attr_required, attr_description)
class TRIP_DATAMODEL(DataModel): PERSON_ID = AttributeSchema("account_id", str, is_attribute_required=True, attribute_description="Person ID.") CAN_BE_DRIVER = AttributeSchema( "can_be_driver", bool, is_attribute_required=True, attribute_description="Can person be driver?") TRIP_ID = AttributeSchema("trip_id", str, is_attribute_required=True, attribute_description="Person ID.") FROM_LOCATION_LATITUDE = AttributeSchema( "from_location_latitude", float, is_attribute_required=True, attribute_description="Latitude of FROM point.") FROM_LOCATION_LONGITUDE = AttributeSchema( "from_location_longitude", float, is_attribute_required=True, attribute_description="Longitude of FROM point.") TO_LOCATION_LATITUDE = AttributeSchema( "to_location_latitude", float, is_attribute_required=True, attribute_description="Latitude of TO point.") TO_LOCATION_LONGITUDE = AttributeSchema( "to_location_longitude", float, is_attribute_required=True, attribute_description="Longitude of TO point.") DISTANCE_AREA = AttributeSchema( "distance_area", float, is_attribute_required=False, attribute_description= "Allowed distance of area from center of way-point.") def __init__(self): super().__init__("trip_datamodel", [ self.PERSON_ID, self.CAN_BE_DRIVER, self.TRIP_ID, self.FROM_LOCATION_LATITUDE, self.FROM_LOCATION_LONGITUDE, self.TO_LOCATION_LATITUDE, self.TO_LOCATION_LONGITUDE, self.DISTANCE_AREA ], "Trip create fully.")
def modlify(data): attributes = data['attributes'] # This is the least restrictive apporach. # To make attributes required override the specific one with True as the third argument. attribute_list = [] for key, value in attributes.items(): attribute_list.append(AttributeSchema(key, bool, False, value)) data_model = DataModel(data['name'], attribute_list, data['description']) return data_model
def test_query_invalid_when_constraint_attribute_name_different_type(self): """Test that we raise an exception when at least one constraint attribute name has a different type wrt the data model.""" with pytest.raises(ValueError, match=""): a_query = Query( [Constraint("an_attribute_name", Eq(0))], DataModel("a_data_model", [AttributeSchema("an_attribute_name", str, True)]))
def test_generate_schema_single_element(self, schema_name, attribute_name, value_type_pair, description): """ Test that construct_schema constructs the correct schema from single-element attribute values """ value, type_ = value_type_pair generate_schema_checker( schema_name, {attribute_name: value}, DataModel( schema_name, [AttributeSchema(attribute_name, type_, True, description)]))
def load_service(self, metadata, load_path): dataset_info = metadata['base'] attributes = description = {} for item in dataset_info['tags']: attributes[item] = description[item] = 'Tagged: ' + item attribute_list = [] for key, value in attributes.items(): attribute_list.append(AttributeSchema(key, str, False, value)) data_model = DataModel(dataset_info['name'], attribute_list, dataset_info['description']) service = Description(description, data_model) data = Utils.load_json(load_path) return service, data
def test_group_dialogue_one_client_n_servers(self): with NetworkOEFNode(): client_proxy = OEFNetworkProxy("client", oef_addr="127.0.0.1", port=3333) client = ClientAgentGroupDialogueTest(client_proxy) client.connect() N = 10 server_proxies = [ OEFNetworkProxy("server_{:02d}".format(i), oef_addr="127.0.0.1", port=3333) for i in range(N) ] server_data_model = DataModel("server", [AttributeSchema("foo", bool, True)]) servers = [ ServerAgentTest(server_proxy, price=random.randint(10, 100)) for server_proxy in server_proxies ] for server in servers: server.connect() server.register_service( 0, Description({"foo": True}, server_data_model)) best_server = ServerAgentTest(OEFNetworkProxy("best_server", oef_addr="127.0.0.1", port=3333), price=5) best_server.connect() best_server.register_service( 0, Description({"foo": True}, server_data_model)) servers.append(best_server) asyncio.get_event_loop().run_until_complete( asyncio.sleep(_ASYNCIO_DELAY)) query = Query([Constraint("foo", Eq(True))], server_data_model) client.search_services(0, query) asyncio.get_event_loop().run_until_complete( asyncio.gather(client.async_run(), *[server.async_run() for server in servers])) assert "best_server" == client.group.best_agent assert 5 == client.group.best_price
class TRANSPORT_DATAMODEL(DataModel): PRICE_PER_KM = AttributeSchema( "price_per_km", float, is_attribute_required=True, attribute_description="Provides the price per kilometer.") STATE = AttributeSchema( "state", str, is_attribute_required=True, attribute_description="Current state of transport.") DRIVER_ID = AttributeSchema("driver_id", str, is_attribute_required=False, attribute_description="Driver.") PASSENGERS = AttributeSchema("passengers_ids", str, is_attribute_required=False, attribute_description="All passangers.") LOCATION_LATITUDE = AttributeSchema( "location_latitude", float, is_attribute_required=True, attribute_description="Latitude of transport.") LOCATION_LONGITUDE = AttributeSchema( "location_longitude", float, is_attribute_required=True, attribute_description="Longitude of transport.") def __init__(self): super().__init__("transport_datamodel", [ self.PRICE_PER_KM, self.STATE, self.DRIVER_ID, self.PASSENGERS, self.LOCATION_LATITUDE, self.LOCATION_LONGITUDE ], "Transport create fully.")
# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018 Fetch.AI Limited # # 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 oef.schema import DataModel, AttributeSchema PRICE_PER_KM = AttributeSchema( "price_per_km", bool, is_attribute_required=True, attribute_description="Provides the price per kilometer.") JOURNEY_MODEL = DataModel("journey", [PRICE_PER_KM], "All possible scooter data.")
# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018 Fetch.AI Limited # # 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 oef.schema import DataModel, AttributeSchema PARKING_PRICE_PER_1HOUR = AttributeSchema( "price_per_1hour", bool, is_attribute_required=True, attribute_description="Provides the price per 1 hour of parking.") PARKING_JOURNEY_MODEL = DataModel("journey", [PARKING_PRICE_PER_1HOUR], "All possible parking data.")
# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018 Fetch.AI Limited # # 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 oef.schema import DataModel, AttributeSchema CHARGE_PRICE_PER_20MIN = AttributeSchema( "price_per_20m", bool, is_attribute_required=True, attribute_description="Provides the price per 20 minutes of charging.") CHARGE_JOURNEY_MODEL = DataModel("journey", [CHARGE_PRICE_PER_20MIN], "All possible charge data.")
class ControllerAgent(OEFAgent): """Class for a controller agent.""" CONTROLLER_DATAMODEL = DataModel("tac", [ AttributeSchema("version", int, True, "Version number of the TAC Controller Agent."), ]) def __init__(self, name: str = "controller", oef_addr: str = "127.0.0.1", oef_port: int = 10000, version: int = 1, monitor: Optional[Monitor] = None, **kwargs): """ Initialize a Controller Agent for TAC. :param name: The name of the OEF Agent. :param oef_addr: the OEF address. :param oef_port: the OEF listening port. :param version: the version of the TAC controller. :param monitor: the dashboard monitor. If None, defaults to a null (dummy) monitor. """ self.name = name self.crypto = Crypto() super().__init__(self.crypto.public_key, oef_addr, oef_port, loop=asyncio.new_event_loop()) logger.debug( "[{}]: Initialized myself as Controller Agent :\n{}".format( self.name, pprint.pformat(vars()))) self.dispatcher = ControllerDispatcher(self) self.monitor = NullMonitor( ) if monitor is None else monitor # type: Monitor self.version = version self.game_handler = None # type: Optional[GameHandler] self.last_activity = datetime.datetime.now() self._message_processing_task = None self._timeout_checker_task = None self._is_running = False self._terminated = False def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes) -> None: """ Handle a simple message. The TAC Controller expects that 'content' is a Protobuf serialization of a tac.messages.Request object. The request is dispatched to the right request handler (using the ControllerDispatcher). The handler returns an optional response, that is sent back to the sender. Notice: the message sent back has the same message id, such that the client knows to which request the response is associated to. :param msg_id: the message id :param dialogue_id: the dialogue id :param origin: the public key of the sender. :param content: the content of the message. :return: None """ logger.debug( "[{}] on_message: msg_id={}, dialogue_id={}, origin={}".format( self.name, msg_id, dialogue_id, origin)) self.update_last_activity() response = self.dispatcher.process_request( content, origin) # type: Optional[Response] if response is not None: self.send_message(msg_id, dialogue_id, origin, response.serialize()) def on_oef_error(self, answer_id: int, operation: OEFErrorOperation) -> None: """ Handle an oef error. :param answer_id: the answer id :param operation: the oef operation :return: None """ logger.error( "[{}]: Received OEF error: answer_id={}, operation={}".format( self.name, answer_id, operation)) def on_dialogue_error(self, answer_id: int, dialogue_id: int, origin: str) -> None: """ Handle a dialogue error. :param answer_id: the answer id :param dialogue_id: the dialogue id :param origin: the public key of the sending agent :return: None """ logger.error( "[{}]: Received Dialogue error: answer_id={}, dialogue_id={}, origin={}" .format(self.name, answer_id, dialogue_id, origin)) def register(self): """ Register on the OEF as a TAC controller agent. :return: None. """ desc = Description({"version": 1}, data_model=self.CONTROLLER_DATAMODEL) logger.debug("[{}]: Registering with {} data model".format( self.name, desc.data_model.name)) self.register_service(0, desc) def dump(self, directory: str, experiment_name: str) -> None: """ Dump the details of the simulation. :param directory: the directory where experiments details are listed. :param experiment_name: the name of the folder where the data about experiment will be saved. :return: None. """ experiment_dir = directory + "/" + experiment_name if self.game_handler is None or not self.game_handler.is_game_running( ): logger.warning( "[{}]: Game not present. Using empty dictionary.".format( self.name)) game_dict = {} # type: Dict[str, Any] else: game_dict = self.game_handler.current_game.to_dict() os.makedirs(experiment_dir, exist_ok=True) with open(os.path.join(experiment_dir, "game.json"), "w") as f: json.dump(game_dict, f) def terminate(self) -> None: """ Terminate the controller agent. :return: None """ if self._is_running: logger.debug("[{}]: Terminating the controller...".format( self.name)) self._is_running = False self.game_handler.notify_tac_cancelled() self._loop.call_soon_threadsafe(self.stop) self._message_processing_task.join() self._message_processing_task = None if self.monitor.is_running: self.monitor.stop() def check_inactivity_timeout(self, rate: Optional[float] = 2.0) -> None: """ Check periodically if the timeout for inactivity or competition expired. :param: rate: at which rate (in seconds) the frequency of the check. :return: None """ logger.debug( "[{}]: Started job to check for inactivity of {} seconds. Checking rate: {}" .format( self.name, self.game_handler.tac_parameters.inactivity_timedelta. total_seconds(), rate)) while True: if self._is_running is False: return time.sleep(rate) current_time = datetime.datetime.now() inactivity_duration = current_time - self.last_activity if inactivity_duration > self.game_handler.tac_parameters.inactivity_timedelta: logger.debug( "[{}]: Inactivity timeout expired. Terminating...".format( self.name)) self.terminate() return elif current_time > self.game_handler.tac_parameters.end_time: logger.debug( "[{}]: Competition timeout expired. Terminating...".format( self.name)) self.terminate() return def update_last_activity(self): """Update the last activity tracker.""" self.last_activity = datetime.datetime.now() def handle_competition(self, tac_parameters: TACParameters): """ Start a Trading Agent Competition. :param tac_parameters: the parameter of the competition. :return: """ logger.debug("[{}]: Starting competition with parameters: {}".format( self.name, pprint.pformat(tac_parameters.__dict__))) self._is_running = True self._message_processing_task = Thread(target=self.run) self.monitor.start(None) self.monitor.update() self.game_handler = GameHandler(self, tac_parameters) self._message_processing_task.start() if self.game_handler.handle_registration_phase(): # start the inactivity timeout. self._timeout_checker_task = Thread( target=self.check_inactivity_timeout) self._timeout_checker_task.run() else: self.terminate() def wait_and_handle_competition(self, tac_parameters: TACParameters) -> None: """ Wait until the current time is greater than the start time, then, start the TAC. :param tac_parameters: the parameters for TAC. :return: None """ now = datetime.datetime.now() logger.debug( "[{}]: waiting for starting the competition: start_time={}, current_time={}, timedelta ={}s" .format(self.name, str(tac_parameters.start_time), str(now), (tac_parameters.start_time - now).total_seconds())) seconds_to_wait = (tac_parameters.start_time - now).total_seconds() time.sleep(0.5 if seconds_to_wait < 0 else seconds_to_wait) self.handle_competition(tac_parameters)
def test_raise_when_have_incorrect_types(self): """ Test that if an attribute value has a value inconsistent with its schema, we moan. """ check_inconsistency_checker([AttributeSchema("foo", float, True)], {"foo": "bar"}, "incorrect type")
def test_raise_when_have_unallowed_types(self): """ Test that if an attribute value has a value inconsistent with its schema, we moan. """ check_inconsistency_checker([AttributeSchema("foo", tuple, True)], {"foo": tuple()}, "unallowed type")
def test_raise_when_not_required_attribute_is_omitted(self): """ Test that if we miss out a required attribute, we moan about it """ check_inconsistency_checker([AttributeSchema("foo", str, True)], {}, "Missing required attribute.")
# -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # # Copyright 2018 Fetch.AI Limited # # 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 oef.schema import DataModel, AttributeSchema PRICE_PER_ENERGY_PERCENT = AttributeSchema( "price_per_energy_percent", bool, is_attribute_required=True, attribute_description="Provides the price per one energy percent") JOURNEY_MODEL = DataModel("journey", [PRICE_PER_ENERGY_PERCENT], "All possible scooter data.")
# Instead of having an OEF Node running somewhere, we can use a in-process instance of an OEF Node. # It will run and process messages concurrently with the other agents. local_node = OEFLocalProxy.LocalNode() client_proxy = OEFLocalProxy("greetings_client", local_node) server_proxy = OEFLocalProxy("greetings_server", local_node) # create agents client_agent = GreetingsAgent(client_proxy) server_agent = GreetingsAgent(server_proxy) # connect the agents to the OEF client_agent.connect() server_agent.connect() # register the greetings service agent on the OEF say_hello = AttributeSchema("say_hello", bool, True, "The agent answers to 'hello' messages.") greetings_model = DataModel("greetings", [say_hello], "Greetings service.") greetings_description = Description({"say_hello": True}, greetings_model) server_agent.register_service(0, greetings_description) # the client executes the search for greetings services # we are looking for services that answers to "hello" messages query = Query([Constraint("say_hello", Eq(True))], greetings_model) print("[{}]: Search for 'greetings' services. search_id={}".format( client_agent.public_key, 0)) client_agent.search_services(0, query) # run the agents try: loop = asyncio.get_event_loop()
def test_not_equal_when_compared_with_different_type(self): a_query = Query([Constraint("foo", Eq(0))], DataModel("bar", [AttributeSchema("foo", int, True)])) not_a_query = tuple() assert a_query != not_a_query
import json import unittest import sys import csv import os from oef.schema import Description from utils.src.python.protostuff import dictToProto, protoToDict from protocol.src.proto import agent_pb2 from oef.schema import DataModel, AttributeSchema from oef.agents import OefMessageHandler WIND_SPEED_ATTR = AttributeSchema( "wind_speed", bool, is_attribute_required=True, attribute_description="Provides wind speed measurements.") TEMPERATURE_ATTR = AttributeSchema( "temperature", bool, is_attribute_required=True, attribute_description="Provides temperature measurements.") AIR_PRESSURE_ATTR = AttributeSchema( "air_pressure", bool, is_attribute_required=True, attribute_description="Provides air pressure measurements.")
origin)) self.send_message(1, dialogue_id, origin, content) if __name__ == '__main__': # create agent and connect it to OEF server_agent = EchoServiceAgent("echo_server", oef_addr="oef-node", oef_port=10000, loop=asyncio.get_event_loop()) server_agent.connect() # register a service on the OEF echo_feature = AttributeSchema( "does_echo", bool, True, "Whether the service agent can do echo or not.") echo_model = DataModel("echo", [echo_feature], "echo service.") echo_description = Description({"does_echo": True}, echo_model) msg_id = 22 server_agent.register_service(msg_id, echo_description) # run the agent print("[{}]: Waiting for messages...".format(server_agent.public_key)) try: server_agent.run() finally: print("[{}]: Disconnecting...".format(server_agent.public_key)) server_agent.stop() server_agent.disconnect()
# 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 oef.schema import DataModel, AttributeSchema, Location PRICE_PER_KM = AttributeSchema("price_per_km", int, True, "Provides the price per kilometer.") PRICE_KWH = AttributeSchema("price_kilowatt_hour", int, True, "Provides the price kilowatt hour.") CHARGER_BONUS = AttributeSchema("charger_bonus", int, False, "Provides bonus cash back for charging here.") CHARGER_LOCATION = AttributeSchema("charger_location", Location, True, "The location of the charger ") CHARGER_AVAILABLE = AttributeSchema( "charger_available", bool, True, "Provides the availability of the charger ") CHARGER_OWNER = AttributeSchema( "charger_owner", str, False, "Provides charger's owner, " "it could be person id or name name")