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

Create table APIs #1850

Merged
merged 3 commits into from
Jan 28, 2025
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
68 changes: 68 additions & 0 deletions docs.openc3.com/docs/guides/scripting-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2936,6 +2936,74 @@ Python Example:
router_protocol_cmd("INST", "DISABLE_CRC", read_write='READ_WRITE', index=-1)
```

## Tables

These methods allow the user to script Table Manager.

### table_create_binary

Creates a table binary based on a table definition file. You can achieve the same result in the Table Manager GUI with File->New File. Returns the path to the binary file created.

Ruby / Python Syntax:

```ruby
table_create_binary(<Table Definition File>)
```

| Parameter | Description |
| --------------------- | ------------------------------------------------------------------------------- |
| Table Definition File | Path to the table definition file, e.g. INST/tables/config/ConfigTables_def.txt |

Ruby Example:

```ruby
table = table_create_binary("INST/tables/config/ConfigTables_def.txt") #=>
# {"filename"=>"INST/tables/bin/ConfigTables.bin"}
```

Python Example:

```python
table = table_create_binary("INST/tables/config/ConfigTables_def.txt") #=>
# {'filename': 'INST/tables/bin/ConfigTables.bin'}
```

### table_create_report

Creates a table binary based on a table definition file. You can achieve the same result in the Table Manager GUI with File->New File. Returns the path to the binary file created.

Ruby / Python Syntax:

```ruby
table_create_report(<Table Binary Filename>, <Table Definition File>, <Table Name (optional)>)
```

filename, definition, table_name

| Parameter | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Table Binary File | Path to the table binary file, e.g. INST/tables/bin/ConfigTables.bin |
| Table Definition File | Path to the table definition file, e.g. INST/tables/config/ConfigTables_def.txt |
| Table Name | Name of the table to create the report. This only applies if the Table Binary and Table Definition consist of multiple tables. By default the report consists of all tables and is named after the binary file. If the table name is given, the report is just the specified table and is named after the table. |

Ruby Example:

```ruby
table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt") #=>
# {"filename"=>"INST/tables/bin/ConfigTables.csv", "contents"=>"MC_CONFIGURATION\nLabel, ...
table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt", table_name: "MC_CONFIGURATION") #=>
# {"filename"=>"INST/tables/bin/McConfiguration.csv", "contents"=>"MC_CONFIGURATION\nLabel, ...
```

Python Example:

```python
table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt") #=>
# {'filename': 'INST/tables/bin/ConfigTables.csv', 'contents': 'MC_CONFIGURATION\nLabel, ...
table = table_create_report("INST/tables/bin/ConfigTables.bin", "INST/tables/config/ConfigTables_def.txt", table_name="MC_CONFIGURATION") #=>
# {'filename': 'INST/tables/bin/ConfigTables.csv', 'contents': 'MC_CONFIGURATION\nLabel, ...
```

## Stashing Data

These methods allow the user to store temporary data into COSMOS and retrieve it. The storage is implemented as a key / value storage (Ruby hash or Python dict). This can be used in scripts to store information that applies across multiple scripts or multiple runs of a single script.
Expand Down
12 changes: 6 additions & 6 deletions openc3-cosmos-cmd-tlm-api/app/controllers/tables_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def index

def binary
return unless authorization('system')
scope, binary, definition, table = sanitize_params([:scope, :binary, :definition, :table], require_params: false, allow_forward_slash: true)
scope, binary, definition, table_name = sanitize_params([:scope, :binary, :definition, :table_name], require_params: false, allow_forward_slash: true)
return unless scope
begin
file = Table.binary(scope, binary, definition, table)
file = Table.binary(scope, binary, definition, table_name)
results = { filename: file.filename, contents: Base64.encode64(file.contents) }
render json: results
rescue Table::NotFound => e
Expand All @@ -47,10 +47,10 @@ def binary

def definition
return unless authorization('system')
scope, definition, table = sanitize_params([:scope, :definition, :table], require_params: false, allow_forward_slash: true)
scope, definition, table_name = sanitize_params([:scope, :definition, :table_name], require_params: false, allow_forward_slash: true)
return unless scope
begin
file = Table.definition(scope, definition, table)
file = Table.definition(scope, definition, table_name)
render json: { filename: file.filename, contents: file.contents }
rescue Table::NotFound => e
log_error(e)
Expand All @@ -60,10 +60,10 @@ def definition

def report
return unless authorization('system')
scope, binary, definition, table = sanitize_params([:scope, :binary, :definition, :table], require_params: false, allow_forward_slash: true)
scope, binary, definition, table_name = sanitize_params([:scope, :binary, :definition, :table_name], require_params: false, allow_forward_slash: true)
return unless scope
begin
file = Table.report(scope, binary, definition, table)
file = Table.report(scope, binary, definition, table_name)
render json: { filename: file.filename, contents: file.contents }
rescue Table::NotFound => e
log_error(e)
Expand Down
6 changes: 3 additions & 3 deletions openc3-cosmos-cmd-tlm-api/app/models/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ def self.report(scope, binary_filename, definition_filename, table_name = nil)
# Convert the typical table naming convention of all caps with underscores
# to the typical binary convention of camelcase, e.g. MC_CONFIG => McConfig.bin
filename = table_name.split('_').map { |part| part.capitalize }.join()
report.filename = "#{filename}.csv"
report.filename = "#{File.dirname(binary_filename)}/#{filename}.csv"
else
report.filename = File.basename(binary_filename).sub('.bin', '.csv')
report.filename = binary_filename.sub('.bin', '.csv')
end
report.contents = OpenC3::TableManagerCore.report(binary, root_definition, table_name)
create(scope, binary_filename.sub('.bin', '.csv'), report.contents)
create(scope, report.filename, report.contents)
return report
end

Expand Down
6 changes: 3 additions & 3 deletions openc3-cosmos-cmd-tlm-api/spec/models/table_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def add_table(table_name)
@get_object.body.read = 'definition'
allow(OpenC3::TableManagerCore).to receive(:report).and_return('report')
file = Table.report('DEFAULT', 'INST/tables/bin/table.bin', 'INST/tables/config/table_def.txt')
expect(file.filename).to eql 'table.csv'
expect(file.filename).to eql 'INST/tables/bin/table.csv'
expect(file.contents).to eql 'report'
expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/table.csv']).to eql "report"
end
Expand All @@ -202,9 +202,9 @@ def add_table(table_name)
Dir.mkdir(tmp_dir) unless File.exist?(tmp_dir)
allow(Dir).to receive(:mktmpdir).and_return(tmp_dir)
file = Table.report('DEFAULT', 'INST/tables/bin/table.bin', 'INST/tables/config/table_def.txt', 'MY_TABLE')
expect(file.filename).to eql 'MyTable.csv'
expect(file.filename).to eql 'INST/tables/bin/MyTable.csv'
expect(file.contents).to eql 'report' # Check the simple stub
expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/table.csv']).to eql "report"
expect(@put_object['DEFAULT/targets_modified/INST/tables/bin/MyTable.csv']).to eql "report"
# Real test is did we create the definition files
files = Dir.glob("#{tmp_dir}/**/*").map {|file| File.basename(file) }
expect(files).to include('table_def.txt')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ export default {
formData.append('binary', this.filename)
formData.append('definition', this.definitionFilename)
if (tableName !== null) {
formData.append('table', tableName)
formData.append('table_name', tableName)
}
Api.post(`/openc3-api/tables/binary`, {
data: formData,
Expand Down Expand Up @@ -553,7 +553,7 @@ export default {
const formData = new FormData()
formData.append('definition', this.definitionFilename)
if (tableName !== null) {
formData.append('table', tableName)
formData.append('table_name', tableName)
}
Api.post(`/openc3-api/tables/definition`, {
data: formData,
Expand All @@ -574,7 +574,7 @@ export default {
formData.append('binary', this.filename)
formData.append('definition', this.definitionFilename)
if (tableName !== null) {
formData.append('table', tableName)
formData.append('table_name', tableName)
}
Api.post(`/openc3-api/tables/report`, {
data: formData,
Expand Down
17 changes: 11 additions & 6 deletions openc3/lib/openc3/script/script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,23 @@
require 'openc3/io/json_drb_object'
require 'openc3/script/api_shared'
require 'openc3/script/calendar'
require 'openc3/script/metadata'
require 'openc3/script/commands'
require 'openc3/script/telemetry'
require 'openc3/script/limits'
require 'openc3/script/critical_cmd'
require 'openc3/script/exceptions'
# openc3/script/extract is just helper methods
require 'openc3/script/limits'
require 'openc3/script/metadata'
require 'openc3/script/packages'
require 'openc3/script/plugins'
require 'openc3/script/screen'
require 'openc3/script/script_runner'
require 'openc3/script/storage'
# openc3/script/suite_results and suite_runner are used by
# running_script.rb and the script_runner_api
# openc3/script/suite is used by end user SR Suites
require 'openc3/script/tables'
require 'openc3/script/telemetry'
require 'openc3/script/web_socket_api'
require 'openc3/script/packages'
require 'openc3/script/plugins'
require 'openc3/script/critical_cmd'
require 'openc3/utilities/authentication'

$api_server = nil
Expand Down
49 changes: 49 additions & 0 deletions openc3/lib/openc3/script/tables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# encoding: ascii-8bit

# Copyright 2025 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.

module OpenC3
module Script
private

def table_create_binary(definition, scope: $openc3_scope)
post_data = {}
post_data['definition'] = definition
response = $api_server.request('post', '/openc3-api/tables/generate', json: true, data: post_data, scope: scope)
return _handle_response(response, 'Failed to create binary')
end

def table_create_report(filename, definition, table_name: nil, scope: $openc3_scope)
post_data = {}
post_data['binary'] = filename
post_data['definition'] = definition
post_data['table_name'] = table_name if table_name
response = $api_server.request('post', '/openc3-api/tables/report', json: true, data: post_data, scope: scope)
return _handle_response(response, 'Failed to create report')
end

# Helper method to handle the response
def _handle_response(response, error_message)
return nil if response.nil?
if response.status >= 400
result = JSON.parse(response.body, :allow_nan => true, :create_additions => true)
raise "#{error_message} due to #{result['message']}"
end
return JSON.parse(response.body, :allow_nan => true, :create_additions => true)
end
end
end
7 changes: 4 additions & 3 deletions openc3/python/openc3/script/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,17 @@ def prompt(
###########################################################################

from .api_shared import *
from .cosmos_calendar import *
from .commands import *
from .cosmos_calendar import *
from .critical_cmd import *
from .exceptions import *
from .limits import *
from .telemetry import *
from .metadata import *
from .screen import *
from .script_runner import *
from .storage import *
from .critical_cmd import *
from .tables import *
from .telemetry import *
from openc3.api import WHITELIST


Expand Down
47 changes: 47 additions & 0 deletions openc3/python/openc3/script/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2025 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
import openc3.script
from openc3.environment import OPENC3_SCOPE

def table_create_binary(definition: str, scope: str = OPENC3_SCOPE):
data = {}
data['definition'] = definition
response = openc3.script.API_SERVER.request("post", "/openc3-api/tables/generate", data=data, json=True, scope=scope)
return _handle_response(response, 'Failed to create binary')

def table_create_report(filename: str, definition: str, table_name: str = None, scope: str = OPENC3_SCOPE):
data = {}
data['binary'] = filename
data['definition'] = definition
if table_name:
data['table_name'] = table_name
response = openc3.script.API_SERVER.request("post", "/openc3-api/tables/report", data=data, json=True, scope=scope)
return _handle_response(response, 'Failed to create binary')

# Helper method to handle the response
def _handle_response(response, error_message):
if response is None:
return None
if response.status_code >= 400:
result = json.loads(response.text)
raise RuntimeError(f"{error_message} due to {result['message']}")
# Not sure why the response body is empty (on delete) but check for that
if response.text is None or len(response.text) == 0:
return None
else:
return json.loads(response.text)
Loading