Skip to content

Commit 7fa3916

Browse files
authored
Add generic DownloadData<T> extension methods for deserialization (#9286)
* Add new helper methods to deserialize to T * Solve review comments * Minor fix * Add unit tests
1 parent 0212acf commit 7fa3916

2 files changed

Lines changed: 207 additions & 72 deletions

File tree

Common/Extensions.cs

Lines changed: 111 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ namespace QuantConnect
7070
/// </summary>
7171
public static class Extensions
7272
{
73-
private static readonly Dictionary<string, bool> _emptyDirectories = new ();
73+
private static readonly Dictionary<string, bool> _emptyDirectories = new();
7474
private static readonly HashSet<string> InvalidSecurityTypes = new HashSet<string>();
7575
private static readonly Regex DateCheck = new Regex(@"\d{8}", RegexOptions.Compiled);
7676
private static RecyclableMemoryStreamManager MemoryManager = new RecyclableMemoryStreamManager();
@@ -174,7 +174,7 @@ public static bool IsDirectoryEmpty(this string directoryPath)
174174
{
175175
lock (_emptyDirectories)
176176
{
177-
if(!_emptyDirectories.TryGetValue(directoryPath, out var result))
177+
if (!_emptyDirectories.TryGetValue(directoryPath, out var result))
178178
{
179179
// is empty unless it exists and it has at least 1 file or directory in it
180180
result = true;
@@ -285,7 +285,73 @@ public static List<T> DeserializeList<T>(this string jsonArray)
285285
/// <param name="headers">Add custom headers for the request</param>
286286
public static bool TryDownloadData(this HttpClient client, string url, out string data, out HttpStatusCode? statusCode, Dictionary<string, string> headers = null)
287287
{
288-
data = null;
288+
return client.TryDownloadData(url, out data, out statusCode, headers, null);
289+
}
290+
291+
/// <summary>
292+
/// Helper method to download a provided url as a string
293+
/// </summary>
294+
/// <param name="client">The http client to use</param>
295+
/// <param name="url">The url to download data from</param>
296+
/// <param name="headers">Add custom headers for the request</param>
297+
public static string DownloadData(this HttpClient client, string url, Dictionary<string, string> headers = null)
298+
{
299+
return client.DownloadData<string>(url, headers, null);
300+
}
301+
302+
/// <summary>
303+
/// Helper method to download a provided url as a string
304+
/// </summary>
305+
/// <param name="url">The url to download data from</param>
306+
/// <param name="headers">Add custom headers for the request</param>
307+
public static string DownloadData(this string url, Dictionary<string, string> headers = null)
308+
{
309+
return url.DownloadData<string>(headers, null);
310+
}
311+
312+
/// <summary>
313+
/// Download the content of a url to a string and deserialize it to the specified type
314+
/// </summary>
315+
/// <typeparam name="T">The type to deserialize to</typeparam>
316+
/// <param name="client">The http client to use</param>
317+
/// <param name="url">The url to download data from</param>
318+
/// <param name="headers">Add custom headers for the request</param>
319+
/// <param name="settings">Optional JSON serializer settings</param>
320+
/// <returns>The deserialized data</returns>
321+
public static T DownloadData<T>(this HttpClient client, string url, Dictionary<string, string> headers = null, JsonSerializerSettings settings = null)
322+
{
323+
client.TryDownloadData<T>(url, out var result, out _, headers, settings);
324+
return result;
325+
}
326+
327+
/// <summary>
328+
/// Download the content of a url to a string and deserialize it to the specified type
329+
/// </summary>
330+
/// <typeparam name="T">The type to deserialize to</typeparam>
331+
/// <param name="url">The url to download data from</param>
332+
/// <param name="headers">Add custom headers for the request</param>
333+
/// <param name="settings">Optional JSON serializer settings</param>
334+
/// <returns>The deserialized data</returns>
335+
public static T DownloadData<T>(this string url, Dictionary<string, string> headers = null, JsonSerializerSettings settings = null)
336+
{
337+
using var client = new HttpClient();
338+
return client.DownloadData<T>(url, headers, settings);
339+
}
340+
341+
/// <summary>
342+
/// Tries to download and deserialize directly from stream to T
343+
/// </summary>
344+
/// <typeparam name="T">The type to deserialize to</typeparam>
345+
/// <param name="client">The http client to use</param>
346+
/// <param name="url">The url to download data from</param>
347+
/// <param name="result">The deserialized data if successful</param>
348+
/// <param name="statusCode">The request status code</param>
349+
/// <param name="headers">Add custom headers for the request</param>
350+
/// <param name="settings">Optional JSON serializer settings</param>
351+
/// <returns>True if successful, otherwise false</returns>
352+
public static bool TryDownloadData<T>(this HttpClient client, string url, out T result, out HttpStatusCode? statusCode, Dictionary<string, string> headers = null, JsonSerializerSettings settings = null)
353+
{
354+
result = default;
289355
statusCode = null;
290356
using var request = new HttpRequestMessage(HttpMethod.Get, url);
291357
if (headers != null)
@@ -295,6 +361,7 @@ public static bool TryDownloadData(this HttpClient client, string url, out strin
295361
request.Headers.TryAddWithoutValidation(kvp.Key, kvp.Value);
296362
}
297363
}
364+
298365
try
299366
{
300367
using var response = client.SendAsync(request).SynchronouslyAwaitTaskResult();
@@ -306,39 +373,29 @@ public static bool TryDownloadData(this HttpClient client, string url, out strin
306373
return false;
307374
}
308375

309-
data = response.Content.ReadAsStringAsync().SynchronouslyAwaitTaskResult();
376+
using var stream = response.Content.ReadAsStreamAsync().SynchronouslyAwaitTaskResult();
377+
using var reader = new StreamReader(stream);
378+
379+
if (typeof(T) == typeof(string))
380+
{
381+
// Special case: return the response as a raw string without deserialization
382+
result = (T)(object)reader.ReadToEnd();
383+
}
384+
else
385+
{
386+
using var jsonReader = new JsonTextReader(reader);
387+
var serializer = JsonSerializer.Create(settings);
388+
result = serializer.Deserialize<T>(jsonReader);
389+
}
310390
return true;
311391
}
312-
catch (WebException ex)
392+
catch (HttpRequestException ex)
313393
{
314394
Log.Error(ex, $"DownloadData(): {Messages.Extensions.DownloadDataFailed(url)}");
315395
return false;
316396
}
317397
}
318398

319-
/// <summary>
320-
/// Helper method to download a provided url as a string
321-
/// </summary>
322-
/// <param name="client">The http client to use</param>
323-
/// <param name="url">The url to download data from</param>
324-
/// <param name="headers">Add custom headers for the request</param>
325-
public static string DownloadData(this HttpClient client, string url, Dictionary<string, string> headers = null)
326-
{
327-
client.TryDownloadData(url, out var data, out _, headers);
328-
return data;
329-
}
330-
331-
/// <summary>
332-
/// Helper method to download a provided url as a string
333-
/// </summary>
334-
/// <param name="url">The url to download data from</param>
335-
/// <param name="headers">Add custom headers for the request</param>
336-
public static string DownloadData(this string url, Dictionary<string, string> headers = null)
337-
{
338-
using var client = new HttpClient();
339-
return client.DownloadData(url, headers);
340-
}
341-
342399
/// <summary>
343400
/// Helper method to download a provided url as a byte array
344401
/// </summary>
@@ -841,7 +898,8 @@ public static IEnumerable<IPortfolioTarget> OrderTargetsByMarginImpact(
841898
&& (targetIsDelta ? Math.Abs(x.TargetQuantity) : Math.Abs(x.TargetQuantity - x.ExistingQuantity))
842899
>= x.Security.SymbolProperties.LotSize
843900
)
844-
.Select(x => new {
901+
.Select(x => new
902+
{
845903
x.PortfolioTarget,
846904
OrderValue = Math.Abs((targetIsDelta ? x.TargetQuantity : (x.TargetQuantity - x.ExistingQuantity)) * x.Security.Price),
847905
IsReducingPosition = x.ExistingQuantity != 0
@@ -867,7 +925,7 @@ public static BaseData GetBaseDataInstance(this Type type)
867925
}
868926

869927
var instance = objectActivator.Invoke(new object[] { type });
870-
if(instance == null)
928+
if (instance == null)
871929
{
872930
// shouldn't happen but just in case...
873931
throw new ArgumentException(Messages.Extensions.FailedToCreateInstanceOfType(type));
@@ -1007,7 +1065,8 @@ public static string SerializeJsonToString<T>(this T value, JsonSerializer seria
10071065
public static void Clear<T>(this ConcurrentQueue<T> queue)
10081066
{
10091067
T item;
1010-
while (queue.TryDequeue(out item)) {
1068+
while (queue.TryDequeue(out item))
1069+
{
10111070
// NOP
10121071
}
10131072
}
@@ -1294,7 +1353,7 @@ public static void Add(this Ticks dictionary, Symbol key, Tick tick)
12941353
public static decimal RoundToSignificantDigits(this decimal d, int digits)
12951354
{
12961355
if (d == 0) return 0;
1297-
var scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10((double) Math.Abs(d))) + 1);
1356+
var scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10((double)Math.Abs(d))) + 1);
12981357
return scale * Math.Round(d / scale, digits);
12991358
}
13001359

@@ -1459,9 +1518,9 @@ public static decimal SafeDecimalCast(this double input)
14591518
);
14601519
}
14611520

1462-
if (input <= (double) decimal.MinValue) return decimal.MinValue;
1463-
if (input >= (double) decimal.MaxValue) return decimal.MaxValue;
1464-
return (decimal) input;
1521+
if (input <= (double)decimal.MinValue) return decimal.MinValue;
1522+
if (input >= (double)decimal.MaxValue) return decimal.MaxValue;
1523+
return (decimal)input;
14651524
}
14661525

14671526
/// <summary>
@@ -1803,7 +1862,8 @@ public static decimal GetDecimalEpsilon()
18031862
/// </summary>
18041863
/// <param name="str">String we're looking for the extension for.</param>
18051864
/// <returns>Last 4 character string of string.</returns>
1806-
public static string GetExtension(this string str) {
1865+
public static string GetExtension(this string str)
1866+
{
18071867
var ext = str.Substring(Math.Max(0, str.Length - 4));
18081868
var allowedExt = new List<string> { ".zip", ".csv", ".json", ".tsv" };
18091869
if (!allowedExt.Contains(ext))
@@ -2212,19 +2272,19 @@ public static Resolution ToHigherResolutionEquivalent(this TimeSpan timeSpan, bo
22122272
{
22132273
if (requireExactMatch)
22142274
{
2215-
if (TimeSpan.Zero == timeSpan) return Resolution.Tick;
2275+
if (TimeSpan.Zero == timeSpan) return Resolution.Tick;
22162276
if (Time.OneSecond == timeSpan) return Resolution.Second;
22172277
if (Time.OneMinute == timeSpan) return Resolution.Minute;
2218-
if (Time.OneHour == timeSpan) return Resolution.Hour;
2219-
if (Time.OneDay == timeSpan) return Resolution.Daily;
2278+
if (Time.OneHour == timeSpan) return Resolution.Hour;
2279+
if (Time.OneDay == timeSpan) return Resolution.Daily;
22202280
throw new InvalidOperationException(Messages.Extensions.UnableToConvertTimeSpanToResolution(timeSpan));
22212281
}
22222282

22232283
// for non-perfect matches
22242284
if (Time.OneSecond > timeSpan) return Resolution.Tick;
22252285
if (Time.OneMinute > timeSpan) return Resolution.Second;
2226-
if (Time.OneHour > timeSpan) return Resolution.Minute;
2227-
if (Time.OneDay > timeSpan) return Resolution.Hour;
2286+
if (Time.OneHour > timeSpan) return Resolution.Minute;
2287+
if (Time.OneDay > timeSpan) return Resolution.Hour;
22282288

22292289
return Resolution.Daily;
22302290
}
@@ -2263,7 +2323,7 @@ public static bool TryParseSecurityType(this string value, out SecurityType secu
22632323
/// <returns>The converted value</returns>
22642324
public static T ConvertTo<T>(this string value)
22652325
{
2266-
return (T) value.ConvertTo(typeof (T));
2326+
return (T)value.ConvertTo(typeof(T));
22672327
}
22682328

22692329
/// <summary>
@@ -2279,16 +2339,16 @@ public static object ConvertTo(this string value, Type type)
22792339
return Enum.Parse(type, value, true);
22802340
}
22812341

2282-
if (typeof (IConvertible).IsAssignableFrom(type))
2342+
if (typeof(IConvertible).IsAssignableFrom(type))
22832343
{
22842344
return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
22852345
}
22862346

22872347
// try and find a static parse method
2288-
var parse = type.GetMethod("Parse", new[] {typeof (string)});
2348+
var parse = type.GetMethod("Parse", new[] { typeof(string) });
22892349
if (parse != null)
22902350
{
2291-
var result = parse.Invoke(null, new object[] {value});
2351+
var result = parse.Invoke(null, new object[] { value });
22922352
return result;
22932353
}
22942354

@@ -2323,7 +2383,7 @@ public static bool WaitOne(this WaitHandle waitHandle, CancellationToken cancell
23232383
/// <exception cref="T:System.InvalidOperationException">The maximum number of waiters has been exceeded. </exception><exception cref="T:System.ObjectDisposedException">The object has already been disposed or the <see cref="T:System.Threading.CancellationTokenSource"/> that created <paramref name="cancellationToken"/> has been disposed.</exception>
23242384
public static bool WaitOne(this WaitHandle waitHandle, TimeSpan timeout, CancellationToken cancellationToken)
23252385
{
2326-
return waitHandle.WaitOne((int) timeout.TotalMilliseconds, cancellationToken);
2386+
return waitHandle.WaitOne((int)timeout.TotalMilliseconds, cancellationToken);
23272387
}
23282388

23292389
/// <summary>
@@ -2906,7 +2966,7 @@ public static bool TryConvert<T>(this PyObject pyObject, out T result, bool allo
29062966
{
29072967
result = (T)pyObject.AsManagedObject(type);
29082968
// pyObject is a C# object wrapped in PyObject, in this case return true
2909-
if(!pyObject.HasAttr("__name__"))
2969+
if (!pyObject.HasAttr("__name__"))
29102970
{
29112971
return true;
29122972
}
@@ -3343,7 +3403,7 @@ public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, in
33433403
{
33443404
if (list == null)
33453405
{
3346-
list = new List<T> {enumerator.Current};
3406+
list = new List<T> { enumerator.Current };
33473407
}
33483408
else if (list.Count < batchSize)
33493409
{
@@ -3352,7 +3412,7 @@ public static IEnumerable<List<T>> BatchBy<T>(this IEnumerable<T> enumerable, in
33523412
else
33533413
{
33543414
yield return list;
3355-
list = new List<T> {enumerator.Current};
3415+
list = new List<T> { enumerator.Current };
33563416
}
33573417
}
33583418

@@ -3600,7 +3660,7 @@ public static IEnumerator<BaseData> SubscribeWithMapping(this IDataQueueHandler
36003660
/// <returns>Enumeration of lines in file</returns>
36013661
public static IEnumerable<string> ReadLines(this IDataProvider dataProvider, string file)
36023662
{
3603-
if(dataProvider == null)
3663+
if (dataProvider == null)
36043664
{
36053665
throw new ArgumentException(Messages.Extensions.NullDataProvider);
36063666
}
@@ -4161,7 +4221,7 @@ public static OptionRight Invert(this OptionRight right)
41614221
switch (right)
41624222
{
41634223
case OptionRight.Call: return OptionRight.Put;
4164-
case OptionRight.Put: return OptionRight.Call;
4224+
case OptionRight.Put: return OptionRight.Call;
41654225
default:
41664226
throw new ArgumentOutOfRangeException(nameof(right), right, null);
41674227
}

0 commit comments

Comments
 (0)