p3lib 1.1.108__py2.py3-none-any.whl

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.
p3lib/login.html ADDED
@@ -0,0 +1,98 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Bokeh App with Login</title>
5
+ <style>
6
+ *{
7
+ margin:0;
8
+ padding: 0;
9
+ box-sizing: border-box;
10
+ }
11
+ html{
12
+ height: 100%;
13
+ }
14
+ body{
15
+ font-family: 'Segoe UI', sans-serif;;
16
+ font-size: 1rem;
17
+ line-height: 1.6;
18
+ height: 100%;
19
+ }
20
+ p{
21
+ padding-bottom: 5px;
22
+ }
23
+ .wrap {
24
+ width: 100%;
25
+ height: 100%;
26
+ display: flex;
27
+ justify-content: center;
28
+ align-items: center;
29
+ background: #fafafa;
30
+ }
31
+ .login-form{
32
+ width: 350px;
33
+ margin: 0 auto;
34
+ border: 1px solid #ddd;
35
+ padding: 2rem;
36
+ background: #ffffff;
37
+ }
38
+ .form-input{
39
+ background: #fafafa;
40
+ border: 1px solid #eeeeee;
41
+ padding: 12px;
42
+ width: 100%;
43
+ }
44
+ .form-group{
45
+ margin-bottom: 1rem;
46
+ }
47
+ .form-button{
48
+ background: #69d2e7;
49
+ border: 1px solid #ddd;
50
+ color: #ffffff;
51
+ padding: 10px;
52
+ width: 100%;
53
+ text-transform: uppercase;
54
+ }
55
+ .form-button:hover{
56
+ background: #69c8e7;
57
+ }
58
+ .form-header{
59
+ text-align: center;
60
+ margin-bottom : 2rem;
61
+ }
62
+ .form-footer{
63
+ text-align: center;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="wrap">
69
+ <form class="login-form" action="/login" method="post">
70
+ {% module xsrf_form_html() %}
71
+ <div class="form-header">
72
+ <h3><img src="https://static.bokeh.org/logos/logotype.svg"></h3>
73
+ <p> Login to access your application</p>
74
+ </div>
75
+ <!--Email Input-->
76
+ <div class="form-group">
77
+ <input name="username" type="text" class="form-input" autocapitalize="off" autocorrect="off" placeholder="username">
78
+ </div>
79
+ <!--Password Input-->
80
+ <div class="form-group">
81
+ <input name="password" type="password" class="form-input" placeholder="password">
82
+ </div>
83
+ <!--Login Button-->
84
+ <div class="form-group">
85
+ <button class="form-button" type="submit">Login</button>
86
+ </div>
87
+ <span class="errormessage">{{errormessage}}</span>
88
+ <div><small>
89
+ <p>This is a <b>toy example</b> that demonstrates the authentication building blocks that are available with a Bokeh server.</p>
90
+
91
+ <p>You can log in with user: <b><i>bokeh</i></b> / pass: <b><i>bokeh</i></b></p>
92
+
93
+ <p><a href="https://docs.bokeh.org/en/latest/docs/user_guide/server/deploy.html#authentication">See the documentation</a> for a full discussion.</p>
94
+ </small></div>
95
+ </form>
96
+ </div>
97
+ </body>
98
+ </html>
p3lib/mqtt_rpc.py ADDED
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/python3
2
+
3
+ import paho.mqtt.client as mqtt
4
+ from abc import abstractmethod
5
+ import json
6
+ import traceback
7
+ from threading import Condition
8
+ from queue import Queue
9
+
10
+ class MQTTError(Exception):
11
+ pass
12
+
13
+ class MQTTRPCClient(object):
14
+
15
+ DEFAULT_HOST = "localhost"
16
+ DEFAULT_PORT = 1883
17
+ DEFAULT_KEEPALIVE_SECONDS = 60
18
+ DEFAULT_SRV_ID = 1
19
+
20
+ CLIENT_ID_DICT_KEY = "SRC"
21
+ METHOD_DICT_KEY = "METHOD"
22
+ ARGS_DICT_KEY = "ARGS"
23
+ RESPONSE_DICT_KEY = "RESPONSE"
24
+
25
+ @staticmethod
26
+ def GetServerRPCTopic(idNumber=1):
27
+ """@brief Responsible for getting the Server RPC topic string.
28
+ @param idNumber The idNumber of the RPC server that RPC messages can be sent to.
29
+ @return A string that represents the RPC server topic."""
30
+ return "server%d/RPC" % (idNumber)
31
+
32
+ @staticmethod
33
+ def GetClientID(id=1):
34
+ """@brief Responsible for getting a client ID string.
35
+ @param id The unique id of the MQTT client.
36
+ @return A string that represents the unique client ID."""
37
+ return "client%s/RPC" % ( str(id) )
38
+
39
+ def __init__(self, uo, options):
40
+ """@brief Responsible fopr providing RPC functionality via an MQTT server.
41
+ @param uo A UO instance that implements the info(text) and error(text) methods
42
+ to send information to the user.
43
+ @param options An options instance.
44
+ options.sid = A number to uniquely identify the server
45
+ options.cid = A number to uniquely identify the client
46
+ options.server = The host address of the MQTT server
47
+ options.port = The port number to connect to the MQTT server
48
+ options.keepalive = The keepalive period in seconds
49
+
50
+ The MQTTRPCClient class can only be a base class and should be extended. Child classes must
51
+ implement/override the _onMessage() method."""
52
+ self._uo = uo
53
+ self._options = options
54
+
55
+ self._serverRPCTopic = MQTTRPCClient.GetServerRPCTopic(idNumber=self._options.sid)
56
+
57
+ self._client = mqtt.Client()
58
+ self._client.on_connect = self._onConnect
59
+ self._client.on_message = self._onMessage
60
+
61
+ def connect(self):
62
+ """@brief Connect to the MQTT server"""
63
+ self._client.connect(self._options.server, self._options.port, self._options.keepalive)
64
+
65
+ def loopForever(self):
66
+ """@brief Wait here to service messages after a successfll connection."""
67
+ self._client.loop_forever()
68
+
69
+ def _onConnect(self, client, userdata, flags, rc):
70
+ """@brief Called when connected to the MQTT server."""
71
+ self._client.subscribe( self._serverRPCTopic )
72
+ self._uo.info("Connected to MQTT server (%s:%d) and subscribed to %s" % (self._options.server, self._options.port, self._serverRPCTopic) )
73
+
74
+ @abstractmethod
75
+ def _onMessage(self, client, userdata, msg):
76
+ """@brief Called when a message is received from the MQTT server.
77
+ This method mst be implented in a child class."""
78
+ pass
79
+
80
+ class MQTTRPCProviderClient(MQTTRPCClient):
81
+
82
+ def __init__(self, uo, options, rpcMethodProviderList):
83
+ """@brief Responsible for providing RPC functionality via an MQTT server.
84
+ MQTTRPCProviderClient instances implement the RPC at the destination/remote side
85
+ and return the results to the caller side.
86
+ @param uo A UO instance that implements the info(text) and error(text) methods
87
+ to send information to the user.
88
+ @param options An options instance.
89
+ options.sid = A number to uniquely identify the server
90
+ options.cid = A number to uniquely identify the client receiving responses from the server.
91
+ options.server = The host address of the MQTT server
92
+ options.port = The port number to connect to the MQTT server
93
+ options.keepalive = The keepalive period in seconds
94
+ @param rpcMethodProviderList A list/tuple of class instances that provide the RPC methods."""
95
+ MQTTRPCClient.__init__(self, uo, options)
96
+ self._rpcMethodProviderList=rpcMethodProviderList
97
+
98
+ def _onMessage(self, client, userdata, msg):
99
+ """@brief Called when a message is received from the MQTT server.
100
+ @param client: the client instance for this callback
101
+ @param userdata: the private user data as set in Client() or userdata_set()
102
+ @param message: an instance of MQTTMessage.
103
+ This is a class with members topic, payload, qos, retain."""
104
+ try:
105
+
106
+ rxStr = msg.payload.decode()
107
+ self._uo.info( "RX: %s" % (rxStr) )
108
+
109
+ jsonDict = json.loads( msg.payload.decode() )
110
+
111
+ if MQTTRPCClient.CLIENT_ID_DICT_KEY in jsonDict and MQTTRPCClient.METHOD_DICT_KEY in jsonDict:
112
+ clientID = jsonDict[MQTTRPCClient.CLIENT_ID_DICT_KEY]
113
+ methodName = jsonDict[MQTTRPCClient.METHOD_DICT_KEY]
114
+ args = jsonDict[MQTTRPCClient.ARGS_DICT_KEY]
115
+
116
+ method = None
117
+ for rpcMethodProvider in self._rpcMethodProviderList:
118
+ if hasattr(rpcMethodProvider, methodName):
119
+ method = getattr(rpcMethodProvider, methodName)
120
+
121
+ if method:
122
+ if len(args) > 0:
123
+ response = method(args)
124
+ else:
125
+ response = method()
126
+
127
+ jsonDict[MQTTRPCClient.RESPONSE_DICT_KEY]=response
128
+
129
+ jsonString = json.dumps(jsonDict)
130
+
131
+ self._uo.info( "TX: %s" % (jsonString) )
132
+ client.publish(clientID, jsonString)
133
+
134
+ else:
135
+ raise AttributeError("Unable to find a provider of the %s method." % (methodName) )
136
+
137
+ except:
138
+ tb = traceback.format_exc()
139
+ self._uo.error(tb)
140
+
141
+
142
+
143
+ class MQTTRPCCallerClient(MQTTRPCClient):
144
+
145
+ def __init__(self, uo, options, responseTimeoutSeconds = 10):
146
+ """@brief Responsible fopr providing RPC functionality via an MQTT server.
147
+ MQTTRPCCallerClient instances implement the RPC at the src/caller side
148
+ sending RPC requests to destination/remote and reciving responses.
149
+ @param uo A UO instance that implements the info(text) and error(text) methods
150
+ to send information to the user.
151
+ @param options An options instance.
152
+ options.sid = A number to uniquely identify the server responding to RPC calls.
153
+ options.cid = A number to uniquely identify the client receiving responses from the server.
154
+ options.server = The host address of the MQTT server
155
+ options.port = The port number to connect to the MQTT server
156
+ options.keepalive = The keepalive period in seconds
157
+ @param timeoutSeconds The number of seconds to wait for a timeout when no response is received."""
158
+ MQTTRPCClient.__init__(self, uo, options)
159
+ self._responseTimeoutSeconds = responseTimeoutSeconds
160
+
161
+ self._clientSRC = MQTTRPCClient.GetClientID(options.cid)
162
+
163
+ self._serverRPCTopic = MQTTRPCClient.GetServerRPCTopic(idNumber=self._options.sid)
164
+
165
+ self._sendMsgDict = None
166
+ self._responseQueue = Queue()
167
+
168
+ self._condition = Condition()
169
+
170
+ def _onMessage(self, client, userdata, msg):
171
+ """@brief Called when a message is received from the MQTT server.
172
+ @param client: the client instance for this callback
173
+ @param userdata: the private user data as set in Client() or userdata_set()
174
+ @param message: an instance of MQTTMessage.
175
+ This is a class with members topic, payload, qos, retain.
176
+ @return None"""
177
+ try:
178
+
179
+ responseDict = json.loads( msg.payload.decode() )
180
+ if responseDict and MQTTRPCClient.RESPONSE_DICT_KEY in responseDict:
181
+ self._condition.acquire()
182
+ self._responseQueue.put(responseDict[MQTTRPCClient.RESPONSE_DICT_KEY])
183
+ self._condition.notify()
184
+ self._condition.release()
185
+
186
+ except:
187
+ tb = traceback.format_exc()
188
+ self._uo.error(tb)
189
+
190
+ def _getResponse(self):
191
+ """@brief Internal method used to get the response to an RPC
192
+ @return The response to the RPC or None if a timeout occurs."""
193
+
194
+ # blocks until the response is processed or a timeout occurs
195
+ self._condition.wait(timeout=self._responseTimeoutSeconds)
196
+
197
+ response = None
198
+ if not self._responseQueue.empty():
199
+ response = self._responseQueue.get()
200
+
201
+ return response
202
+
203
+ def _getRPCString(self):
204
+ """@brief Get the last RPC called as a string.
205
+ @return The RPC string representation."""
206
+ if self._sendMsgDict and MQTTRPCClient.METHOD_DICT_KEY in self._sendMsgDict and MQTTRPCClient.ARGS_DICT_KEY in self._sendMsgDict:
207
+ return "%s(%s)" % (self._sendMsgDict[MQTTRPCClient.METHOD_DICT_KEY], self._sendMsgDict[MQTTRPCClient.ARGS_DICT_KEY])
208
+ else:
209
+ return ""
210
+
211
+ def rpcCall(self, methodName, argList):
212
+ """@brief Call an RPC
213
+ @param methodName The name of the RPC to call. Currently this is just the method name.
214
+ This method could exist on an instance of any object. We could have
215
+ made this include instance details but to keep things simple
216
+ just the method name is provided.
217
+ @param argList The arguments for the RPC
218
+ @return The response to the RPC."""
219
+ self._sendMsgDict={}
220
+ self._sendMsgDict[MQTTRPCClient.CLIENT_ID_DICT_KEY]=self._clientSRC
221
+ self._sendMsgDict[MQTTRPCClient.METHOD_DICT_KEY]=methodName
222
+ self._sendMsgDict[MQTTRPCClient.ARGS_DICT_KEY]=argList
223
+
224
+ jsonDict = json.dumps(self._sendMsgDict, indent=4, sort_keys=True)
225
+
226
+ self._client.subscribe(self._clientSRC)
227
+
228
+ self._condition.acquire()
229
+
230
+ response = None
231
+ try:
232
+ self._client.publish(self._serverRPCTopic, jsonDict)
233
+
234
+ response = self._getResponse()
235
+
236
+ finally:
237
+
238
+ self._condition.release()
239
+
240
+ return response
p3lib/netif.py ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3.8
2
+
3
+ import platform
4
+ from subprocess import check_output
5
+ import socket
6
+ import struct
7
+
8
+ class NetIF(object):
9
+ """@Responsible for determining details about the network interfaces on a PC."""
10
+
11
+ LINUX_OS_NAME = "Linux"
12
+ SUPPORTED_OS_NAMES = [ LINUX_OS_NAME ]
13
+
14
+ ID_STR1 = "inet addr:"
15
+ ID_STR2 = "addr:"
16
+ ID_STR3 = "mask:"
17
+ ID_STR4 = "inet "
18
+
19
+ IP_NETMASK_SEP = "/"
20
+
21
+ @staticmethod
22
+ def IsAddressInNetwork(ip, net):
23
+ """@brief Determine if an IP address is in a IP network.
24
+ @param ip The IP address of the interface in the format 192.168.1.20
25
+ @param net The network in the format 192.168.1.0/24"""
26
+ ipaddr = socket.inet_aton(ip)
27
+ netaddr, netmask = net.split(NetIF.IP_NETMASK_SEP)
28
+ netaddr = socket.inet_aton(netaddr)
29
+
30
+ ipint = struct.unpack("!I", ipaddr)[0]
31
+ netint = struct.unpack("!I", netaddr)[0]
32
+ maskint = (0xFFFFFFFF << (32 - int(netmask))) & 0xFFFFFFFF
33
+
34
+ return ipint & maskint == netint
35
+
36
+
37
+ @staticmethod
38
+ def IPStr2int(addr):
39
+ """@brief Convert an IP address string to an integer.
40
+ @param addr The IP address string.
41
+ @return The integer represented by the IP address."""
42
+ return struct.unpack("!I", socket.inet_aton(addr))[0]
43
+
44
+ @staticmethod
45
+ def Int2IPStr(addr):
46
+ """@brief Convert an integer to an IP address string.
47
+ @param addr The Integer value of the IP address.
48
+ @return The string value (dotted quad) of the IP address."""
49
+ return socket.inet_ntoa(struct.pack("!I", addr))
50
+
51
+ @staticmethod
52
+ def NetmaskToBitCount(netmask):
53
+ """@brief Convert a dotted quad netmask to a bit count netmask format.
54
+ @param netmask: The dotted quad netmask (E.G 255.255.255.0)
55
+ @return: The netmask as a count of the set bits (E.G 24).
56
+ """
57
+ return sum([bin(int(x)).count('1') for x in netmask.split('.')])
58
+
59
+ @staticmethod
60
+ def BitCountToNetMask(bitCount):
61
+ """@brief Convert a bit count to a netmask string
62
+ @param bitCount The number of bits in the netmask.
63
+ @return The netmask string E.G 255.255.255.0"""
64
+ if bitCount > 32:
65
+ raise Exception("{} greater than 32".format(bitCount))
66
+ num=1
67
+ for _ in range(0,bitCount):
68
+ num=num>>1
69
+ num=num|0x80000000
70
+ return NetIF.Int2IPStr(num)
71
+
72
+ def __init__(self):
73
+
74
+ self._osName = platform.system()
75
+
76
+ self._checkSupportedOS()
77
+
78
+ self._ifDict = None
79
+
80
+ def getLocalNetworkAddress(self):
81
+ """@brief Get the IP address of the local interface that has the default route.
82
+ The IP address will be the source IP address for packets are sent over the default route from this machine.
83
+ This works on Windows, Linux, Android"""
84
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
85
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
86
+ sock.connect(('<broadcast>', 0))
87
+ netIPAddr = sock.getsockname()[0]
88
+ sock.close()
89
+ return netIPAddr
90
+
91
+ def getIFDict(self, readNow=False, includeNoIPIF = False):
92
+ """@param readNow If True the read the current network interface
93
+ state now regardless of wether we have read it previously.
94
+ If False and we have read the state of the network
95
+ interfaces previously then return the previous state.
96
+ @param includeNoIPIF If True then interfaces with no IPaddress are included in the results.
97
+ @return A dict of the IP network interfaces on this platform.
98
+ key = name of interface
99
+ value = A list of <IP ADDRESS>/<NET MASK BIT COUNT>"""
100
+ if self._ifDict and not readNow:
101
+ return self._ifDict
102
+
103
+ if self._osName.find(NetIF.LINUX_OS_NAME) >= 0 :
104
+ self._ifDict = self.getLinuxIFDict(includeNoIPIF=includeNoIPIF)
105
+
106
+ return self._ifDict
107
+
108
+ def getLinuxIFDict(self, includeNoIPIF = False):
109
+ """@brief Get a dic that contains the local interface details.
110
+ @param includeNoIPIF If True then interfaces with no IPaddress are included in the results.
111
+ @return A dict of the IP network interfaces on this platform.
112
+ key = name of interface
113
+ value = A list of <IP ADDRESS>/<NET MASK BIT COUNT>"""
114
+ netIFDict = {}
115
+
116
+ cmdOutput = check_output(['/sbin/ip','a'] ).decode()
117
+ netIFDict = self._getLinuxIFDict(cmdOutput, includeNoIPIF=includeNoIPIF)
118
+ return netIFDict
119
+
120
+ def _getLinuxIFDict(self, cmdOutput, includeNoIPIF=False):
121
+ """@brief Get a dic that contains the local interface details.
122
+ @param cmdOutput The ip a command output.
123
+ @param includeNoIPIF If True then interfaces with no IPaddress are included in the results.
124
+ @return A dict of the IP network interfaces on this platform.
125
+ key = name of interface
126
+ value = A list of <IP ADDRESS>/<NET MASK BIT COUNT>"""
127
+ netIFDict = {}
128
+ lines = cmdOutput.lower().split('\n')
129
+ ifName = None
130
+ ifAddressList = []
131
+ for line in lines:
132
+ elems = line.split(":")
133
+ try:
134
+ #If first element is the IF ID
135
+ #Note that the IF ID may not be sequential
136
+ int(elems[0])
137
+ #Extract the if name
138
+ if len(elems) > 1:
139
+ ifName = elems[1].strip()
140
+ ifName = ifName.replace(":", "")
141
+ ifAddressList = []
142
+ if includeNoIPIF:
143
+ netIFDict[ifName] = ifAddressList
144
+ except:
145
+ line = line.strip()
146
+ if line.startswith("inet "):
147
+ elems = line.split()
148
+ if len(elems) > 1:
149
+ ipAddress = elems[1]
150
+ ifAddressList.append(ipAddress)
151
+ netIFDict[ifName] = ifAddressList
152
+
153
+ return netIFDict
154
+
155
+ def getIFName(self, ipAddress):
156
+ """@brief Get the name of the interface which the ip address can be reached directly (not via an IP gateway)
157
+ @param ipAddress The IP address that should be out on an interface.
158
+ @return The dict entry for the interface.
159
+ key == Interface name
160
+ value = Interface address.
161
+ @return The name of the local network interface or None if not found."""
162
+ ifDict = self.getIFDict()
163
+ ifNames = list(ifDict.keys())
164
+ ifNames.sort()
165
+ for ifName in ifNames:
166
+ ipDetailsList = ifDict[ifName]
167
+ for ipDetails in ipDetailsList:
168
+ elems = ipDetails.split(NetIF.IP_NETMASK_SEP)
169
+ if len(elems) == 2:
170
+ ipAddr = elems[0]
171
+ netMaskBits= int(elems[1])
172
+ netMask = NetIF.BitCountToNetMask(netMaskBits)
173
+ ipAddrInt = NetIF.IPStr2int(ipAddr)
174
+ netMaskInt = NetIF.IPStr2int(netMask)
175
+ network = ipAddrInt&netMaskInt
176
+ networkStr = socket.inet_ntoa(struct.pack("!I", network))
177
+ netMaskInt=24
178
+ if NetIF.IsAddressInNetwork(ipAddress, "%s/%s" % ( networkStr, netMaskInt) ):
179
+ return ifName
180
+
181
+ return None
182
+
183
+ def _getIFDetails(self, ifName):
184
+ """@brief Get the details of the network interface on this machine given the interface name.
185
+ @param ifName The name of the interface.
186
+ @return A tuple containing the tuples that contain
187
+ 0: IP address
188
+ 1: Netmask"""
189
+ self.getIFDict()
190
+ if ifName in self._ifDict:
191
+ ifDetailsList = self._ifDict[ifName]
192
+ for ifDetails in ifDetailsList:
193
+ if ifDetails:
194
+ elems = ifDetails.split(NetIF.IP_NETMASK_SEP)
195
+ if len(elems) == 2:
196
+ return elems
197
+ return None
198
+
199
+ def getIFIPAddress(self, ifName):
200
+ """@brief Get the IP address of the network interface on this machine given the interface name.
201
+ @param ifName The name of the interface.
202
+ @return The IP address oft heinterface of None if unknown."""
203
+ ifDetails = self._getIFDetails(ifName)
204
+ if ifDetails and len(ifDetails) == 2:
205
+ return ifDetails[0]
206
+
207
+ return None
208
+
209
+ def getIFNetmask(self, ifName):
210
+ """@brief Get the netmask of the network interface on this machine given the interface name.
211
+ @param ifName The name of the interface.
212
+ @return The IP address of the interface of None if unknown."""
213
+ ifDetails = self._getIFDetails(ifName)
214
+ if ifDetails and len(ifDetails) == 2:
215
+ netMaskBitCount = int(ifDetails[1])
216
+ return NetIF.BitCountToNetMask(netMaskBitCount)
217
+
218
+ return None
219
+
220
+ def _checkSupportedOS(self):
221
+ """@brief Check that the OS is supported."""
222
+ for supportedOSName in NetIF.SUPPORTED_OS_NAMES:
223
+ if self._osName.find(supportedOSName) != -1:
224
+ return
225
+
226
+ raise Exception("%s: %s is an unsupported platform", type(self).__name__, self._osName)