def test_validate_err(): """An erroneous Thing Description raises error on validation.""" update_funcs = [ lambda x: x.update({"properties": [1, 2, 3]}) or x, lambda x: x.update({"actions": "hello-interactions"}) or x, lambda x: x.update({"events": { "overheating": { "forms": 0.5 } }}) or x, lambda x: x.update({"events": { "Invalid Name": {} }}) or x, lambda x: x.update({"events": { 100: { "label": "Invalid Name" } }}) or x ] for update_func in update_funcs: td_err = update_func(copy.deepcopy(TD_EXAMPLE)) with pytest.raises(InvalidDescription): ThingDescription.validate(doc=td_err)
def all_protocols_servient(): """Returns a Servient configured to use all available protocol bindings.""" servient = Servient(catalogue_port=None) 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) if is_coap_supported(): from wotpy.protocols.coap.server import CoAPServer coap_port = find_free_port() coap_server = CoAPServer(port=coap_port) servient.add_server(coap_server) if is_mqtt_supported(): from wotpy.protocols.mqtt.server import MQTTServer from tests.protocols.mqtt.broker import get_test_broker_url, is_test_broker_online if is_test_broker_online(): mqtt_server = MQTTServer(broker_url=get_test_broker_url()) servient.add_server(mqtt_server) @tornado.gen.coroutine def start(): raise tornado.gen.Return((yield servient.start())) wot = tornado.ioloop.IOLoop.current().run_sync(start) td_dict = { "id": uuid.uuid4().urn, "title": uuid.uuid4().hex, "properties": { uuid.uuid4().hex: { "observable": True, "type": "string" } } } td = ThingDescription(td_dict) exposed_thing = wot.produce(td.to_str()) exposed_thing.expose() yield servient @tornado.gen.coroutine def shutdown(): yield servient.shutdown() tornado.ioloop.IOLoop.current().run_sync(shutdown)
def fetch(cls, url, timeout_secs=None): """Accepts an url argument and returns a Future that resolves with a Thing Description string.""" timeout_secs = timeout_secs or DEFAULT_FETCH_TIMEOUT_SECS http_client = AsyncHTTPClient() http_request = HTTPRequest(url, request_timeout=timeout_secs) http_response = yield http_client.fetch(http_request) td_doc = json.loads(http_response.body) td = ThingDescription(td_doc) raise tornado.gen.Return(td.to_str())
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 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 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 consume(self, td_str): """Accepts a thing description string argument and returns a ConsumedThing object instantiated based on that description.""" td = ThingDescription(td_str) return ConsumedThing(servient=self._servient, td=td)
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 test_build_thing(): """Thing objects can be built from ThingDescription objects.""" json_td = ThingDescription(TD_EXAMPLE) thing = json_td.build_thing() td_dict = json_td.to_dict() def assert_same_keys(dict_a, dict_b): assert sorted(list(dict_a.keys())) == sorted(list(dict_b.keys())) assert thing.id == td_dict.get("id") assert thing.title == td_dict.get("title") assert thing.description == td_dict.get("description") assert_same_keys(thing.properties, td_dict.get("properties", {})) assert_same_keys(thing.actions, td_dict.get("actions", {})) assert_same_keys(thing.events, td_dict.get("events", {}))
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 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 test_coroutine(): wot_01 = yield servient_01.start() wot_02 = yield servient_02.start() wot_01.produce(ThingFragment(TD_DICT_01)).expose() wot_01.produce(ThingFragment(TD_DICT_02)).expose() thing_filter = ThingFilterDict(method=DiscoveryMethod.MULTICAST) observable = wot_02.discover(thing_filter, dnssd_find_kwargs={ "min_results": 1, "timeout": 5 }) subscription = observable.subscribe( on_next=lambda td_str: found.append(ThingDescription(td_str) ) or resolve()) yield future_done assert_equal_td_sequences(found, [TD_DICT_01, TD_DICT_02]) subscription.dispose() yield servient_01.shutdown() yield servient_02.shutdown()
def test_from_dict(): """ThingDescription objects can be built from TD documents in dict format.""" td = ThingDescription(TD_EXAMPLE) assert td.id == TD_EXAMPLE.get("id") assert td.title == TD_EXAMPLE.get("title") assert td.description == TD_EXAMPLE.get("description")
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 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 _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 test_clients_subset(): """Although all clients are enabled by default, the user may only enable a subset.""" ws_client = WebsocketClient() servient_01 = Servient() servient_02 = Servient(clients=[ws_client]) td = ThingDescription(TD_DICT_01) prop_name = next(six.iterkeys(TD_DICT_01["properties"])) assert servient_01.select_client(td, prop_name) is not ws_client assert servient_02.select_client(td, prop_name) is ws_client
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_coroutine(): thing_filter = ThingFilterDict(method=DiscoveryMethod.LOCAL) observable = wot.discover(thing_filter) subscription = observable.subscribe( on_next=lambda td_str: found.append(ThingDescription(td_str) ) or resolve()) yield future_done assert_equal_td_sequences(found, [TD_DICT_01, TD_DICT_02]) subscription.dispose()
def assert_equal_tds(one, other): """Asserts that both TDs are equal.""" one = ThingDescription(one) if not isinstance(one, ThingDescription) else one other = ThingDescription(other) if not isinstance( other, ThingDescription) else other assert one.to_dict() == other.to_dict()
def callback(): address_port_pairs = yield self._servient.dnssd.find( **dnssd_find_kwargs) def build_pair_url(idx, path=None): addr, port = address_port_pairs[idx] base = "http://{}:{}".format(addr, port) path = path if path else '' return "{}/{}".format(base, path.strip("/")) http_client = AsyncHTTPClient() catalogue_resps = [ http_client.fetch(build_pair_url(idx)) for idx in range(len(address_port_pairs)) ] wait_iter = tornado.gen.WaitIterator(*catalogue_resps) while not wait_iter.done() and not state["stop"]: try: catalogue_resp = yield wait_iter.next() except Exception as ex: self._logr.warning( "Exception on HTTP request to TD catalogue: {}". format(ex)) else: catalogue = json.loads(catalogue_resp.body) if state["stop"]: return td_resps = yield [ http_client.fetch( build_pair_url(wait_iter.current_index, path=path)) for thing_id, path in six.iteritems(catalogue) ] tds = [ ThingDescription(td_resp.body) for td_resp in td_resps ] tds_filtered = [ td for td in tds if self._is_fragment_match(td, thing_filter) ] [observer.on_next(td.to_str()) for td in tds_filtered]
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 discover_first(): observable = wot.discover(thing_filter) subscription = observable.subscribe( on_next=lambda td_str: found.append(ThingDescription(td_str) ) or resolve()) yield future_done subscription.dispose() assert len(found) raise tornado.gen.Return(found[0])
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 thing_from_model(cls, model): """Takes a ThingModel and builds a Thing. Raises if the model has an unexpected type.""" expected_types = (six.string_types, ThingFragment, ConsumedThing) if not isinstance(model, expected_types): raise ValueError("Expected one of: {}".format(expected_types)) if isinstance(model, six.string_types): thing = ThingDescription(doc=model).build_thing() elif isinstance(model, ThingFragment): thing = Thing(thing_fragment=model) else: thing = model.td.build_thing() return thing
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