示例#1
0
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
示例#2
0
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
示例#3
0
    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
示例#4
0
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
示例#5
0
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
示例#6
0
    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
示例#7
0
    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)
示例#8
0
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()
示例#9
0
 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', {}
示例#10
0
    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