Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Python APIs #877

Merged
merged 7 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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