From e55361df76bd0ef6a7c543af6882e2589066f8cb Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Thu, 29 Oct 2026 08:45:14 -0600 Subject: [PATCH] fix(secrets): restore python Secrets.getClient as deprecated alias - Add Python Secrets.getClient, which warns and calls get_client; 7.0 renamed it without an alias, breaking 6.x plugins - Add Ruby Secrets.get_client and Bucket.get_client so the snake_case names work in both languages - Document the rename under COSMOS API Changes in upgrading.md Co-Authored-By: Claude Opus 5.5 --- docs.openc3.com/docs/getting-started/upgrading.md | 10 ++++++++++ openc3/lib/openc3/utilities/bucket.rb | 5 +++++ openc3/lib/openc3/utilities/secrets.rb | 5 +++++ openc3/python/openc3/utilities/secrets.py | 9 +++++++++ openc3/python/test/utilities/test_secrets.py | 10 ++++++++++ openc3/spec/utilities/bucket_spec.rb | 8 ++++++++ openc3/spec/utilities/secrets_spec.rb | 8 ++++++++ 7 files changed, 55 insertions(+) diff --git a/docs.openc3.com/docs/getting-started/upgrading.md b/docs.openc3.com/docs/getting-started/upgrading.md index b847895788..30ed4d4a01 100644 --- a/docs.openc3.com/docs/getting-started/upgrading.md +++ b/docs.openc3.com/docs/getting-started/upgrading.md @@ -373,6 +373,16 @@ The following target configuration keywords are silently ignored during parsing - **`CMD_DECOM_RETAIN_TIME`** — Sets the retention time for command decommutation data in QuestDB. Accepts a time value with a unit suffix: `h` (hours), `d` (days), `w` (weeks), `M` (months), or `y` (years). Example: `CMD_DECOM_RETAIN_TIME 30d` - **`TLM_DECOM_RETAIN_TIME`** — Sets the retention time for telemetry decommutation data in QuestDB. Same format as above. Example: `TLM_DECOM_RETAIN_TIME 1y` +### COSMOS API Changes + +#### Python `Secrets.getClient()` renamed to `Secrets.get_client()` + +COSMOS 7.0 renamed the Python `Secrets.getClient()` class method to `Secrets.get_client()` without keeping the old name. In COSMOS 7.0 through 7.4, any Python plugin, interface, or microservice that calls `Secrets.getClient()` fails with an `AttributeError`. Releases starting with 7.5 restore `Secrets.getClient()` as a deprecated alias that logs a `DeprecationWarning`, matching the existing `Bucket.getClient()` alias. + +Update Python code to call `Secrets.get_client()`. Ruby code is unaffected: `Secrets.getClient` and `Bucket.getClient` still work, and starting with 7.5 Ruby also accepts `Secrets.get_client` and `Bucket.get_client` so the same names work in both languages. + +If you've updated your plugin but still see errors about `getClient`, an older copy of your plugin code is probably still being loaded. Check the Admin Console Plugins tab for a previous version of the plugin that is still installed, and check the `shared` Python packages in the Admin Console Packages tab for an old copy of your plugin's Python package that COSMOS 6 installed. + ### Troubleshooting #### Init Container Failure diff --git a/openc3/lib/openc3/utilities/bucket.rb b/openc3/lib/openc3/utilities/bucket.rb index 186d7a9582..c4f91fad59 100644 --- a/openc3/lib/openc3/utilities/bucket.rb +++ b/openc3/lib/openc3/utilities/bucket.rb @@ -28,6 +28,11 @@ def self.getClient klass.new end + # snake_case alias to match the Python Bucket.get_client + def self.get_client # rubocop:disable Naming/AccessorMethodName + getClient() + end + def initialize # Setup the client instance end diff --git a/openc3/lib/openc3/utilities/secrets.rb b/openc3/lib/openc3/utilities/secrets.rb index a577534755..e5f894d586 100644 --- a/openc3/lib/openc3/utilities/secrets.rb +++ b/openc3/lib/openc3/utilities/secrets.rb @@ -71,6 +71,11 @@ def self.getClient klass.new end + # snake_case alias to match the Python Secrets.get_client + def self.get_client # rubocop:disable Naming/AccessorMethodName + getClient() + end + def keys(secret_store: nil, scope:) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end diff --git a/openc3/python/openc3/utilities/secrets.py b/openc3/python/openc3/utilities/secrets.py index 7f617efc3c..69210e5499 100644 --- a/openc3/python/openc3/utilities/secrets.py +++ b/openc3/python/openc3/utilities/secrets.py @@ -10,6 +10,7 @@ # if purchased from OpenC3, Inc. import os +import warnings from openc3.top_level import get_class_from_module from openc3.utilities.string import filename_to_class_name @@ -78,6 +79,14 @@ def get_client(cls): ) return klass() + @classmethod + def getClient(cls): # noqa: N802 + """Deprecated: Use get_client() instead. This camelCase method is provided for backwards compatibility.""" + warnings.warn( + "Secrets.getClient() is deprecated, use Secrets.get_client() instead", DeprecationWarning, stacklevel=2 + ) + return cls.get_client() + def keys(self, secret_store=None, scope=None): raise RuntimeError(f"{self.__class__.__name__} has not implemented method 'keys'") diff --git a/openc3/python/test/utilities/test_secrets.py b/openc3/python/test/utilities/test_secrets.py index 7ed231505a..4cf4443177 100644 --- a/openc3/python/test/utilities/test_secrets.py +++ b/openc3/python/test/utilities/test_secrets.py @@ -138,3 +138,13 @@ def test_setup_does_not_read_file_secrets_outside_the_secret_file_dir(self): for path in ["/etc/passwd", "/tmp/../etc/passwd"]: with self.assertRaisesRegex(ValueError, "must be under"): secrets.setup([["FILE", "KEY", path]]) + + def test_get_client_returns_the_configured_backend(self): + with patch.dict(os.environ, {"OPENC3_SECRET_BACKEND": "redis"}): + self.assertEqual(Secrets.get_client().__class__.__name__, "RedisSecrets") + + def test_get_client_camel_case_is_a_deprecated_alias(self): + with patch.object(Secrets, "get_client", return_value="client") as get_client: + with self.assertWarnsRegex(DeprecationWarning, r"use Secrets\.get_client\(\)"): + self.assertEqual(Secrets.getClient(), "client") + get_client.assert_called_once() diff --git a/openc3/spec/utilities/bucket_spec.rb b/openc3/spec/utilities/bucket_spec.rb index 396eac757d..13e6227291 100644 --- a/openc3/spec/utilities/bucket_spec.rb +++ b/openc3/spec/utilities/bucket_spec.rb @@ -30,5 +30,13 @@ module OpenC3 expect(client).to be_a LocalBucket end end + + describe "get_client" do + it "is an alias for getClient" do + ENV['OPENC3_CLOUD'] = 'AWS' + client = Bucket.get_client + expect(client).to be_a AwsBucket + end + end end end diff --git a/openc3/spec/utilities/secrets_spec.rb b/openc3/spec/utilities/secrets_spec.rb index 8caa7d6aa7..3375819e8c 100644 --- a/openc3/spec/utilities/secrets_spec.rb +++ b/openc3/spec/utilities/secrets_spec.rb @@ -113,6 +113,14 @@ module OpenC3 end end + describe "get_client" do + it "is an alias for getClient" do + client = double("client") + expect(Secrets).to receive(:getClient).and_return(client) + expect(Secrets.get_client).to be client + end + end + describe "setup" do it "requires at least 3 items" do expect { Secrets.new.setup([['ENV', 'KEY']]) }.to raise_error(ArgumentError, /at least 3 items/)