def client_test_write_property(servient, protocol_client_cls, timeout=None): """Helper function to test Property writes on bindings clients.""" exposed_thing = next(servient.exposed_things) prop_name = uuid.uuid4().hex exposed_thing.add_property(prop_name, PropertyFragmentDict({ "type": "string", "observable": True }), value=Faker().sentence()) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() prop_value = Faker().sentence() prev_value = yield exposed_thing.properties[prop_name].read() assert prev_value != prop_value yield protocol_client.write_property(td, prop_name, prop_value, timeout=timeout) curr_value = yield exposed_thing.properties[prop_name].read() assert curr_value == prop_value run_test_coroutine(test_coroutine)
def test_coroutine(): td = ThingDescription.from_thing(exposed_thing.thing) event_name = next(six.iterkeys(td.events)) future_conn = Future() future_event = Future() payload = Faker().sentence() def on_next(ev): if not future_conn.done(): future_conn.set_result(True) return if ev.data == payload: future_event.set_result(True) subscription = subscribe_func(event_name, on_next) while not future_conn.done(): yield tornado.gen.sleep(0) exposed_thing.emit_event(event_name, Faker().sentence()) exposed_thing.emit_event(event_name, payload) yield future_event assert future_event.result() subscription.dispose()
async def expose_proxy(wot, consumed_thing): """Takes a Consumed Thing and exposes an Exposed Thing that acts as a proxy.""" description = { "id": THING_ID, "name": "Thing Proxy: {}".format(consumed_thing.name) } td_dict = consumed_thing.td.to_dict() for intrct_key in ['properties', 'actions', 'events']: description.update({intrct_key: td_dict.get(intrct_key, {})}) exposed_thing = wot.produce(json.dumps(description)) for name in six.iterkeys(description.get('properties')): exposed_thing.set_property_read_handler( name, build_prop_read_proxy(consumed_thing, name)) exposed_thing.set_property_write_handler( name, build_prop_write_proxy(consumed_thing, name)) for name in six.iterkeys(description.get('actions')): exposed_thing.set_action_handler( name, build_action_invoke_proxy(consumed_thing, name)) for name in six.iterkeys(description.get('events')): subscribe_event(consumed_thing, exposed_thing, name) exposed_thing.expose() logger.info("Exposed Thing proxy TD:\n{}".format( pprint.pformat( ThingDescription.from_thing(exposed_thing.thing).to_dict()))) return exposed_thing
def client_test_invoke_action(servient, protocol_client_cls, timeout=None): """Helper function to test Action invocations on bindings clients.""" exposed_thing = next(servient.exposed_things) action_name = uuid.uuid4().hex @tornado.gen.coroutine def action_handler(parameters): input_value = parameters.get("input") yield tornado.gen.sleep(random.random() * 0.1) raise tornado.gen.Return("{:f}".format(input_value)) exposed_thing.add_action(action_name, ActionFragmentDict({ "input": {"type": "number"}, "output": {"type": "string"} }), action_handler) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() input_value = Faker().pyint() result = yield protocol_client.invoke_action(td, action_name, input_value, timeout=timeout) result_expected = yield action_handler({"input": input_value}) assert result == result_expected run_test_coroutine(test_coroutine)
def test_coroutine(): td = ThingDescription.from_thing(exposed_thing.thing) prop_name = next(six.iterkeys(td.properties)) future_conn = Future() future_change = Future() prop_value = Faker().sentence() def on_next(ev): if not future_conn.done(): future_conn.set_result(True) return if ev.data.value == prop_value: future_change.set_result(True) subscription = subscribe_func(prop_name, on_next) while not future_conn.done(): yield tornado.gen.sleep(0) yield exposed_thing.write_property(prop_name, Faker().sentence()) yield exposed_thing.write_property(prop_name, prop_value) yield future_change assert future_change.result() subscription.dispose()
def client_test_invoke_action_error(servient, protocol_client_cls): """Helper function to test Action invocations that raise errors on bindings clients.""" exposed_thing = next(servient.exposed_things) action_name = uuid.uuid4().hex err_message = Faker().sentence() # noinspection PyUnusedLocal def action_handler(parameters): raise ValueError(err_message) exposed_thing.add_action(action_name, ActionFragmentDict({ "input": {"type": "number"}, "output": {"type": "string"} }), action_handler) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() try: yield protocol_client.invoke_action(td, action_name, Faker().pyint()) raise AssertionError("Did not raise Exception") except Exception as ex: assert err_message in str(ex) run_test_coroutine(test_coroutine)
def consumed_exposed_pair(): """Returns a dict with two keys: * consumed_thing: A ConsumedThing instance. The Servient instance that contains this ConsumedThing has been patched to use the ExposedThingProxyClient Protocol Binding client. * exposed_thing: The ExposedThing behind the previous ConsumedThing (for assertion purposes).""" servient = Servient() exp_thing = ExposedThing(servient=servient, thing=Thing(id=uuid.uuid4().urn)) servient.select_client = MagicMock( return_value=ExposedThingProxyClient(exp_thing)) @tornado.gen.coroutine def lower(parameters): input_value = parameters.get("input") yield tornado.gen.sleep(0) raise tornado.gen.Return(str(input_value).lower()) exp_thing.add_property(uuid.uuid4().hex, _build_property_fragment()) exp_thing.add_action(uuid.uuid4().hex, _build_action_fragment(), lower) exp_thing.add_event(uuid.uuid4().hex, _build_event_fragment()) td = ThingDescription.from_thing(exp_thing.thing) return { "consumed_thing": ConsumedThing(servient=servient, td=td), "exposed_thing": exp_thing }
def get_property(prop_name): """Gets the given property using the WS Link contained in the thing description.""" td = ThingDescription.from_thing(exposed_thing.thing) consumed_thing = ConsumedThing(servient, td=td) value = yield consumed_thing.read_property(prop_name) raise tornado.gen.Return(value)
def _is_fragment_match(cls, item, thing_filter): """Returns True if the given item (an ExposedThing, Thing or TD) matches the fragment in the given Thing filter.""" td = None if isinstance(item, ExposedThing): td = ThingDescription.from_thing(item.thing) elif isinstance(item, Thing): td = ThingDescription.from_thing(item) elif isinstance(item, ThingDescription): td = item assert td fragment_dict = thing_filter.fragment if thing_filter.fragment else {} return all(item in six.iteritems(td.to_dict()) for item in six.iteritems(fragment_dict))
def _build_local_discover_observable(self, thing_filter): """Builds an Observable to discover Things using the local method.""" found_tds = [ ThingDescription.from_thing(exposed_thing.thing).to_str() for exposed_thing in self._servient.exposed_things if self._is_fragment_match(exposed_thing, thing_filter) ] # noinspection PyUnresolvedReferences return Observable.of(*found_tds)
def get(self, thing_url_name): exp_thing = self.servient.exposed_thing_set.find_by_thing_id( thing_url_name) td_doc = ThingDescription.from_thing(exp_thing.thing).to_dict() base_url = self.servient.get_thing_base_url(exp_thing) if base_url: td_doc.update({"base": base_url}) self.write(td_doc)
def test_all_protocols_combined(all_protocols_servient): """Protocol bindings work as expected when multiple servers are combined within the same Servient.""" exposed_thing = next(all_protocols_servient.exposed_things) td = ThingDescription.from_thing(exposed_thing.thing) clients = [WebsocketClient(), HTTPClient()] if is_coap_supported(): from wotpy.protocols.coap.client import CoAPClient clients.append(CoAPClient()) if is_mqtt_supported(): from tests.protocols.mqtt.broker import is_test_broker_online from wotpy.protocols.mqtt.client import MQTTClient if is_test_broker_online(): clients.append(MQTTClient()) prop_name = next(six.iterkeys(td.properties)) @tornado.gen.coroutine def read_property(the_client): prop_value = Faker().sentence() curr_value = yield the_client.read_property(td, prop_name) assert curr_value != prop_value yield exposed_thing.properties[prop_name].write(prop_value) curr_value = yield the_client.read_property(td, prop_name) assert curr_value == prop_value @tornado.gen.coroutine def write_property(the_client): updated_value = Faker().sentence() curr_value = yield exposed_thing.properties[prop_name].read() assert curr_value != updated_value yield the_client.write_property(td, prop_name, updated_value) curr_value = yield exposed_thing.properties[prop_name].read() assert curr_value == updated_value @tornado.gen.coroutine def test_coroutine(): for client in clients: yield read_property(client) yield write_property(client) run_test_coroutine(test_coroutine)
def test_read_property_unknown(websocket_servient): """The Websockets client raises an error when attempting to read an unknown property.""" exposed_thing = next(websocket_servient.exposed_things) td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): ws_client = WebsocketClient() with pytest.raises(ProtocolClientException): yield ws_client.read_property(td, uuid.uuid4().hex) run_test_coroutine(test_coroutine)
def client_test_on_property_change(servient, protocol_client_cls): """Helper function to test observation of Property updates on bindings clients.""" exposed_thing = next(servient.exposed_things) prop_name = uuid.uuid4().hex exposed_thing.add_property(prop_name, PropertyFragmentDict({ "type": "string", "observable": True }), value=Faker().sentence()) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() values = [Faker().sentence() for _ in range(10)] values_observed = {value: tornado.concurrent.Future() for value in values} @tornado.gen.coroutine def write_next(): try: next_value = next(val for val, fut in six.iteritems(values_observed) if not fut.done()) yield exposed_thing.properties[prop_name].write(next_value) except StopIteration: pass def on_next(ev): prop_value = ev.data.value if prop_value in values_observed and not values_observed[prop_value].done(): values_observed[prop_value].set_result(True) observable = protocol_client.on_property_change(td, prop_name) subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(on_next) periodic_emit = tornado.ioloop.PeriodicCallback(write_next, 10) periodic_emit.start() yield list(values_observed.values()) periodic_emit.stop() subscription.dispose() run_test_coroutine(test_coroutine)
def client_test_on_property_change_error(servient, protocol_client_cls): """Helper function to test propagation of errors raised during observation of Property updates on bindings clients.""" exposed_thing = next(servient.exposed_things) prop_name = uuid.uuid4().hex exposed_thing.add_property(prop_name, PropertyFragmentDict({ "type": "string", "observable": True }), value=Faker().sentence()) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() yield servient.shutdown() future_err = tornado.concurrent.Future() # noinspection PyUnusedLocal def on_next(item): future_err.set_exception(Exception("Should not have emitted any items")) def on_error(err): future_err.set_result(err) observable = protocol_client.on_property_change(td, prop_name) subscribe_kwargs = { "on_next": on_next, "on_error": on_error } subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(**subscribe_kwargs) observe_err = yield future_err assert isinstance(observe_err, Exception) subscription.dispose() run_test_coroutine(test_coroutine)
def get(self): response = {} for exp_thing in self.servient.enabled_exposed_things: thing_id = exp_thing.thing.id if self.get_argument("expanded", False): val = ThingDescription.from_thing(exp_thing.thing).to_dict() val.update( {"base": self.servient.get_thing_base_url(exp_thing)}) else: val = "/{}".format(exp_thing.thing.url_name) response[thing_id] = val self.write(response)
def test_from_thing(): """ThingDescription objects can be built from Thing objects.""" fake = Faker() thing_id = uuid.uuid4().urn action_id = uuid.uuid4().hex prop_id = uuid.uuid4().hex event_id = uuid.uuid4().hex action_form_href = fake.url() prop_form_href = fake.url() thing = Thing(id=thing_id) action = Action(thing=thing, name=action_id) action_form = Form(interaction=action, protocol=Protocols.HTTP, href=action_form_href) action.add_form(action_form) thing.add_interaction(action) prop = Property(thing=thing, name=prop_id, type="string") prop_form = Form(interaction=prop, protocol=Protocols.HTTP, href=prop_form_href) prop.add_form(prop_form) thing.add_interaction(prop) event = Event(thing=thing, name=event_id) thing.add_interaction(event) json_td = ThingDescription.from_thing(thing) td_dict = json_td.to_dict() assert td_dict["id"] == thing.id assert td_dict["title"] == thing.title assert len(td_dict["properties"]) == 1 assert len(td_dict["actions"]) == 1 assert len(td_dict["events"]) == 1 assert len(td_dict["actions"][action_id]["forms"]) == 1 assert len(td_dict["properties"][prop_id]["forms"]) == 1 assert td_dict["actions"][action_id]["forms"][0][ "href"] == action_form_href assert td_dict["properties"][prop_id]["forms"][0]["href"] == prop_form_href
def client_test_on_event(servient, protocol_client_cls): """Helper function to test observation of Events on bindings clients.""" exposed_thing = next(servient.exposed_things) event_name = uuid.uuid4().hex exposed_thing.add_event(event_name, EventFragmentDict({ "type": "number" })) servient.refresh_forms() td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): protocol_client = protocol_client_cls() payloads = [Faker().pyint() for _ in range(10)] future_payloads = {key: tornado.concurrent.Future() for key in payloads} @tornado.gen.coroutine def emit_next(): next_value = next(val for val, fut in six.iteritems(future_payloads) if not fut.done()) exposed_thing.events[event_name].emit(next_value) def on_next(ev): if ev.data in future_payloads and not future_payloads[ev.data].done(): future_payloads[ev.data].set_result(True) observable = protocol_client.on_event(td, event_name) subscription = observable.subscribe_on(IOLoopScheduler()).subscribe(on_next) periodic_emit = tornado.ioloop.PeriodicCallback(emit_next, 10) periodic_emit.start() yield list(future_payloads.values()) periodic_emit.stop() subscription.dispose() run_test_coroutine(test_coroutine)
def test_timeout_write_property(mqtt_servient): """Timeouts can be defined on Property writes.""" exposed_thing = next(mqtt_servient.exposed_things) prop_name = next(six.iterkeys(exposed_thing.properties)) td = ThingDescription.from_thing(exposed_thing.thing) mqtt_mock = _build_hbmqtt_mock(_effect_raise_timeout) timeout = random.random() @tornado.gen.coroutine def test_coroutine(): with patch('wotpy.protocols.mqtt.client.hbmqtt.client.MQTTClient', new=mqtt_mock): mqtt_client = MQTTClient() with pytest.raises(ClientRequestTimeout): yield mqtt_client.write_property(td, prop_name, Faker().pystr(), timeout=timeout) run_test_coroutine(test_coroutine)
def add_event(self, name, event_init): """Adds an event to the Thing object as defined by the event argument of type ThingEventInit and updates the Thing Description.""" if isinstance(event_init, dict): event_init = EventFragmentDict(event_init) event = Event(thing=self._thing, name=name, init_dict=event_init) self._thing.add_interaction(event) event_data = ThingDescriptionChangeEventInit( td_change_type=TDChangeType.EVENT, method=TDChangeMethod.ADD, name=name, data=event_init.to_dict(), description=ThingDescription.from_thing(self.thing).to_dict()) self._events_stream.on_next( ThingDescriptionChangeEmittedEvent(init=event_data))
def add_property(self, name, property_init, value=None): """Adds a Property defined by the argument and updates the Thing Description. Takes an instance of ThingPropertyInit as argument.""" if isinstance(property_init, dict): property_init = PropertyFragmentDict(property_init) prop = Property(thing=self._thing, name=name, init_dict=property_init) self._thing.add_interaction(prop) self._set_property_value(prop, value) event_data = ThingDescriptionChangeEventInit( td_change_type=TDChangeType.PROPERTY, method=TDChangeMethod.ADD, name=name, data=property_init.to_dict(), description=ThingDescription.from_thing(self.thing).to_dict()) self._events_stream.on_next( ThingDescriptionChangeEmittedEvent(init=event_data))
def test_stop_timeout(mqtt_servient): """Attempting to stop an unresponsive connection does not result in an indefinite wait.""" exposed_thing = next(mqtt_servient.exposed_things) prop_name = next(six.iterkeys(exposed_thing.properties)) td = ThingDescription.from_thing(exposed_thing.thing) timeout = random.random() assert (timeout * 3) < DEFAULT_TIMEOUT_SECS mqtt_mock = _build_hbmqtt_mock(_build_effect_sleep(DEFAULT_TIMEOUT_SECS * 10)) @tornado.gen.coroutine def test_coroutine(): with patch('wotpy.protocols.mqtt.client.hbmqtt.client.MQTTClient', new=mqtt_mock): mqtt_client = MQTTClient(stop_loop_timeout_secs=timeout) with pytest.raises(ClientRequestTimeout): yield mqtt_client.read_property(td, prop_name, timeout=timeout) run_test_coroutine(test_coroutine)
def add_action(self, name, action_init, action_handler=None): """Adds an Action to the Thing object as defined by the action argument of type ThingActionInit and updates th,e Thing Description.""" if isinstance(action_init, dict): action_init = ActionFragmentDict(action_init) action = Action(thing=self._thing, name=name, init_dict=action_init) self._thing.add_interaction(action) event_data = ThingDescriptionChangeEventInit( td_change_type=TDChangeType.ACTION, method=TDChangeMethod.ADD, name=name, data=action_init.to_dict(), description=ThingDescription.from_thing(self.thing).to_dict()) self._events_stream.on_next( ThingDescriptionChangeEmittedEvent(init=event_data)) if action_handler: self.set_action_handler(name, action_handler)
def test_empty_thing_valid(): """An empty Thing initialized by default has a valid JSON-LD serialization.""" thing = Thing(id=uuid.uuid4().urn) json_td = ThingDescription.from_thing(thing) ThingDescription.validate(json_td.to_dict())
def test_on_property_change(websocket_servient): """The Websockets client can observe property changes.""" exposed_thing = next(websocket_servient.exposed_things) td = ThingDescription.from_thing(exposed_thing.thing) @tornado.gen.coroutine def test_coroutine(): ws_client = WebsocketClient() prop_names = list(td.properties.keys()) prop_name_01 = prop_names[0] prop_name_02 = prop_names[1] obsv_01 = ws_client.on_property_change(td, prop_name_01) obsv_02 = ws_client.on_property_change(td, prop_name_02) prop_values_01 = [uuid.uuid4().hex for _ in range(10)] prop_values_02 = [uuid.uuid4().hex for _ in range(90)] future_values_01 = {key: Future() for key in prop_values_01} future_values_02 = {key: Future() for key in prop_values_02} future_conn_01 = Future() future_conn_02 = Future() def build_on_next(fut_conn, fut_vals): def on_next(ev): if not fut_conn.done(): fut_conn.set_result(True) if ev.data.value in fut_vals: fut_vals[ev.data.value].set_result(True) return on_next on_next_01 = build_on_next(future_conn_01, future_values_01) on_next_02 = build_on_next(future_conn_02, future_values_02) subscription_01 = obsv_01.subscribe_on( IOLoopScheduler()).subscribe(on_next_01) subscription_02 = obsv_02.subscribe_on( IOLoopScheduler()).subscribe(on_next_02) while not future_conn_01.done() or not future_conn_02.done(): yield exposed_thing.write_property(prop_name_01, uuid.uuid4().hex) yield exposed_thing.write_property(prop_name_02, uuid.uuid4().hex) yield tornado.gen.sleep(0) assert len(prop_values_01) < len(prop_values_02) for idx in range(len(prop_values_01)): yield exposed_thing.write_property(prop_name_01, prop_values_01[idx]) yield exposed_thing.write_property(prop_name_02, prop_values_02[idx]) yield list(future_values_01.values()) assert next(fut for fut in six.itervalues(future_values_02) if not fut.done()) subscription_01.dispose() for val in prop_values_02[len(prop_values_01):]: yield exposed_thing.write_property(prop_name_02, val) yield list(future_values_02.values()) subscription_02.dispose() run_test_coroutine(test_coroutine)
def test_consumed_client_protocols_preference(): """The Servient selects different protocol clients to consume Things depending on the protocol choices displayed on the Thing Description.""" servient = Servient(catalogue_port=None) @tornado.gen.coroutine def servient_start(): raise tornado.gen.Return((yield servient.start())) @tornado.gen.coroutine def servient_shutdown(): yield servient.shutdown() http_port = find_free_port() http_server = HTTPServer(port=http_port) servient.add_server(http_server) ws_port = find_free_port() ws_server = WebsocketServer(port=ws_port) servient.add_server(ws_server) client_server_map = { HTTPClient: http_server, WebsocketClient: ws_server } wot = tornado.ioloop.IOLoop.current().run_sync(servient_start) prop_name = uuid.uuid4().hex td_produce = ThingDescription({ "id": uuid.uuid4().urn, "name": uuid.uuid4().hex, "properties": { prop_name: { "observable": True, "type": "string" } } }) exposed_thing = wot.produce(td_produce.to_str()) exposed_thing.expose() td_forms_all = ThingDescription.from_thing(exposed_thing.thing) client_01 = servient.select_client(td_forms_all, prop_name) client_01_class = client_01.__class__ assert client_01_class in six.iterkeys(client_server_map) tornado.ioloop.IOLoop.current().run_sync(servient_shutdown) servient.remove_server(client_server_map[client_01_class].protocol) tornado.ioloop.IOLoop.current().run_sync(servient_start) td_forms_removed = ThingDescription.from_thing(exposed_thing.thing) client_02 = servient.select_client(td_forms_removed, prop_name) client_02_class = client_02.__class__ assert client_02_class != client_01_class assert client_02_class in six.iterkeys(client_server_map) tornado.ioloop.IOLoop.current().run_sync(servient_shutdown)