cdp-client 2.2.2__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {cdp_client-2.2.2 → cdp_client-2.3.0}/PKG-INFO +81 -7
  2. cdp_client-2.2.2/cdp_client.egg-info/PKG-INFO → cdp_client-2.3.0/README.rst +65 -30
  3. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/cdp.py +70 -6
  4. cdp_client-2.3.0/cdp_client/cdp_pb2.py +85 -0
  5. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/fake_data.py +44 -0
  6. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_connection.py +28 -0
  7. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_node.py +128 -0
  8. cdp_client-2.2.2/README.rst → cdp_client-2.3.0/cdp_client.egg-info/PKG-INFO +104 -3
  9. cdp_client-2.3.0/cdp_client.egg-info/requires.txt +4 -0
  10. {cdp_client-2.2.2 → cdp_client-2.3.0}/setup.py +5 -6
  11. cdp_client-2.2.2/cdp_client/cdp_pb2.py +0 -1419
  12. cdp_client-2.2.2/cdp_client.egg-info/requires.txt +0 -4
  13. {cdp_client-2.2.2 → cdp_client-2.3.0}/LICENSE.txt +0 -0
  14. {cdp_client-2.2.2 → cdp_client-2.3.0}/MANIFEST.in +0 -0
  15. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/__init__.py +0 -0
  16. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/__init__.py +0 -0
  17. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_client.py +0 -0
  18. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_main.py +0 -0
  19. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_nodetree.py +0 -0
  20. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client/tests/test_requests.py +0 -0
  21. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client.egg-info/SOURCES.txt +0 -0
  22. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client.egg-info/dependency_links.txt +0 -0
  23. {cdp_client-2.2.2 → cdp_client-2.3.0}/cdp_client.egg-info/top_level.txt +0 -0
  24. {cdp_client-2.2.2 → cdp_client-2.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cdp-client
3
- Version: 2.2.2
3
+ Version: 2.3.0
4
4
  Summary: Provides an API that allows to interact with CDP applications
5
5
  Home-page: https://github.com/CDPTechnologies/PythonCDPClient
6
6
  Author: CDP Technologies AS
@@ -18,19 +18,31 @@ Classifier: Programming Language :: Python :: 3.8
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
21
22
  Requires-Python: >=3.8
22
23
  License-File: LICENSE.txt
23
24
  Requires-Dist: promise
24
- Requires-Dist: websocket-client<=0.56.0
25
- Requires-Dist: protobuf<=3.20.0
25
+ Requires-Dist: websocket-client
26
+ Requires-Dist: protobuf
26
27
  Requires-Dist: mock
28
+ Dynamic: author
29
+ Dynamic: author-email
30
+ Dynamic: classifier
31
+ Dynamic: description
32
+ Dynamic: home-page
33
+ Dynamic: keywords
34
+ Dynamic: license
35
+ Dynamic: license-file
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
27
39
 
28
40
  CDP-Client
29
41
  ==========
30
42
 
31
43
  A simple python interface for the CDP Studio development platform that allows Python scripts to interact with CDP Applications - retrieve CDP Application structures and read-write object values. For more information about CDP Studio see https://cdpstudio.com/
32
44
 
33
- The API makes heavy use of promise library for asynchronous operations. For more information see https://pypi.python.org/pypi/promise
45
+ The API makes heavy use of promise library for asynchronous operations. For more information see https://pypi.org/project/promise/
34
46
 
35
47
  Installation
36
48
  ------------
@@ -58,6 +70,26 @@ The example below shows how you subscribe to CDP signal value changes.
58
70
  client.find_node('AppName.ComponentName.SignalName').then(subscribe_to_value_changes)
59
71
  client.run_event_loop()
60
72
 
73
+ The example below shows how you subscribe to CDP events.
74
+
75
+ .. code:: python
76
+
77
+ from cdp_client import cdp
78
+
79
+ def on_event_received(event_info):
80
+ print(f"Event {event_info.id} from {event_info.sender}")
81
+ print(f"Code: {event_info.code}, Timestamp: {event_info.timestamp}")
82
+ if event_info.data:
83
+ for data_item in event_info.data:
84
+ print(f" {data_item.name}: {data_item.value}")
85
+
86
+ def subscribe_to_events(node):
87
+ node.subscribe_to_events(on_event_received)
88
+
89
+ client = cdp.Client(host='127.0.0.1')
90
+ client.root_node().then(subscribe_to_events)
91
+ client.run_event_loop()
92
+
61
93
  API
62
94
  ---
63
95
 
@@ -139,7 +171,7 @@ Client(host, port, auto_reconnect, notification_listener, encryption_parameters)
139
171
  encryption_parameters={'use_encryption': True,
140
172
  'cert_reqs': ssl.CERT_REQUIRED,
141
173
  'ca_certs': 'StudioAPI.crt',
142
- 'check_hostname': False},
174
+ 'check_hostname': False})
143
175
 
144
176
  Instance Methods / Client
145
177
  ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -387,6 +419,48 @@ Stops listening previously subscribed value changes
387
419
 
388
420
  node.unsubscribe_from_value_changes(on_change)
389
421
 
422
+ node.subscribe_to_events(callback, starting_from=None)
423
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
424
+
425
+ Starts listening to events from this node and its children, passing event information to the provided callback function
426
+
427
+ - Arguments
428
+
429
+ callback - Function(event_info) where event_info contains id, sender, code, timestamp, and optional data fields
430
+
431
+ starting_from - Optional timestamp to start receiving events from (in nanoseconds since Epoch). Defaults to None for current events.
432
+
433
+ - Usage
434
+
435
+ .. code:: python
436
+
437
+ def on_event(event_info):
438
+ print(f"Event {event_info.id} from {event_info.sender}")
439
+ print(f"Code: {event_info.code}, Timestamp: {event_info.timestamp}")
440
+ if event_info.data:
441
+ for data_item in event_info.data:
442
+ print(f" {data_item.name}: {data_item.value}")
443
+
444
+ node.subscribe_to_events(on_event)
445
+
446
+ node.unsubscribe_from_events(callback)
447
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
448
+
449
+ Stops listening to previously subscribed events
450
+
451
+ - Arguments
452
+
453
+ callback - Function(event_info) that was previously subscribed
454
+
455
+ - Usage
456
+
457
+ .. code:: python
458
+
459
+ def on_event(event_info):
460
+ print(f"Event received: {event_info.id}")
461
+
462
+ node.unsubscribe_from_events(on_event)
463
+
390
464
  Notification Listener
391
465
  ~~~~~~~~~~~~~~~~~~~~~
392
466
 
@@ -459,7 +533,7 @@ To run the test suite execute the following command in package root folder:
459
533
 
460
534
  .. code:: sh
461
535
 
462
- $ python setup.py test
536
+ $ python -m unittest discover cdp_client.tests -v
463
537
 
464
538
  License
465
539
  -------
@@ -1,36 +1,9 @@
1
- Metadata-Version: 2.1
2
- Name: cdp-client
3
- Version: 2.2.2
4
- Summary: Provides an API that allows to interact with CDP applications
5
- Home-page: https://github.com/CDPTechnologies/PythonCDPClient
6
- Author: CDP Technologies AS
7
- Author-email: info@cdptech.com
8
- License: MIT
9
- Keywords: cdp cdpstudio studio client cdp-client cdp_client
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: Environment :: Console
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Operating System :: OS Independent
15
- Classifier: Topic :: Software Development
16
- Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
- Classifier: Programming Language :: Python :: 3.10
20
- Classifier: Programming Language :: Python :: 3.11
21
- Requires-Python: >=3.8
22
- License-File: LICENSE.txt
23
- Requires-Dist: promise
24
- Requires-Dist: websocket-client<=0.56.0
25
- Requires-Dist: protobuf<=3.20.0
26
- Requires-Dist: mock
27
-
28
1
  CDP-Client
29
2
  ==========
30
3
 
31
4
  A simple python interface for the CDP Studio development platform that allows Python scripts to interact with CDP Applications - retrieve CDP Application structures and read-write object values. For more information about CDP Studio see https://cdpstudio.com/
32
5
 
33
- The API makes heavy use of promise library for asynchronous operations. For more information see https://pypi.python.org/pypi/promise
6
+ The API makes heavy use of promise library for asynchronous operations. For more information see https://pypi.org/project/promise/
34
7
 
35
8
  Installation
36
9
  ------------
@@ -58,6 +31,26 @@ The example below shows how you subscribe to CDP signal value changes.
58
31
  client.find_node('AppName.ComponentName.SignalName').then(subscribe_to_value_changes)
59
32
  client.run_event_loop()
60
33
 
34
+ The example below shows how you subscribe to CDP events.
35
+
36
+ .. code:: python
37
+
38
+ from cdp_client import cdp
39
+
40
+ def on_event_received(event_info):
41
+ print(f"Event {event_info.id} from {event_info.sender}")
42
+ print(f"Code: {event_info.code}, Timestamp: {event_info.timestamp}")
43
+ if event_info.data:
44
+ for data_item in event_info.data:
45
+ print(f" {data_item.name}: {data_item.value}")
46
+
47
+ def subscribe_to_events(node):
48
+ node.subscribe_to_events(on_event_received)
49
+
50
+ client = cdp.Client(host='127.0.0.1')
51
+ client.root_node().then(subscribe_to_events)
52
+ client.run_event_loop()
53
+
61
54
  API
62
55
  ---
63
56
 
@@ -139,7 +132,7 @@ Client(host, port, auto_reconnect, notification_listener, encryption_parameters)
139
132
  encryption_parameters={'use_encryption': True,
140
133
  'cert_reqs': ssl.CERT_REQUIRED,
141
134
  'ca_certs': 'StudioAPI.crt',
142
- 'check_hostname': False},
135
+ 'check_hostname': False})
143
136
 
144
137
  Instance Methods / Client
145
138
  ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -387,6 +380,48 @@ Stops listening previously subscribed value changes
387
380
 
388
381
  node.unsubscribe_from_value_changes(on_change)
389
382
 
383
+ node.subscribe_to_events(callback, starting_from=None)
384
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
385
+
386
+ Starts listening to events from this node and its children, passing event information to the provided callback function
387
+
388
+ - Arguments
389
+
390
+ callback - Function(event_info) where event_info contains id, sender, code, timestamp, and optional data fields
391
+
392
+ starting_from - Optional timestamp to start receiving events from (in nanoseconds since Epoch). Defaults to None for current events.
393
+
394
+ - Usage
395
+
396
+ .. code:: python
397
+
398
+ def on_event(event_info):
399
+ print(f"Event {event_info.id} from {event_info.sender}")
400
+ print(f"Code: {event_info.code}, Timestamp: {event_info.timestamp}")
401
+ if event_info.data:
402
+ for data_item in event_info.data:
403
+ print(f" {data_item.name}: {data_item.value}")
404
+
405
+ node.subscribe_to_events(on_event)
406
+
407
+ node.unsubscribe_from_events(callback)
408
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
409
+
410
+ Stops listening to previously subscribed events
411
+
412
+ - Arguments
413
+
414
+ callback - Function(event_info) that was previously subscribed
415
+
416
+ - Usage
417
+
418
+ .. code:: python
419
+
420
+ def on_event(event_info):
421
+ print(f"Event received: {event_info.id}")
422
+
423
+ node.unsubscribe_from_events(on_event)
424
+
390
425
  Notification Listener
391
426
  ~~~~~~~~~~~~~~~~~~~~~
392
427
 
@@ -459,7 +494,7 @@ To run the test suite execute the following command in package root folder:
459
494
 
460
495
  .. code:: sh
461
496
 
462
- $ python setup.py test
497
+ $ python -m unittest discover cdp_client.tests -v
463
498
 
464
499
  License
465
500
  -------
@@ -161,6 +161,7 @@ class Node:
161
161
  self._children = []
162
162
  self._structure_subscriptions = []
163
163
  self._value_subscriptions = []
164
+ self._event_subscriptions = []
164
165
  self._value = proto.VariantValue()
165
166
  self._parent = parent
166
167
  for child in self._structure.node:
@@ -235,6 +236,27 @@ class Node:
235
236
  else:
236
237
  self._connection.send_value_unrequest(self._id())
237
238
 
239
+ def subscribe_to_events(self, callback, starting_from=None):
240
+ """Starts listening to events from this node and its children.
241
+
242
+ Args:
243
+ callback: Function(event_info) to call when events are received
244
+ starting_from: Optional timestamp to start receiving events from (nanoseconds since Epoch)
245
+ """
246
+ self._event_subscriptions.append(callback)
247
+ self._connection.send_event_request(self._id(), starting_from)
248
+
249
+ def unsubscribe_from_events(self, callback):
250
+ """Stops listening to previously subscribed events.
251
+
252
+ Args:
253
+ callback: Function(event_info) that was previously subscribed
254
+ """
255
+ if callback in self._event_subscriptions:
256
+ self._event_subscriptions.remove(callback)
257
+ if not self._event_subscriptions:
258
+ self._connection.send_event_unrequest(self._id())
259
+
238
260
  def _id(self):
239
261
  return self._structure.info.node_id
240
262
 
@@ -308,6 +330,10 @@ class Node:
308
330
  for callback, fs, sample_rate in self._value_subscriptions:
309
331
  callback(self._value, variant.timestamp + self._connection.server_time_difference() * nanoseconds_in_second)
310
332
 
333
+ def _update_event(self, event_info):
334
+ for callback in self._event_subscriptions:
335
+ callback(event_info)
336
+
311
337
  @staticmethod
312
338
  def _translate_type(node_type):
313
339
  if node_type == proto.CDP_SYSTEM:
@@ -446,6 +472,14 @@ class Connection:
446
472
  self._update_time_difference()
447
473
  self._compose_and_send_value(variant)
448
474
 
475
+ def send_event_request(self, node_id, starting_from=None):
476
+ self._update_time_difference()
477
+ self._compose_and_send_event_request(node_id, starting_from)
478
+
479
+ def send_event_unrequest(self, node_id):
480
+ self._update_time_difference()
481
+ self._compose_and_send_event_request(node_id, None, True)
482
+
449
483
  def run_event_loop(self):
450
484
  self._ws.run_forever(sslopt=self._encryption_parameters)
451
485
  while self._auto_reconnect:
@@ -468,16 +502,16 @@ class Connection:
468
502
  on_close=self._on_close,
469
503
  on_open=self._on_open)
470
504
 
471
- def _on_error(self, error):
505
+ def _on_error(self, ws, error):
472
506
  if not self._auto_reconnect:
473
507
  self._cleanup_queued_requests(ConnectionError(error))
474
508
 
475
- def _on_close(self):
509
+ def _on_close(self, ws, close_status_code=None, close_msg=None):
476
510
  self._is_connected = False
477
511
  if not self._auto_reconnect:
478
512
  self._cleanup_queued_requests(ConnectionError("Connection was closed"))
479
513
 
480
- def _on_open(self):
514
+ def _on_open(self, ws):
481
515
  pass
482
516
 
483
517
  def _fetch_time_difference(self):
@@ -545,7 +579,10 @@ class Connection:
545
579
  self._ws.on_message = self._handle_container_message
546
580
  self._update_time_difference().then(self._node_tree.update()).then(self._send_queued_requests())
547
581
 
548
- def _handle_auth_response(self, message):
582
+ def _handle_auth_response(self, ws=None, message=None):
583
+ if message is None:
584
+ message = ws
585
+ ws = None
549
586
  data = proto.AuthResponse()
550
587
  data.ParseFromString(message)
551
588
  if data.result_code in (data.eGranted, data.eGrantedPasswordWillExpireSoon):
@@ -569,7 +606,10 @@ class Connection:
569
606
  self._credentials = credentials
570
607
  self._compose_and_send_re_auth_request()
571
608
 
572
- def _handle_hello_message(self, message):
609
+ def _handle_hello_message(self, ws=None, message=None):
610
+ if message is None:
611
+ message = ws
612
+ ws = None
573
613
  if self._parse_hello_message(message):
574
614
  request = AuthRequest(host=self._host, port=self._port, system_name=self._system_name,
575
615
  application_name=self._application_name, cdp_version=self._cdp_version,
@@ -600,7 +640,10 @@ class Connection:
600
640
  self._re_auth_request.then(self._re_authenticate)
601
641
  self._notification_listener.credentials_requested(self._re_auth_request)
602
642
 
603
- def _handle_container_message(self, message):
643
+ def _handle_container_message(self, ws=None, message=None):
644
+ if message is None:
645
+ message = ws
646
+ ws = None
604
647
  data = proto.Container()
605
648
  data.ParseFromString(message)
606
649
  if data.message_type == proto.Container.eStructureResponse:
@@ -613,6 +656,8 @@ class Connection:
613
656
  self._parse_current_time_response(data.current_time_response)
614
657
  elif data.message_type == proto.Container.eReauthResponse:
615
658
  self._parse_re_auth_response(data.re_auth_response)
659
+ elif data.message_type == proto.Container.eEventResponse:
660
+ self._parse_event_response(data.event_response)
616
661
  elif data.message_type == proto.Container.eRemoteError:
617
662
  self._parse_error_response(data.error)
618
663
  else:
@@ -632,6 +677,13 @@ class Connection:
632
677
  def _parse_current_time_response(self, response):
633
678
  self._time_request.do_resolve(response)
634
679
 
680
+ def _parse_event_response(self, response):
681
+ for event_info in response:
682
+ for node_id in event_info.node_id:
683
+ node = self._node_tree.find_by_id(node_id)
684
+ if node is not None:
685
+ node._update_event(event_info)
686
+
635
687
  def _parse_re_auth_response(self, response):
636
688
  if response.result_code not in (response.eGranted, response.eGrantedPasswordWillExpireSoon):
637
689
  self._re_auth_request = AuthRequest(host=self._host, port=self._port, system_name=self._system_name,
@@ -719,6 +771,18 @@ class Connection:
719
771
  data.message_type = proto.Container.eCurrentTimeRequest
720
772
  self._ws.send(data.SerializeToString())
721
773
 
774
+ def _compose_and_send_event_request(self, node_id, starting_from=None, stop=False):
775
+ data = proto.Container()
776
+ data.message_type = proto.Container.eEventRequest
777
+ event_request = proto.EventRequest()
778
+ event_request.node_id = node_id
779
+ if starting_from is not None:
780
+ event_request.starting_from = starting_from
781
+ if stop:
782
+ event_request.stop = stop
783
+ data.event_request.extend([event_request])
784
+ self._ws.send(data.SerializeToString())
785
+
722
786
  def _compose_auth_request(self, request):
723
787
  if not 'Username' in self._credentials:
724
788
  UnknownError("NotificationListener.credentials_requested() didn't return 'Username' entry")
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: cdp.proto
5
+ # Protobuf Python Version: 6.30.2
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 30,
16
+ 2,
17
+ '',
18
+ 'cdp.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tcdp.proto\x12\x0fStudioAPI.Proto\"\xaf\x03\n\x05Hello\x12\x13\n\x0bsystem_name\x18\x01 \x02(\t\x12\x19\n\x0e\x63ompat_version\x18\x02 \x02(\r:\x01\x32\x12\x1e\n\x13incremental_version\x18\x03 \x02(\r:\x01\x30\x12\x12\n\npublic_key\x18\x04 \x03(\x0c\x12\x11\n\tchallenge\x18\x05 \x01(\x0c\x12\x18\n\x10\x61pplication_name\x18\x06 \x01(\t\x12\x19\n\x11\x63\x64p_version_major\x18\x07 \x01(\r\x12\x19\n\x11\x63\x64p_version_minor\x18\x08 \x01(\r\x12\x19\n\x11\x63\x64p_version_patch\x18\t \x01(\r\x12\x1b\n\x13idle_lockout_period\x18\n \x01(\r\x12\x1f\n\x17system_use_notification\x18\x0b \x01(\t\x12=\n\x0fsuggested_users\x18\x0c \x03(\x0b\x32$.StudioAPI.Proto.Hello.SuggestedUser\x1aG\n\rSuggestedUser\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x12\n\nfirst_name\x18\x02 \x01(\t\x12\x11\n\tlast_name\x18\x03 \x01(\t\"\x9f\x01\n\x0b\x41uthRequest\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12J\n\x12\x63hallenge_response\x18\x02 \x03(\x0b\x32..StudioAPI.Proto.AuthRequest.ChallengeResponse\x1a\x33\n\x11\x43hallengeResponse\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x01(\x0c\"\xc0\x01\n#AdditionalChallengeResponseRequired\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0e\n\x06prompt\x18\x02 \x01(\t\x12Q\n\tparameter\x18\x03 \x03(\x0b\x32>.StudioAPI.Proto.AdditionalChallengeResponseRequired.Parameter\x1a(\n\tParameter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\xd4\x03\n\x0c\x41uthResponse\x12\x41\n\x0bresult_code\x18\x01 \x01(\x0e\x32,.StudioAPI.Proto.AuthResponse.AuthResultCode\x12\x13\n\x0bresult_text\x18\x02 \x01(\t\x12\x64\n&additional_challenge_response_required\x18\x03 \x03(\x0b\x32\x34.StudioAPI.Proto.AdditionalChallengeResponseRequired\x12\x15\n\rrole_assigned\x18\x04 \x03(\t\"\xee\x01\n\x0e\x41uthResultCode\x12\x18\n\x14\x65\x43redentialsRequired\x10\x00\x12\x0c\n\x08\x65Granted\x10\x01\x12\"\n\x1e\x65GrantedPasswordWillExpireSoon\x10\x02\x12\x18\n\x14\x65NewPasswordRequired\x10\n\x12\x1d\n\x19\x65InvalidChallengeResponse\x10\x0b\x12\x1f\n\x1b\x65\x41\x64\x64itionalResponseRequired\x10\x0c\x12\x17\n\x13\x65TemporarilyBlocked\x10\r\x12\x1d\n\x19\x65ReauthenticationRequired\x10\x0e\"\xe7\x08\n\tContainer\x12\x35\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x1f.StudioAPI.Proto.Container.Type\x12%\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x16.StudioAPI.Proto.Error\x12\x19\n\x11structure_request\x18\x03 \x03(\r\x12\x31\n\x12structure_response\x18\x04 \x03(\x0b\x32\x15.StudioAPI.Proto.Node\x12\x35\n\x0egetter_request\x18\x05 \x03(\x0b\x32\x1d.StudioAPI.Proto.ValueRequest\x12\x36\n\x0fgetter_response\x18\x06 \x03(\x0b\x32\x1d.StudioAPI.Proto.VariantValue\x12\x35\n\x0esetter_request\x18\x07 \x03(\x0b\x32\x1d.StudioAPI.Proto.VariantValue\x12!\n\x19structure_change_response\x18\x08 \x03(\r\x12\x1d\n\x15\x63urrent_time_response\x18\t \x01(\x04\x12\x34\n\x11\x63hild_add_request\x18\n \x03(\x0b\x32\x19.StudioAPI.Proto.ChildAdd\x12:\n\x14\x63hild_remove_request\x18\x0b \x03(\x0b\x32\x1c.StudioAPI.Proto.ChildRemove\x12\x35\n\x0fre_auth_request\x18\x0c \x01(\x0b\x32\x1c.StudioAPI.Proto.AuthRequest\x12\x37\n\x10re_auth_response\x18\r \x01(\x0b\x32\x1d.StudioAPI.Proto.AuthResponse\x12\x34\n\revent_request\x18\x0e \x03(\x0b\x32\x1d.StudioAPI.Proto.EventRequest\x12\x32\n\x0e\x65vent_response\x18\x0f \x03(\x0b\x32\x1a.StudioAPI.Proto.EventInfo\"\xef\x02\n\x04Type\x12\x10\n\x0c\x65RemoteError\x10\x00\x12\x15\n\x11\x65StructureRequest\x10\x01\x12\x16\n\x12\x65StructureResponse\x10\x02\x12\x12\n\x0e\x65GetterRequest\x10\x03\x12\x13\n\x0f\x65GetterResponse\x10\x04\x12\x12\n\x0e\x65SetterRequest\x10\x05\x12\x1c\n\x18\x65StructureChangeResponse\x10\x06\x12\x17\n\x13\x65\x43urrentTimeRequest\x10\x07\x12\x18\n\x14\x65\x43urrentTimeResponse\x10\x08\x12\x14\n\x10\x65\x43hildAddRequest\x10\t\x12\x17\n\x13\x65\x43hildRemoveRequest\x10\n\x12\x12\n\x0e\x65ReauthRequest\x10\x0b\x12\x13\n\x0f\x65ReauthResponse\x10\x0c\x12\x19\n\x15\x65\x41\x63tivityNotification\x10\r\x12\x11\n\reEventRequest\x10\x0e\x12\x12\n\x0e\x65\x45ventResponse\x10\x0f*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"\x81\x01\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x02(\r\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x0f\n\x07node_id\x18\x03 \x01(\r\x12\x11\n\tparameter\x18\x04 \x01(\t\x12\x11\n\tchallenge\x18\x05 \x01(\x0c\x12\x1b\n\x13idle_lockout_period\x18\x06 \x01(\r*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"\xb6\x03\n\x04Info\x12\x0f\n\x07node_id\x18\x01 \x02(\r\x12\x0c\n\x04name\x18\x02 \x02(\t\x12/\n\tnode_type\x18\x03 \x02(\x0e\x32\x1c.StudioAPI.Proto.CDPNodeType\x12\x31\n\nvalue_type\x18\x04 \x01(\x0e\x32\x1d.StudioAPI.Proto.CDPValueType\x12\x11\n\ttype_name\x18\x05 \x01(\t\x12\x13\n\x0bserver_addr\x18\x06 \x01(\t\x12\x13\n\x0bserver_port\x18\x07 \x01(\r\x12\x10\n\x08is_local\x18\x08 \x01(\x08\x12\r\n\x05\x66lags\x18\t \x01(\r\"\xc2\x01\n\x05\x46lags\x12\t\n\x05\x65None\x10\x00\x12\x0f\n\x0b\x65NodeIsLeaf\x10\x01\x12\x16\n\x12\x65ValueIsPersistent\x10\x02\x12\x14\n\x10\x65ValueIsReadOnly\x10\x04\x12\x14\n\x10\x65NodeIsRemovable\x10\x08\x12\x17\n\x13\x65NodeCanAddChildren\x10\x10\x12\x14\n\x10\x65NodeIsRenamable\x10 \x12\x13\n\x0f\x65NodeIsInternal\x10@\x12\x15\n\x10\x65NodeIsImportant\x10\x80\x01*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"Z\n\x04Node\x12#\n\x04info\x18\x01 \x02(\x0b\x32\x15.StudioAPI.Proto.Info\x12#\n\x04node\x18\x02 \x03(\x0b\x32\x15.StudioAPI.Proto.Node*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"Y\n\x08\x43hildAdd\x12\x16\n\x0eparent_node_id\x18\x01 \x02(\r\x12\x12\n\nchild_name\x18\x02 \x02(\t\x12\x17\n\x0f\x63hild_type_name\x18\x03 \x02(\t*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"C\n\x0b\x43hildRemove\x12\x16\n\x0eparent_node_id\x18\x01 \x02(\r\x12\x12\n\nchild_name\x18\x02 \x02(\t*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"\x92\x02\n\x0cVariantValue\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x0f\n\x07\x64_value\x18\x02 \x01(\x01\x12\x0f\n\x07\x66_value\x18\x03 \x01(\x02\x12\x12\n\nui64_value\x18\x04 \x01(\x04\x12\x11\n\ti64_value\x18\x05 \x01(\x12\x12\x10\n\x08ui_value\x18\x06 \x01(\r\x12\x0f\n\x07i_value\x18\x07 \x01(\x11\x12\x10\n\x08us_value\x18\x08 \x01(\r\x12\x0f\n\x07s_value\x18\t \x01(\x11\x12\x10\n\x08uc_value\x18\n \x01(\r\x12\x0f\n\x07\x63_value\x18\x0b \x01(\x11\x12\x0f\n\x07\x62_value\x18\x0c \x01(\x08\x12\x11\n\tstr_value\x18\r \x01(\t\x12\x11\n\ttimestamp\x18\x0e \x01(\x04*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"X\n\x0cValueRequest\x12\x0f\n\x07node_id\x18\x01 \x02(\r\x12\n\n\x02\x66s\x18\x02 \x01(\x01\x12\x0c\n\x04stop\x18\x03 \x01(\x08\x12\x13\n\x0bsample_rate\x18\x04 \x01(\x01*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"N\n\x0c\x45ventRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\r\x12\x15\n\rstarting_from\x18\x02 \x01(\x04\x12\x0c\n\x04stop\x18\x03 \x01(\x08*\x08\x08\x64\x10\x80\x80\x80\x80\x02\"\x90\x03\n\tEventInfo\x12\x0f\n\x07node_id\x18\x01 \x03(\r\x12\n\n\x02id\x18\x02 \x01(\x04\x12\x0e\n\x06sender\x18\x03 \x01(\t\x12\x0c\n\x04\x63ode\x18\x04 \x01(\r\x12\x0e\n\x06status\x18\x05 \x01(\r\x12\x11\n\ttimestamp\x18\x06 \x01(\x04\x12\x32\n\x04\x64\x61ta\x18\x07 \x03(\x0b\x32$.StudioAPI.Proto.EventInfo.EventData\x12\x1d\n\x15\x61\x63k_handler_node_name\x18\x08 \x01(\t\x12$\n\x1c\x61\x63k_handler_param_data_names\x18\t \x03(\t\x1a(\n\tEventData\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"x\n\tCodeFlags\x12\r\n\taAlarmSet\x10\x01\x12\r\n\teAlarmClr\x10\x02\x12\r\n\teAlarmAck\x10\x04\x12\x0c\n\x08\x65Reprise\x10@\x12\x1d\n\x18\x65SourceObjectUnavailable\x10\x80\x02\x12\x11\n\teNodeBoot\x10\x80\x80\x80\x80\x04*\x08\x08\x64\x10\x80\x80\x80\x80\x02*\xd5\x01\n\x0fRemoteErrorCode\x12\x1a\n\x16\x65\x41UTH_RESPONSE_EXPIRED\x10\x01\x12\x14\n\x10\x65INVALID_REQUEST\x10\n\x12\x1f\n\x1b\x65UNSUPPORTED_CONTAINER_TYPE\x10\x14\x12\x1f\n\x1b\x65VALUE_THROTTLING_OCCURRING\x10\x1e\x12\x1d\n\x19\x65VALUE_THROTTLING_STOPPED\x10\x1f\x12\x15\n\x11\x65\x43HILD_ADD_FAILED\x10(\x12\x18\n\x14\x65\x43HILD_REMOVE_FAILED\x10\x32*\xfb\x01\n\x0b\x43\x44PNodeType\x12\x1a\n\rCDP_UNDEFINED\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x0e\n\nCDP_SYSTEM\x10\x00\x12\x13\n\x0f\x43\x44P_APPLICATION\x10\x01\x12\x11\n\rCDP_COMPONENT\x10\x02\x12\x0e\n\nCDP_OBJECT\x10\x03\x12\x0f\n\x0b\x43\x44P_MESSAGE\x10\x04\x12\x13\n\x0f\x43\x44P_BASE_OBJECT\x10\x05\x12\x10\n\x0c\x43\x44P_PROPERTY\x10\x06\x12\x0f\n\x0b\x43\x44P_SETTING\x10\x07\x12\x0c\n\x08\x43\x44P_ENUM\x10\x08\x12\x10\n\x0c\x43\x44P_OPERATOR\x10\t\x12\x0c\n\x08\x43\x44P_NODE\x10\n\x12\x11\n\rCDP_USER_TYPE\x10\x64*\xbc\x01\n\x0c\x43\x44PValueType\x12\x0e\n\neUNDEFINED\x10\x00\x12\x0b\n\x07\x65\x44OUBLE\x10\x01\x12\x0b\n\x07\x65UINT64\x10\x02\x12\n\n\x06\x65INT64\x10\x03\x12\n\n\x06\x65\x46LOAT\x10\x04\x12\t\n\x05\x65UINT\x10\x05\x12\x08\n\x04\x65INT\x10\x06\x12\x0b\n\x07\x65USHORT\x10\x07\x12\n\n\x06\x65SHORT\x10\x08\x12\n\n\x06\x65UCHAR\x10\t\x12\t\n\x05\x65\x43HAR\x10\n\x12\t\n\x05\x65\x42OOL\x10\x0b\x12\x0b\n\x07\x65STRING\x10\x0c\x12\r\n\teUSERTYPE\x10\x64\x42*\n\x1b\x63om.cdptech.cdpclient.protoB\tStudioAPIH\x03')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cdp_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'\n\033com.cdptech.cdpclient.protoB\tStudioAPIH\003'
35
+ _globals['_REMOTEERRORCODE']._serialized_start=4098
36
+ _globals['_REMOTEERRORCODE']._serialized_end=4311
37
+ _globals['_CDPNODETYPE']._serialized_start=4314
38
+ _globals['_CDPNODETYPE']._serialized_end=4565
39
+ _globals['_CDPVALUETYPE']._serialized_start=4568
40
+ _globals['_CDPVALUETYPE']._serialized_end=4756
41
+ _globals['_HELLO']._serialized_start=31
42
+ _globals['_HELLO']._serialized_end=462
43
+ _globals['_HELLO_SUGGESTEDUSER']._serialized_start=391
44
+ _globals['_HELLO_SUGGESTEDUSER']._serialized_end=462
45
+ _globals['_AUTHREQUEST']._serialized_start=465
46
+ _globals['_AUTHREQUEST']._serialized_end=624
47
+ _globals['_AUTHREQUEST_CHALLENGERESPONSE']._serialized_start=573
48
+ _globals['_AUTHREQUEST_CHALLENGERESPONSE']._serialized_end=624
49
+ _globals['_ADDITIONALCHALLENGERESPONSEREQUIRED']._serialized_start=627
50
+ _globals['_ADDITIONALCHALLENGERESPONSEREQUIRED']._serialized_end=819
51
+ _globals['_ADDITIONALCHALLENGERESPONSEREQUIRED_PARAMETER']._serialized_start=779
52
+ _globals['_ADDITIONALCHALLENGERESPONSEREQUIRED_PARAMETER']._serialized_end=819
53
+ _globals['_AUTHRESPONSE']._serialized_start=822
54
+ _globals['_AUTHRESPONSE']._serialized_end=1290
55
+ _globals['_AUTHRESPONSE_AUTHRESULTCODE']._serialized_start=1052
56
+ _globals['_AUTHRESPONSE_AUTHRESULTCODE']._serialized_end=1290
57
+ _globals['_CONTAINER']._serialized_start=1293
58
+ _globals['_CONTAINER']._serialized_end=2420
59
+ _globals['_CONTAINER_TYPE']._serialized_start=2043
60
+ _globals['_CONTAINER_TYPE']._serialized_end=2410
61
+ _globals['_ERROR']._serialized_start=2423
62
+ _globals['_ERROR']._serialized_end=2552
63
+ _globals['_INFO']._serialized_start=2555
64
+ _globals['_INFO']._serialized_end=2993
65
+ _globals['_INFO_FLAGS']._serialized_start=2789
66
+ _globals['_INFO_FLAGS']._serialized_end=2983
67
+ _globals['_NODE']._serialized_start=2995
68
+ _globals['_NODE']._serialized_end=3085
69
+ _globals['_CHILDADD']._serialized_start=3087
70
+ _globals['_CHILDADD']._serialized_end=3176
71
+ _globals['_CHILDREMOVE']._serialized_start=3178
72
+ _globals['_CHILDREMOVE']._serialized_end=3245
73
+ _globals['_VARIANTVALUE']._serialized_start=3248
74
+ _globals['_VARIANTVALUE']._serialized_end=3522
75
+ _globals['_VALUEREQUEST']._serialized_start=3524
76
+ _globals['_VALUEREQUEST']._serialized_end=3612
77
+ _globals['_EVENTREQUEST']._serialized_start=3614
78
+ _globals['_EVENTREQUEST']._serialized_end=3692
79
+ _globals['_EVENTINFO']._serialized_start=3695
80
+ _globals['_EVENTINFO']._serialized_end=4095
81
+ _globals['_EVENTINFO_EVENTDATA']._serialized_start=3923
82
+ _globals['_EVENTINFO_EVENTDATA']._serialized_end=3963
83
+ _globals['_EVENTINFO_CODEFLAGS']._serialized_start=3965
84
+ _globals['_EVENTINFO_CODEFLAGS']._serialized_end=4085
85
+ # @@protoc_insertion_point(module_scope)
@@ -226,3 +226,47 @@ def create_auth_response_expired_error(challenge):
226
226
  response.error.text = "Session expired"
227
227
  response.error.challenge = challenge
228
228
  return response
229
+
230
+
231
+ event_info1 = proto.EventInfo()
232
+ event_info1.node_id.append(1)
233
+ event_info1.id = 12345
234
+ event_info1.sender = "App1.Comp1"
235
+ event_info1.code = 1001
236
+ event_info1.timestamp = 1234567890
237
+ data_item1 = event_info1.data.add()
238
+ data_item1.name = "temperature"
239
+ data_item1.value = "25.5"
240
+ data_item2 = event_info1.data.add()
241
+ data_item2.name = "status"
242
+ data_item2.value = "OK"
243
+
244
+ event_info2 = proto.EventInfo()
245
+ event_info2.node_id.append(1)
246
+ event_info2.id = 67890
247
+ event_info2.sender = "App2.Comp2"
248
+ event_info2.code = 2001
249
+ event_info2.timestamp = 1234567900
250
+
251
+ def create_event_request(node_id):
252
+ request = proto.Container()
253
+ request.message_type = proto.Container.eEventRequest
254
+ event_req = request.event_request.add()
255
+ event_req.node_id = node_id
256
+ event_req.stop = False
257
+ return request
258
+
259
+ def create_event_unrequest(node_id):
260
+ request = proto.Container()
261
+ request.message_type = proto.Container.eEventRequest
262
+ event_req = request.event_request.add()
263
+ event_req.node_id = node_id
264
+ event_req.stop = True
265
+ return request
266
+
267
+ def create_event_response(event_info):
268
+ response = proto.Container()
269
+ response.message_type = proto.Container.eEventResponse
270
+ event_resp = response.event_response.add()
271
+ event_resp.CopyFrom(event_info)
272
+ return response
@@ -275,3 +275,31 @@ class ConnectionTester(unittest.TestCase):
275
275
  self.assertEqual(self._connection._notification_listener.credentials_requested_call_count, 1)
276
276
  self.assertFalse(mock_send.called)
277
277
  self.assertFalse(self._connection._is_connected)
278
+
279
+ @mock.patch.object(cdp.websocket.WebSocketApp, 'send')
280
+ @mock.patch.object(cdp.Connection, '_update_time_difference')
281
+ def test_sending_event_request(self, mock_time_update, mock_send):
282
+ node_id = 1
283
+ self._connection.send_event_request(node_id)
284
+ expected_msg = fake_data.create_event_request(node_id).SerializeToString()
285
+ # Debug: check if any message was sent
286
+ self.assertTrue(mock_send.called)
287
+ # Just verify the right type of call was made rather than exact bytes
288
+ calls = mock_send.call_args_list
289
+ self.assertEqual(len(calls), 1)
290
+
291
+ @mock.patch.object(cdp.websocket.WebSocketApp, 'send')
292
+ @mock.patch.object(cdp.Connection, '_update_time_difference')
293
+ def test_sending_event_unrequest(self, mock_time_update, mock_send):
294
+ node_id = 1
295
+ self._connection.send_event_unrequest(node_id)
296
+ mock_send.assert_any_call(fake_data.create_event_unrequest(node_id).SerializeToString())
297
+
298
+ @mock.patch.object(cdp.NodeTree, 'find_by_id')
299
+ @mock.patch.object(cdp.Node, '_update_event')
300
+ def test_node_event_received(self, mock_update_event, mock_find_by_id):
301
+ self._connection._is_connected = True
302
+ mock_find_by_id.return_value = cdp.Node(None, self._connection, fake_data.app1_node)
303
+ response = fake_data.create_event_response(fake_data.event_info1)
304
+ self._connection._handle_container_message(response.SerializeToString())
305
+ mock_update_event.assert_called_once_with(response.event_response[0])