def test_setmember_nested(): dc_parent = dc() dc_parent.a = dc() assert utils.getmember(utils.getmember(dc_parent, 'a'), 'a') == 2 utils.setmember(utils.getmember(dc_parent, 'a'), 'a', 3) assert dc_parent.a.a == 3
def test_increment_modification_number(): now = datetime.now(timezone.utc) event = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', priority=1, market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now - timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) utils.increment_event_modification_number(event) assert utils.getmember(event, 'event_descriptor.modification_number') == 1 utils.increment_event_modification_number(event) assert utils.getmember(event, 'event_descriptor.modification_number') == 2
def add_raw_event(self, ven_id, event, callback=None): """ Add a new event to the queue for a specific VEN. :param str ven_id: The ven_id to which this event should be distributed. :param dict event: The event (as a dict or as a objects.Event instance) that contains the event details. :param callable callback: A callback that will receive the opt status for this event. This callback receives ven_id, event_id, opt_type as its arguments. """ if utils.getmember(event, 'response_required') == 'always': if callback is None: logger.warning("You did not provide a 'callback', which means you won't know if the " "VEN will opt in or opt out of your event. You should consider adding " "a callback for this.") elif not asyncio.isfuture(callback): args = inspect.signature(callback).parameters if not all(['ven_id' in args, 'event_id' in args, 'opt_type' in args]): raise ValueError("The 'callback' must have at least the following parameters: " "'ven_id' (str), 'event_id' (str), 'opt_type' (str). Please fix " "your 'callback' handler.") event_id = utils.getmember(event, 'event_descriptor.event_id') # Create the event queue if it does not exist yet if ven_id not in self.events: self.events[ven_id] = [] # Add event to the queue self.events[ven_id].append(event) self.events_updated[ven_id] = True # Add the callback for the response to this event if callback is not None: self.event_callbacks[event_id] = (event, callback) return event_id
def test_setmember(): obj = {'a': 1} utils.setmember(obj, 'a', 10) assert utils.getmember(obj, 'a') == 10 obj = dc() utils.setmember(obj, 'a', 10) assert utils.getmember(obj, 'a') == 10
def test_getmember(): obj = {'a': 1} assert utils.getmember(obj, 'a') == 1 obj = dc() assert utils.getmember(obj, 'a') == 2 obj = {'a': {'b': 1}} assert utils.getmember(obj, 'a.b') == 1
async def request_event(self, payload): """ The VEN requests us to send any events we have. """ ven_id = payload['ven_id'] if self.polling_method == 'internal': if ven_id in self.events and self.events[ven_id]: events = utils.order_events(self.events[ven_id]) for event in events: event_status = utils.getmember( event, 'event_descriptor.event_status') # Pop the event from the events so that this is the last time it is communicated if event_status == enums.EVENT_STATUS.COMPLETED: self.events[ven_id].pop( self.events[ven_id].index(event)) else: events = None else: result = self.on_request_event(ven_id=payload['ven_id']) if asyncio.iscoroutine(result): result = await result if result is None: events = None else: events = utils.order_events(result) if events is None: return 'oadrResponse', {} else: return 'oadrDistributeEvent', {'events': events} return 'oadrResponse', result
async def register_reports(self, reports): """ Tell the VTN about our reports. The VTN miht respond with an oadrCreateReport message that tells us which reports are to be sent. """ request_id = utils.generate_id() payload = {'request_id': request_id, 'ven_id': self.ven_id, 'reports': reports, 'report_request_id': 0} for report in payload['reports']: utils.setmember(report, 'report_request_id', 0) service = 'EiReport' message = self._create_message('oadrRegisterReport', **payload) response_type, response_payload = await self._perform_request(service, message) # Handle the subscriptions that the VTN is interested in. if 'report_requests' in response_payload: for report_request in response_payload['report_requests']: result = await self.create_report(report_request) # Send the oadrCreatedReport message message_type = 'oadrCreatedReport' message_payload = {'pending_reports': [{'report_request_id': utils.getmember(report, 'report_request_id')} for report in self.report_requests]} message = self._create_message('oadrCreatedReport', response={'response_code': 200, 'response_description': 'OK'}, ven_id=self.ven_id, **message_payload) response_type, response_payload = await self._perform_request(service, message)
async def test_cancel_event(): async def opt_in_to_event(event, future=None): if future: future.set_result(True) return 'optIn' async def on_update_event(event, future=None): if future: future.set_result(event) return 'optIn' now = datetime.datetime.now(datetime.timezone.utc) server = OpenADRServer(vtn_id='myvtn', requested_poll_freq=datetime.timedelta(seconds=1)) server.add_handler('on_create_party_registration', on_create_party_registration) loop = asyncio.get_event_loop() event_1_callback_future = loop.create_future() event_id = server.add_event( ven_id='ven123', signal_name='simple', signal_type='level', intervals=[ objects.Interval(dtstart=now, duration=datetime.timedelta(seconds=60), signal_payload=1) ], callback=event_1_callback_future, response_required='always') await server.run() client = OpenADRClient( ven_name='ven123', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') client.add_handler('on_event', opt_in_to_event) cancel_future = loop.create_future() client.add_handler('on_update_event', partial(on_update_event, future=cancel_future)) await client.run() await event_1_callback_future server.cancel_event('ven123', event_id) result = await cancel_future assert utils.getmember(result, 'event_descriptor.event_status') == 'cancelled' response_type, response_payload = await client.request_event() assert response_type == 'oadrResponse' await client._event_cleanup() assert len(client.responded_events) == 0 assert len(client.received_events) == 0 await server.stop() await client.stop()
async def created_event(self, payload): """ The VEN informs us that they created an EiEvent. """ ven_id = payload['ven_id'] if self.polling_method == 'internal': for event_response in payload['event_responses']: event_id = event_response['event_id'] modification_number = event_response['modification_number'] opt_type = event_response['opt_type'] event = utils.find_by(self.events[ven_id], 'event_descriptor.event_id', event_id, 'event_descriptor.modification_number', modification_number) if not event: if event_id not in self.completed_event_ids.get( ven_id, []): logger.warning( f"""Got an oadrCreatedEvent message from ven '{ven_id}' """ f"""for event '{event_id}' with modification number """ f"""{modification_number} that does not exist.""") raise errors.InvalidIdError # Remove the event from the events list if the cancellation is confirmed. if utils.getmember(event, 'event_descriptor.event_status' ) == enums.EVENT_STATUS.CANCELLED: utils.pop_by(self.events[ven_id], 'event_descriptor.event_id', event_id) if event_response['event_id'] in self.event_callbacks: event, callback = self.event_callbacks.pop(event_id) if isinstance(callback, asyncio.Future): if callback.done(): logger.warning( f"Got a second response '{opt_type}' from ven '{ven_id}' " f"to event '{event_id}', which we cannot use because the " "callback future you provided was already completed during " "the first response.") else: callback.set_result(opt_type) else: result = callback(ven_id=ven_id, event_id=event_id, opt_type=opt_type) if asyncio.iscoroutine(result): result = await result else: for event_response in payload['event_responses']: event_id = event_response['event_id'] opt_type = event_response['opt_type'] result = await utils.await_if_required( self.on_created_event(ven_id=ven_id, event_id=event_id, opt_type=opt_type)) return 'oadrResponse', {}
def add_event(self, ven_id, signal_name, signal_type, intervals, callback=None, event_id=None, targets=None, targets_by_type=None, target=None, response_required='always', market_context="oadr://unknown.context", notification_period=None, ramp_up_period=None, recovery_period=None, signal_target_mrid=None): """ Convenience method to add an event with a single signal. :param str ven_id: The ven_id to whom this event must be delivered. :param str signal_name: The OpenADR name of the signal; one of openleadr.objects.SIGNAL_NAME :param str signal_type: The OpenADR type of the signal; one of openleadr.objects.SIGNAL_TYPE :param str intervals: A list of intervals with a dtstart, duration and payload member. :param str callback: A callback function for when your event has been accepted (optIn) or refused (optOut). :param list targets: A list of Targets that this Event applies to. :param target: A single target for this event. :param dict targets_by_type: A dict of targets, grouped by type. :param str market_context: A URI for the DR program that this event belongs to. :param timedelta notification_period: The Notification period for the Event's Active Period. :param timedelta ramp_up_period: The Ramp Up period for the Event's Active Period. :param timedelta recovery_period: The Recovery period for the Event's Active Period. If you don't provide a target using any of the three arguments, the target will be set to the given ven_id. """ if self.services['event_service'].polling_method == 'external': logger.error("You cannot use the add_event method after you assign your own on_poll " "handler. If you use your own on_poll handler, you are responsible for " "delivering events from that handler. If you want to use OpenLEADRs " "message queuing system, you should not assign an on_poll handler. " "Your Event will NOT be added.") return if not re.match(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", market_context): raise ValueError("The Market Context must be a valid URI.") event_id = event_id or utils.generate_id() if response_required not in ('always', 'never'): raise ValueError("'response_required' should be either 'always' or 'never'; " f"you provided '{response_required}'.") # Figure out the target for this Event if target is None and targets is None and targets_by_type is None: targets = [{'ven_id': ven_id}] elif target is not None: targets = [target] elif targets_by_type is not None: targets = utils.ungroup_targets_by_type(targets_by_type) if not isinstance(targets, list): targets = [targets] if signal_type not in enums.SIGNAL_TYPE.values: raise ValueError(f"""The signal_type must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """ f"""you specified: '{signal_type}'.""") if signal_name not in enums.SIGNAL_NAME.values and not signal_name.startswith('x-'): raise ValueError(f"""The signal_name must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """ f"""or it must begin with 'x-'. You specified: '{signal_name}'""") if not intervals or not isinstance(intervals, (list, tuple)) or len(intervals) == 0: raise ValueError(f"The intervals must be a list of intervals, you specified: {intervals}") event_descriptor = objects.EventDescriptor(event_id=event_id, modification_number=0, market_context=market_context, event_status="far", created_date_time=datetime.now(timezone.utc)) event_signal = objects.EventSignal(intervals=intervals, signal_name=signal_name, signal_type=signal_type, signal_id=utils.generate_id()) # Make sure the intervals carry timezone-aware timestamps for interval in intervals: if utils.getmember(interval, 'dtstart').tzinfo is None: utils.setmember(interval, 'dtstart', utils.getmember(interval, 'dtstart').astimezone(timezone.utc)) logger.warning("You supplied a naive datetime object to your interval's dtstart. " "This will be interpreted as a timestamp in your local timezone " "and then converted to UTC before sending. Please supply timezone-" "aware timestamps like datetime.datetime.new(timezone.utc) or " "datetime.datetime(..., tzinfo=datetime.timezone.utc)") active_period = utils.get_active_period_from_intervals(intervals, False) active_period.ramp_up_period = ramp_up_period active_period.notification_period = notification_period active_period.recovery_period = recovery_period event = objects.Event(active_period=active_period, event_descriptor=event_descriptor, event_signals=[event_signal], targets=targets, response_required=response_required) self.add_raw_event(ven_id=ven_id, event=event, callback=callback) return event_id