From 183450ba0cf29f0f57486832a5ef1e77e5099206 Mon Sep 17 00:00:00 2001 From: stoicAI1776 Date: Tue, 31 Mar 2026 18:47:07 +0530 Subject: [PATCH 1/4] Add PyObject headers overload for SubscriptionDataSource --- Common/Data/SubscriptionDataSource.cs | 38 ++++++++++++++++++ .../Data/SubscriptionDataSourceTests.cs | 40 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/Common/Data/SubscriptionDataSource.cs b/Common/Data/SubscriptionDataSource.cs index 12d9a883ffd8..bedb2b75a7d6 100644 --- a/Common/Data/SubscriptionDataSource.cs +++ b/Common/Data/SubscriptionDataSource.cs @@ -16,6 +16,7 @@ using System; using System.Linq; using System.Collections.Generic; +using Python.Runtime; using static QuantConnect.StringExtensions; namespace QuantConnect.Data @@ -99,6 +100,43 @@ public SubscriptionDataSource(string source, SubscriptionTransportMedium transpo Headers = headers?.ToList() ?? _empty; } + /// + /// Initializes a new instance of the class with + /// including the specified header values as a Python dictionary. + /// + /// The subscription's data source location + /// The transport medium to be used to retrieve the subscription's data from the source + /// The format of the data within the source + /// The Python dictionary containing the headers to be used for this source + public SubscriptionDataSource(string source, SubscriptionTransportMedium transportMedium, FileFormat format, PyObject headers) + : this(source, transportMedium, format, ConvertHeaders(headers)) + { + } + + private static IEnumerable> ConvertHeaders(PyObject headers) + { + if (headers == null) + { + return null; + } + + var convertedHeaders = new List>(); + using (Py.GIL()) + { + if (!PyDict.IsDictType(headers)) + { + throw new ArgumentException($"SubscriptionDataSource(): Invalid argument. {headers.Repr()} is not a dict"); + } + + using var iterator = headers.GetIterator(); + foreach (PyObject pyKey in iterator) + { + convertedHeaders.Add(new KeyValuePair(pyKey.As(), headers.GetItem(pyKey).As())); + } + } + return convertedHeaders; + } + /// /// Indicates whether the current object is equal to another object of the same type. /// diff --git a/Tests/Common/Data/SubscriptionDataSourceTests.cs b/Tests/Common/Data/SubscriptionDataSourceTests.cs index 6dbb2f6890c3..81784f359a0a 100644 --- a/Tests/Common/Data/SubscriptionDataSourceTests.cs +++ b/Tests/Common/Data/SubscriptionDataSourceTests.cs @@ -14,7 +14,10 @@ */ using NUnit.Framework; +using Python.Runtime; using QuantConnect.Data; +using System; +using System.Collections.Generic; namespace QuantConnect.Tests.Common.Data { @@ -47,5 +50,42 @@ public void ComparesNotEqualWithDifferentTransportMedium() Assert.IsTrue(one != two); Assert.IsTrue(!one.Equals(two)); } + + [Test] + public void SupportsPythonDictionaryHeaders() + { + using (Py.GIL()) + { + using var headers = new PyDict(); + headers.SetItem("Authorization".ToPython(), "Basic test-token".ToPython()); + headers.SetItem("X-Api-Key".ToPython(), "abc123".ToPython()); + + var dataSource = new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, headers); + CollectionAssert.AreEquivalent(new[] + { + new KeyValuePair("Authorization", "Basic test-token"), + new KeyValuePair("X-Api-Key", "abc123") + }, dataSource.Headers); + } + } + + [Test] + public void SupportsNullPythonDictionaryHeaders() + { + var dataSource = new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, (PyObject)null); + Assert.IsEmpty(dataSource.Headers); + } + + [Test] + public void ThrowsForInvalidPythonHeadersType() + { + using (Py.GIL()) + using var invalidHeaders = "invalid-headers".ToPython(); + + var exception = Assert.Throws(() => + new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, invalidHeaders)); + + StringAssert.Contains("is not a dict", exception.Message); + } } } From 4d1748c6eee3f869ce9287ff70cae761336eaf51 Mon Sep 17 00:00:00 2001 From: stoicAI1776 Date: Tue, 31 Mar 2026 18:58:13 +0530 Subject: [PATCH 2/4] Fix ambiguous constructor chaining in SubscriptionDataSource --- Common/Data/SubscriptionDataSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Data/SubscriptionDataSource.cs b/Common/Data/SubscriptionDataSource.cs index bedb2b75a7d6..739c8100c362 100644 --- a/Common/Data/SubscriptionDataSource.cs +++ b/Common/Data/SubscriptionDataSource.cs @@ -80,7 +80,7 @@ public SubscriptionDataSource(string source, SubscriptionTransportMedium transpo /// The transport medium to be used to retrieve the subscription's data from the source /// The format of the data within the source public SubscriptionDataSource(string source, SubscriptionTransportMedium transportMedium, FileFormat format) - : this(source, transportMedium, format, null) + : this(source, transportMedium, format, (IEnumerable>)null) { } From c1b5d6f0bd2c2084e2b9c177c9f55aafd5248bba Mon Sep 17 00:00:00 2001 From: stoicAI1776 Date: Tue, 31 Mar 2026 19:09:25 +0530 Subject: [PATCH 3/4] Fix test using-scope for invalid python headers case --- Tests/Common/Data/SubscriptionDataSourceTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/Common/Data/SubscriptionDataSourceTests.cs b/Tests/Common/Data/SubscriptionDataSourceTests.cs index 81784f359a0a..9fe44cc846e3 100644 --- a/Tests/Common/Data/SubscriptionDataSourceTests.cs +++ b/Tests/Common/Data/SubscriptionDataSourceTests.cs @@ -80,12 +80,14 @@ public void SupportsNullPythonDictionaryHeaders() public void ThrowsForInvalidPythonHeadersType() { using (Py.GIL()) - using var invalidHeaders = "invalid-headers".ToPython(); + { + using var invalidHeaders = "invalid-headers".ToPython(); - var exception = Assert.Throws(() => - new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, invalidHeaders)); + var exception = Assert.Throws(() => + new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, invalidHeaders)); - StringAssert.Contains("is not a dict", exception.Message); + StringAssert.Contains("is not a dict", exception.Message); + } } } } From d9548eb58dc766267161ae52c1d768adf1d3a962 Mon Sep 17 00:00:00 2001 From: stoicAI1776 Date: Tue, 31 Mar 2026 19:17:14 +0530 Subject: [PATCH 4/4] Reuse ConvertToDictionary for PyObject headers --- Common/Data/SubscriptionDataSource.cs | 26 +------------------ .../Data/SubscriptionDataSourceTests.cs | 2 +- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/Common/Data/SubscriptionDataSource.cs b/Common/Data/SubscriptionDataSource.cs index 739c8100c362..2e7daf580d60 100644 --- a/Common/Data/SubscriptionDataSource.cs +++ b/Common/Data/SubscriptionDataSource.cs @@ -109,34 +109,10 @@ public SubscriptionDataSource(string source, SubscriptionTransportMedium transpo /// The format of the data within the source /// The Python dictionary containing the headers to be used for this source public SubscriptionDataSource(string source, SubscriptionTransportMedium transportMedium, FileFormat format, PyObject headers) - : this(source, transportMedium, format, ConvertHeaders(headers)) + : this(source, transportMedium, format, headers == null ? null : headers.ConvertToDictionary()) { } - private static IEnumerable> ConvertHeaders(PyObject headers) - { - if (headers == null) - { - return null; - } - - var convertedHeaders = new List>(); - using (Py.GIL()) - { - if (!PyDict.IsDictType(headers)) - { - throw new ArgumentException($"SubscriptionDataSource(): Invalid argument. {headers.Repr()} is not a dict"); - } - - using var iterator = headers.GetIterator(); - foreach (PyObject pyKey in iterator) - { - convertedHeaders.Add(new KeyValuePair(pyKey.As(), headers.GetItem(pyKey).As())); - } - } - return convertedHeaders; - } - /// /// Indicates whether the current object is equal to another object of the same type. /// diff --git a/Tests/Common/Data/SubscriptionDataSourceTests.cs b/Tests/Common/Data/SubscriptionDataSourceTests.cs index 9fe44cc846e3..5ed9deda7c1a 100644 --- a/Tests/Common/Data/SubscriptionDataSourceTests.cs +++ b/Tests/Common/Data/SubscriptionDataSourceTests.cs @@ -86,7 +86,7 @@ public void ThrowsForInvalidPythonHeadersType() var exception = Assert.Throws(() => new SubscriptionDataSource("https://example.com", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv, invalidHeaders)); - StringAssert.Contains("is not a dict", exception.Message); + StringAssert.Contains("ConvertToDictionary cannot be used", exception.Message); } } }