Skip to content

Commit

Permalink
Merge pull request #1850 from OpenC3/tbl_mgr_api
Browse files Browse the repository at this point in the history
Create table APIs
  • Loading branch information
jmthomas authored Jan 28, 2025
2 parents 1467fc9 + d2dd3b3 commit b2797b6
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 24 deletions.
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)

0 comments on commit b2797b6

Please sign in to comment.