Skip to content

Commit e28f56c

Browse files
authored
Merge pull request #15 from PTCInc/Add-Save-Load-Services
Add ProjectSave and ProjectLoad services
2 parents 2894e08 + b707d76 commit e28f56c

File tree

10 files changed

+217
-26
lines changed

10 files changed

+217
-26
lines changed

kepconfig/connection.py

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ def SSL_trust_all_certs(self, val):
157157
def reinitialize(self, job_ttl = None) -> KepServiceResponse:
158158
'''Executes a Reinitialize call to the Kepware instance.
159159
160+
INPUTS:
161+
162+
"job_ttl" (optional) - Determines the number of seconds a job instance will exist following completion.
163+
160164
RETURNS:
161165
KepServiceResponse instance with job information
162166
@@ -167,7 +171,7 @@ def reinitialize(self, job_ttl = None) -> KepServiceResponse:
167171
'''
168172
url = self.url + self.__project_services_url + '/ReinitializeRuntime'
169173
try:
170-
job = self._kep_service_execute(url,job_ttl)
174+
job = self._kep_service_execute(url, None, job_ttl)
171175
return job
172176
except Exception as err:
173177
raise err
@@ -236,6 +240,74 @@ def modify_project_properties(self, DATA, force = False) -> bool:
236240
if r.code == 200: return True
237241
else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload)
238242

243+
def save_project(self, filename: str, password: str = None, job_ttl: int = None) -> KepServiceResponse:
244+
'''Executes a ProjectSave Service call to the Kepware instance. This saves
245+
a copy of the current project file to disk. The filename
246+
247+
INPUTS:
248+
249+
"filename" - Fully qualified relative file path and name of project file including the file extension.
250+
location of project file save defaults:
251+
TKS or KEP (Windows): C:\\PROGRAMDATA\\PTC\\Thingworx Kepware Server\\V6 or
252+
C:\\PROGRAMDATA\\Kepware\\KEPServerEX\\V6
253+
TKE (Linux): /opt/tkedge/v1/user_data
254+
255+
256+
"password" (optional) - Specify a password with which to load or save an encrypted project file.
257+
This password will be required to load this project file.
258+
259+
"job_ttl" (optional) - Determines the number of seconds a job instance will exist following completion.
260+
261+
RETURNS:
262+
KepServiceResponse instance with job information
263+
264+
EXCEPTIONS (If not HTTP 200 or 429 returned):
265+
266+
KepHTTPError - If urllib provides an HTTPError
267+
KepURLError - If urllib provides an URLError
268+
'''
269+
url = self.url + self.__project_services_url + '/ProjectSave'
270+
prop_data = {'servermain.PROJECT_FILENAME': filename}
271+
if password != None: prop_data['servermain.PROJECT_PASSWORD'] = password
272+
try:
273+
job = self._kep_service_execute(url, prop_data, job_ttl)
274+
return job
275+
except Exception as err:
276+
raise err
277+
278+
def load_project(self, filename: str, password: str = None, job_ttl: int = None) -> KepServiceResponse:
279+
'''Executes a ProjectLoad Service call to the Kepware instance. This loads
280+
a project file to disk.
281+
282+
INPUTS:
283+
284+
"filename" - Fully qualified path and name of project file including the file extension. Absolute
285+
paths required for TKS and KEP while file path is relative for TKE
286+
ex: Windows - filename = C:\\filepath\\test.opf
287+
Linux - filename = filepath/test.lpf - Location is /opt/tkedge/v1/user_data/filepath/test.lpf
288+
289+
"password" (optional) - Specify a password with which to load or save an encrypted project file.
290+
291+
"job_ttl" (optional) - Determines the number of seconds a job instance will exist following completion.
292+
293+
RETURNS:
294+
KepServiceResponse instance with job information
295+
296+
EXCEPTIONS (If not HTTP 200 or 429 returned):
297+
298+
KepHTTPError - If urllib provides an HTTPError
299+
KepURLError - If urllib provides an URLError
300+
'''
301+
url = self.url + self.__project_services_url + '/ProjectLoad'
302+
prop_data = {'servermain.PROJECT_FILENAME': filename}
303+
if password != None: prop_data['servermain.PROJECT_PASSWORD'] = password
304+
try:
305+
job = self._kep_service_execute(url, prop_data, job_ttl)
306+
return job
307+
except Exception as err:
308+
raise err
309+
310+
239311
def service_status(self, resp: KepServiceResponse):
240312
'''Returns the status of a service job. Used to verify if a service call
241313
has completed or not.
@@ -325,11 +397,12 @@ def _force_update_check(self, force, DATA):
325397
pass
326398
return DATA
327399
# General service call handler
328-
def _kep_service_execute(self, url, TTL = None):
400+
def _kep_service_execute(self, url, DATA = None, TTL = None):
329401
try:
330402
if TTL != None:
331-
TTL = {"servermain.JOB_TIME_TO_LIVE_SECONDS": TTL}
332-
r = self._config_update(url, TTL)
403+
if DATA == None: DATA = {}
404+
DATA["servermain.JOB_TIME_TO_LIVE_SECONDS"]= TTL
405+
r = self._config_update(url, DATA)
333406
job = KepServiceResponse(r.payload['code'],r.payload['message'], r.payload['href'])
334407
return job
335408
except KepHTTPError as err:

kepconfig/connectivity/device.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ def auto_tag_gen(server, device_path, job_ttl = None) -> KepServiceResponse:
180180
"device_path" - path identifying device. Standard Kepware address decimal notation string including the
181181
device such as "channel1.device1"
182182
183+
"job_ttl" (optional) - Determines the number of seconds a job instance will exist following completion.
184+
183185
RETURNS:
184186
KepServiceReturn with the result of the service either Code 202 (Accepted) or 429 (Too Busy)
185187
@@ -191,7 +193,7 @@ def auto_tag_gen(server, device_path, job_ttl = None) -> KepServiceResponse:
191193
path_obj = kepconfig.path_split(device_path)
192194
try:
193195
url = server.url +channel._create_url(path_obj['channel']) + _create_url(path_obj['device']) + ATG_URL
194-
job = server._kep_service_execute(url, job_ttl)
196+
job = server._kep_service_execute(url, None, job_ttl)
195197
return job
196198
except KeyError as err:
197199
err_msg = 'Error: No {} identified in {} | Function: {}'.format(err,'device_path', inspect.currentframe().f_code.co_name)

kepconfig/datalogger/log_group.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,5 @@ def reset_column_mapping_service(server, log_group, job_ttl = None) -> KepServic
202202
'''
203203

204204
url = server.url + _create_url(log_group) + SERVICES_ROOT + '/ResetColumnMapping'
205-
job = server._kep_service_execute(url, job_ttl)
205+
job = server._kep_service_execute(url, None, job_ttl)
206206
return job

tests/admin_test.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
# Admin Test - Test to exectute various calls for the Administrator
88
# parts of the Kepware configuration API
99

10+
from wsgiref.simple_server import server_version
1011
from kepconfig.error import KepError, KepHTTPError
1112
import os, sys
1213
from typing import Dict, List
1314
import pytest
1415
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
15-
# import kepconfig
16+
import kepconfig
1617
from kepconfig import admin
1718
import json
1819
import time
@@ -28,6 +29,8 @@
2829
twx_agent_name = 'Thingworx'
2930
iot_item_name ="System__Date"
3031

32+
33+
3134
group1 = {'common.ALLTYPES_NAME': 'Operators'}
3235
group2 = {'common.ALLTYPES_NAME': 'Group1'}
3336
group3 = {'common.ALLTYPES_NAME': 'Group2'}
@@ -89,8 +92,10 @@ def complete(server):
8992

9093

9194
@pytest.fixture(scope="module")
92-
def server(kepware_server):
93-
server = kepware_server
95+
def server(kepware_server: list[kepconfig.connection.server, str]):
96+
server = kepware_server[0]
97+
global server_type
98+
server_type = kepware_server[1]
9499

95100
# Initialize any configuration before testing in module
96101
initialize(server)
@@ -100,10 +105,7 @@ def server(kepware_server):
100105
complete(server)
101106

102107
def test_uaserver(server):
103-
try:
104-
server._config_get(server.url + admin.ua_server._create_url())
105-
except Exception as err:
106-
pytest.skip("UA Endpoints not configurable.")
108+
if server_type == 'TKS': pytest.skip("UA Endpoints not configurable in {}.".format(server_type))
107109

108110
assert admin.ua_server.add_endpoint(server,uaendpoint1)
109111

tests/conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111

1212
@pytest.fixture(scope="module")
1313
def kepware_server():
14-
return kepconfig.connection.server(host = 'localhost', port = 57412, user = 'Administrator', pw = '', https = False)
15-
# return kepconfig.connection.server(host = 'localhost', port = 57413, user = 'Administrator', pw = 'Kepware400400400', https = False)
14+
# return [kepconfig.connection.server(host = 'localhost', port = 57412, user = 'Administrator', pw = '', https = False), 'TKS']
15+
16+
server = kepconfig.connection.server(host = '127.0.0.1', port = 57513, user = 'Administrator', pw = 'Kepware400400400', https = True)
17+
server.SSL_trust_all_certs = True
18+
return [server, 'TKE']

tests/connectivity_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ def complete(server):
5959
HTTPErrorHandler(err)
6060

6161
@pytest.fixture(scope="module")
62-
def server(kepware_server):
63-
server = kepware_server
62+
def server(kepware_server: list[kepconfig.connection.server, str]):
63+
server = kepware_server[0]
64+
global server_type
65+
server_type = kepware_server[1]
6466

6567
# Initialize any configuration before testing in module
6668
initialize(server)

tests/datalogger_test.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ def HTTPErrorHandler(err):
155155
print('Different Exception Received: {}'.format(err))
156156

157157
def initialize(server: kepconfig.connection.server):
158+
if server_type == 'TKE': pytest.skip("Datalogger not configurable in {}.".format(server_type), allow_module_level=True)
159+
158160
try:
159161
server._config_get(server.url + datalogger.log_group._create_url())
160162
except Exception as err:
@@ -169,8 +171,10 @@ def complete(server):
169171
HTTPErrorHandler(err)
170172

171173
@pytest.fixture(scope="module")
172-
def server(kepware_server):
173-
server = kepware_server
174+
def server(kepware_server: list[kepconfig.connection.server, str]):
175+
server = kepware_server[0]
176+
global server_type
177+
server_type = kepware_server[1]
174178

175179
# Initialize any configuration before testing in module
176180
initialize(server)

tests/egd_test.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ def HTTPErrorHandler(err):
118118
}
119119

120120
def initialize(server: kepconfig.connection.server):
121+
if server_type == 'TKE': pytest.skip("EGD not configurable in {}.".format(server_type), allow_module_level=True)
122+
121123
try:
122124
server._config_get(server.url +"/doc/drivers/GE Ethernet Global Data/channels")
123125
except Exception as err:
@@ -135,8 +137,10 @@ def complete(server):
135137
HTTPErrorHandler(err)
136138

137139
@pytest.fixture(scope="module")
138-
def server(kepware_server):
139-
server = kepware_server
140+
def server(kepware_server: list[kepconfig.connection.server, str]):
141+
server = kepware_server[0]
142+
global server_type
143+
server_type = kepware_server[1]
140144

141145
# Initialize any configuration before testing in module
142146
initialize(server)

tests/iot_gateway_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ def complete(server):
9191
HTTPErrorHandler(err)
9292

9393
@pytest.fixture(scope="module")
94-
def server(kepware_server):
95-
server = kepware_server
94+
def server(kepware_server: list[kepconfig.connection.server, str]):
95+
server = kepware_server[0]
96+
global server_type
97+
server_type = kepware_server[1]
9698

9799
# Initialize any configuration before testing in module
98100
initialize(server)

0 commit comments

Comments
 (0)