Skip to content

Commit

Permalink
Merge pull request #877 from OpenC3/python_apis
Browse files Browse the repository at this point in the history
Additional Python APIs
  • Loading branch information
jmthomas authored Oct 21, 2023
2 parents 6a445cb + 8d68848 commit be95d4d
Show file tree
Hide file tree
Showing 51 changed files with 1,944 additions and 635 deletions.
10 changes: 2 additions & 8 deletions openc3/lib/openc3/api/config_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
# GNU Affero General Public License for more details.

# Modified by OpenC3, Inc.
# All changes Copyright 2022, OpenC3, Inc.
# All changes Copyright 2023, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.

require 'openc3/models/tool_config_model'
Expand All @@ -26,18 +26,12 @@ module OpenC3
module Api
WHITELIST ||= []
WHITELIST.concat([
'get_saved_config',
'list_configs',
'load_config',
'save_config',
'delete_config'
])

# Get a saved configuration zip file
def get_saved_config(configuration_name = nil, scope: $openc3_scope, token: $openc3_token)
raise "Not supported by OpenC3 5"
end

def list_configs(tool, scope: $openc3_scope, token: $openc3_token)
authorize(permission: 'system', scope: scope, token: token)
ToolConfigModel.list_configs(tool, scope: scope)
Expand Down
38 changes: 28 additions & 10 deletions openc3/lib/openc3/script/api_shared.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# GNU Affero General Public License for more details.

# Modified by OpenC3, Inc.
# All changes Copyright 2022, OpenC3, Inc.
# All changes Copyright 2023, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
Expand Down Expand Up @@ -306,7 +306,10 @@ def wait_check(*args, type: :CONVERTED, scope: $openc3_scope, token: $openc3_tok
success, value = _openc3_script_wait_implementation_comparison(target_name, packet_name, item_name, type, comparison_to_eval, timeout, polling_rate, scope: scope, token: token, &block)
value = "'#{value}'" if value.is_a? String # Show user the check against a quoted string
time_diff = Time.now.sys - start_time
check_str = "CHECK: #{_upcase(target_name, packet_name, item_name)} #{comparison_to_eval}"
check_str = "CHECK: #{_upcase(target_name, packet_name, item_name)}"
if comparison_to_eval
check_str += " #{comparison_to_eval}"
end
with_value_str = "with value == #{value} after waiting #{time_diff} seconds"
if success
Logger.info "#{check_str} success #{with_value_str}"
Expand Down Expand Up @@ -545,6 +548,10 @@ def _check_process_args(args, method_name)
case args.length
when 1
target_name, packet_name, item_name, comparison_to_eval = extract_fields_from_check_text(args[0])
when 3
target_name = args[0]
packet_name = args[1]
item_name = args[2]
when 4
target_name = args[0]
packet_name = args[1]
Expand All @@ -554,7 +561,9 @@ def _check_process_args(args, method_name)
# Invalid number of arguments
raise "ERROR: Invalid number of arguments (#{args.length}) passed to #{method_name}()"
end
raise "Invalid comparison to non-ascii value" unless comparison_to_eval.is_printable?
if comparison_to_eval and !comparison_to_eval.is_printable?
raise "ERROR: Invalid comparison to non-ascii value"
end
return [target_name, packet_name, item_name, comparison_to_eval]
end

Expand Down Expand Up @@ -732,8 +741,9 @@ def _wait_check_process_args(args)

def _openc3_script_wait_implementation(target_name, packet_name, item_name, value_type, timeout, polling_rate, exp_to_eval, scope: $openc3_scope, token: $openc3_token, &block)
end_time = Time.now.sys + timeout
raise "Invalid comparison to non-ascii value" unless exp_to_eval.is_printable?

if exp_to_eval and !exp_to_eval.is_printable?
raise "ERROR: Invalid comparison to non-ascii value"
end
while true
work_start = Time.now.sys
value = tlm(target_name, packet_name, item_name, type: value_type, scope: scope, token: token)
Expand Down Expand Up @@ -762,15 +772,23 @@ def _openc3_script_wait_implementation(target_name, packet_name, item_name, valu

if canceled
value = tlm(target_name, packet_name, item_name, type: value_type, scope: scope, token: token)
begin
if eval(exp_to_eval)
if not block.nil?
if block.call(value)
return true, value
else
return false, value
end
# NoMethodError is raised when the tlm() returns nil and we try to eval the expression
rescue NoMethodError
return false, value
else
begin
if eval(exp_to_eval)
return true, value
else
return false, value
end
# NoMethodError is raised when the tlm() returns nil and we try to eval the expression
rescue NoMethodError
return false, value
end
end
end
end
Expand Down
43 changes: 43 additions & 0 deletions openc3/python/openc3/api/config_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 OpenC3, Inc
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.:

import json
from openc3.api import WHITELIST
from openc3.environment import OPENC3_SCOPE
from openc3.utilities.authorization import authorize
from openc3.models.tool_config_model import ToolConfigModel

WHITELIST.extend(["list_configs", "load_config", "save_config", "delete_config"])


def list_configs(tool, scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
return ToolConfigModel.list_configs(tool, scope=scope)


def load_config(tool, name, scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
return json.loads(ToolConfigModel.load_config(tool, name, scope=scope))


def save_config(tool, name, data, local_mode=True, scope=OPENC3_SCOPE):
authorize(permission="system_set", scope=scope)
ToolConfigModel.save_config(tool, name, data, local_mode, scope)


def delete_config(tool, name, local_mode=True, scope=OPENC3_SCOPE):
authorize(permission="system_set", scope=scope)
ToolConfigModel.delete_config(tool, name, local_mode, scope)
59 changes: 59 additions & 0 deletions openc3/python/openc3/api/settings_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2023 OpenC3, Inc
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.:

from openc3.api import WHITELIST
from openc3.environment import OPENC3_SCOPE
from openc3.utilities.authorization import authorize
from openc3.models.setting_model import SettingModel
from openc3.utilities.local_mode import LocalMode

WHITELIST.extend(
["list_settings", "get_all_settings", "get_setting", "get_settings", "save_setting"]
)


def list_settings(scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
return SettingModel.names(scope=scope)


def get_all_settings(scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
return SettingModel.all(scope=scope)


def get_setting(name, scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
setting = SettingModel.get(name=name, scope=scope)
if setting:
return setting["data"]
else:
return None


def get_settings(*settings, scope=OPENC3_SCOPE):
authorize(permission="system", scope=scope)
result = []
for name in settings:
result.append(get_setting(name, scope))
return result


def save_setting(name, data, local_mode=True, scope=OPENC3_SCOPE):
authorize(permission="admin", scope=scope)
SettingModel.set({"name": name, "data": data}, scope=scope)
if local_mode:
LocalMode.save_setting(scope, name, data)
63 changes: 31 additions & 32 deletions openc3/python/openc3/logs/log_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,38 +317,37 @@ def close_file(self, take_mutex=True):
threads = []
if take_mutex:
self.mutex.acquire()
# try:
if self.file:
# try:
self.file.close()
Logger.debug(f"Log File Closed : {self.filename}")
date = self.first_timestamp()[0:8] # YYYYMMDD
bucket_key = os.path.join(
self.remote_log_directory, date, self.bucket_filename()
)
# Cleanup timestamps here so they are unset for the next file
self.first_time = None
self.last_time = None
threads.append(
BucketUtilities.move_log_file_to_bucket(self.filename, bucket_key)
)
# Now that the file is in storage, trim the Redis stream after a delay
self.cleanup_offsets.append({})
for redis_topic, last_offset in self.last_offsets:
self.cleanup_offsets[-1][redis_topic] = last_offset
self.cleanup_times.append(
datetime.now(timezone.utc) + timedelta(seconds=LogWriter.CLEANUP_DELAY)
)
self.last_offsets.clear
# except RuntimeError as error:
# Logger.error(f"Error closing {self.filename} : {repr(error)}")

self.file = None
self.file_size = 0
self.filename = None
# except:
# if take_mutex:
# self.mutex.release()
try:
if self.file:
self.file.close()
Logger.debug(f"Log File Closed : {self.filename}")
date = self.first_timestamp()[0:8] # YYYYMMDD
bucket_key = os.path.join(
self.remote_log_directory, date, self.bucket_filename()
)
# Cleanup timestamps here so they are unset for the next file
self.first_time = None
self.last_time = None
threads.append(
BucketUtilities.move_log_file_to_bucket(self.filename, bucket_key)
)
# Now that the file is in storage, trim the Redis stream after a delay
self.cleanup_offsets.append({})
for redis_topic, last_offset in self.last_offsets:
self.cleanup_offsets[-1][redis_topic] = last_offset
self.cleanup_times.append(
datetime.now(timezone.utc)
+ timedelta(seconds=LogWriter.CLEANUP_DELAY)
)
self.last_offsets.clear
self.file = None
self.file_size = 0
self.filename = None
except RuntimeError as error:
Logger.error(f"Error closing {self.filename} : {repr(error)}")
finally:
if take_mutex:
self.mutex.release()
return threads

def bucket_filename(self):
Expand Down
49 changes: 49 additions & 0 deletions openc3/python/openc3/models/setting_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2023 OpenC3, Inc.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.

from openc3.models.model import Model


class SettingModel(Model):
PRIMARY_KEY = "openc3__settings"

# NOTE: The following three class methods are used by the ModelController
# and are reimplemented to enable various Model class methods to work
@classmethod
def get(cls, name, scope=None):
return super().get(SettingModel.PRIMARY_KEY, name=name)

@classmethod
def names(cls, scope=None):
return super().names(SettingModel.PRIMARY_KEY)

@classmethod
def all(cls, scope=None):
return super().all(SettingModel.PRIMARY_KEY)

# END NOTE

def __init__(self, name, data, scope):
super().__init__(SettingModel.PRIMARY_KEY, name=name, scope=scope)
self.data = data

# @return [Hash] JSON encoding of this model
def as_json(self):
return {
"name": self.name,
"data": self.data,
"updated_at": self.updated_at,
}
42 changes: 42 additions & 0 deletions openc3/python/openc3/models/tool_config_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2023 OpenC3, Inc.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.

from openc3.utilities.store import Store
from openc3.utilities.local_mode import LocalMode
from openc3.environment import OPENC3_SCOPE


class ToolConfigModel:
@classmethod
def list_configs(cls, tool, scope=OPENC3_SCOPE):
keys = Store.hkeys(f"{scope}__config__{tool}")
return [key.decode() for key in keys]

@classmethod
def load_config(cls, tool, name, scope=OPENC3_SCOPE):
return Store.hget(f"{scope}__config__{tool}", name).decode()

@classmethod
def save_config(cls, tool, name, data, local_mode=True, scope=OPENC3_SCOPE):
Store.hset(f"{scope}__config__{tool}", name, data)
if local_mode:
LocalMode.save_tool_config(scope, tool, name, data)

@classmethod
def delete_config(cls, tool, name, local_mode=True, scope=OPENC3_SCOPE):
Store.hdel(f"{scope}__config__{tool}", name)
if local_mode:
LocalMode.delete_tool_config(scope, tool, name)
Loading

0 comments on commit be95d4d

Please sign in to comment.