Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.
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
27 changes: 20 additions & 7 deletions BuffettCodeAPIClient/ApiClientCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ namespace BuffettCodeAPIClient
/// BuffettCode API と Http でやり取りする Client のコアクラス
/// </summary>

public class ApiClientCore
public class ApiClientCore : IDisposable, IApiClientCore
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

実際にhttp通信を行うこいつを差し替えられるようにします

{
public string ApiKey { set; get; }
private string apiKey;
private readonly Uri baseUri;
private static readonly long TimeoutMilliseconds = 5000;
private readonly HttpClient httpClient;
public ApiClientCore(string apiKey, Uri baseUri)
{
this.ApiKey = apiKey;
this.apiKey = apiKey;
this.baseUri = baseUri;
this.httpClient = NewHttpClient();
}
Expand All @@ -32,11 +32,11 @@ private HttpClient NewHttpClient()
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("x-api-key", ApiKey);
httpClient.DefaultRequestHeaders.Add("x-api-key", apiKey);
return httpClient;
}

public static string BuildGetPath(ApiGetRequest request)
private static string BuildGetPath(ApiGetRequest request)
{
return $"{request.EndPoint}?{new FormUrlEncodedContent(request.Parameters).ReadAsStringAsync().Result}";
}
Expand All @@ -58,10 +58,23 @@ public async Task<string> Get(ApiGetRequest request, bool isConfigureAwait)
throw new BuffettCodeApiClientException();
}
}
// to do : waiting too long to read as str in csv download
return await response.Content.ReadAsStringAsync().ConfigureAwait(isConfigureAwait);
var content = response.Content.ReadAsStringAsync().Result;
Comment on lines -62 to +61
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReadAsStringAsync().Result って危険な呼び出しにみえるけど、非同期処理が未完了だったらブロックしてくれると。

https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.tasks.task-1.result?view=net-5.0#--

return content;
}
}

public void Dispose()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructor で httpClient を作ってるので、せっかくなので IDisposable にします

{
httpClient.Dispose();
}

public IApiClientCore SetApiKey(string apiKey)
{
this.apiKey = apiKey;
return this;
}

public string GetApiKey() => this.apiKey;
}

}
37 changes: 20 additions & 17 deletions BuffettCodeAPIClient/ApiClientCoreWithCache.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;

namespace BuffettCodeAPIClient
{
public class ApiClientCoreWithCache
{
private readonly ApiClientCore apiClientCore;
private readonly IApiClientCore apiClientCore;
private readonly ApiRequestCacheHelper cacheHelper;

private ApiClientCoreWithCache(string apiKey, Uri baseUri, MemoryCache cache)
private ApiClientCoreWithCache(IApiClientCore apiClientCore
, ApiRequestCacheHelper cacheHelper)
{
this.apiClientCore = new ApiClientCore(apiKey, baseUri);
this.cacheHelper = new ApiRequestCacheHelper(cache, baseUri);
this.apiClientCore = apiClientCore;
this.cacheHelper = cacheHelper;
}

public static ApiClientCoreWithCache Create(string apiKey, string baseUrl, MemoryCache cache)
{
return new ApiClientCoreWithCache(apiKey, new Uri(baseUrl), cache);
var baseUri = new Uri(baseUrl);
var apiClientCore = new ApiClientCore(apiKey, baseUri);
var cacheHelper = new ApiRequestCacheHelper(cache, baseUri);
return new ApiClientCoreWithCache(apiClientCore, cacheHelper);
}

public static ApiClientCoreWithCache Create(string apiKey, Uri baseUrl, MemoryCache cache)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この Create は誰も使ってなかったので削除

public static ApiClientCoreWithCache Create(IApiClientCore apiClientCore, ApiRequestCacheHelper cacheHelper)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代わりにこっちを作りました

{
return new ApiClientCoreWithCache(apiKey, baseUrl, cache);
return new ApiClientCoreWithCache(apiClientCore, cacheHelper);
}

public void UpdateApiKey(string apikey)
{
this.apiClientCore.ApiKey = apikey;
}
public void UpdateApiKey(string apiKey) => this.apiClientCore.SetApiKey(apiKey);

public string GetApiKey() => this.apiClientCore.GetApiKey();

public string GetApiKey() => this.apiClientCore.ApiKey;
public async Task<string> Get(ApiGetRequest request, bool isConfigureAwait, bool useCache)
public string Get(ApiGetRequest request, bool isConfigureAwait, bool useCache)
{
if (useCache && cacheHelper.HasCache(request))
{
return (string)cacheHelper.Get(request);
}
var response = await apiClientCore.Get(request, isConfigureAwait);
cacheHelper.Set(request, response);
return response;
else
{
return apiClientCore.Get(request, isConfigureAwait).Result;
Copy link
Copy Markdown
Contributor Author

@shoe116 shoe116 Nov 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この時点で取得を待って動くようにします。当然ながら cache からは常に string が直接返ってくるので、こっちのが全体として違和感がないと思う。

}
}

}
}
1 change: 1 addition & 0 deletions BuffettCodeAPIClient/BuffettCodeAPIClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<Compile Include="ApiClientInstanceGetter.cs" />
<Compile Include="ApiGetResponseValidator.cs" />
<Compile Include="IBuffettCodeApiClient.cs" />
<Compile Include="IApiClientCore.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
21 changes: 10 additions & 11 deletions BuffettCodeAPIClient/BuffettCodeApiV2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.Caching;
using System.Threading.Tasks;

namespace BuffettCodeAPIClient
{
Expand All @@ -25,31 +24,31 @@ private BuffettCodeApiV2Client()
);
}

public async Task<JObject> GetQuarter(string ticker, FiscalQuarterPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
public JObject GetQuarter(string ticker, FiscalQuarterPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApiClientCoreWithCache が同期的に動くようになったので、ここも Json をそのまま返します

{
var request = BuffettCodeApiV2RequestCreator.CreateGetQuarterRequest(ticker, period, useOndemand);
var response = await apiClientCore.Get(request, isConfigureAwait, useCache);
var response = apiClientCore.Get(request, isConfigureAwait, useCache);
return ApiGetResponseBodyParser.Parse(response);
}

public async Task<JObject> GetIndicator(string ticker, bool isConfigureAwait = true, bool useCache = true)
public JObject GetIndicator(string ticker, bool isConfigureAwait = true, bool useCache = true)
{
var request = BuffettCodeApiV2RequestCreator.CreateGetIndicatorRequest
(ticker);
var response = await apiClientCore.Get(request, isConfigureAwait, useCache);
var response = apiClientCore.Get(request, isConfigureAwait, useCache);
return ApiGetResponseBodyParser.Parse(response);
}

public async Task<JObject> GetQuarterRange(string ticker, FiscalQuarterPeriod from, FiscalQuarterPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
public JObject GetQuarterRange(string ticker, FiscalQuarterPeriod from, FiscalQuarterPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
{
var request = BuffettCodeApiV2RequestCreator.CreateGetQuarterRangeRequest(ticker, from, to);
var response = await apiClientCore.Get(request, isConfigureAwait, useCache);
var response = apiClientCore.Get(request, isConfigureAwait, useCache);
return ApiGetResponseBodyParser.Parse(response);
}
public async Task<JObject> GetCompany(string ticker, bool isConfigureAwait = true, bool useCache = true)
public JObject GetCompany(string ticker, bool isConfigureAwait = true, bool useCache = true)
{
var request = BuffettCodeApiV2RequestCreator.CreateGetCompanyRequest(ticker);
var response = await apiClientCore.Get(request, isConfigureAwait, useCache);
var response = apiClientCore.Get(request, isConfigureAwait, useCache);
return ApiGetResponseBodyParser.Parse(response);
}

Expand All @@ -64,7 +63,7 @@ public static BuffettCodeApiV2Client GetInstance(string apiKey)
}


public Task<JObject> Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
public JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
{
switch (dataType)
{
Expand All @@ -79,7 +78,7 @@ public Task<JObject> Get(DataTypeConfig dataType, string ticker, IPeriod period,
}
}

public Task<JObject> GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
public JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
{
switch (dataType)
{
Expand Down
9 changes: 4 additions & 5 deletions BuffettCodeAPIClient/BuffettCodeApiV3Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using BuffettCodeCommon.Validator;
using Newtonsoft.Json.Linq;
using System.Runtime.Caching;
using System.Threading.Tasks;


namespace BuffettCodeAPIClient
Expand All @@ -27,11 +26,11 @@ private BuffettCodeApiV3Client()

}

public async Task<JObject> GetDaily(string ticker, DayPeriod day, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
public JObject GetDaily(string ticker, DayPeriod day, bool useOndemand, bool isConfigureAwait = true, bool useCache = true)
{
var request = BuffettCodeApiV3RequestCreator.CreateGetDailyRequest(ticker, day, useOndemand);
JpTickerValidator.Validate(ticker);
var response = await apiClientCore.Get(request, isConfigureAwait, useCache);
var response = apiClientCore.Get(request, isConfigureAwait, useCache);
return ApiGetResponseBodyParser.Parse(response);
}

Expand All @@ -45,7 +44,7 @@ public static BuffettCodeApiV3Client GetInstance(string apiKey)
return instance;
}

public Task<JObject> Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache)
public JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache)
{
switch (dataType)
{
Expand All @@ -56,7 +55,7 @@ public Task<JObject> Get(DataTypeConfig dataType, string ticker, IPeriod period,
}
}

public Task<JObject> GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache)
public JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache)
{
switch (dataType)
{
Expand Down
13 changes: 13 additions & 0 deletions BuffettCodeAPIClient/IApiClientCore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading.Tasks;

namespace BuffettCodeAPIClient
{
public interface IApiClientCore
{
Task<string> Get(ApiGetRequest request, bool isConfigureAwait);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using (var response = await httpClient.GetAsync(path).ConfigureAwait(isConfigureAwait)) しているのでシグニチャは Task<string> のまま、と

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これどうするかすごい迷ったけど、この ApiClientCore の通信部分は非同期に呼び出す前提にしておいたほうがいいと思う。

string 直接返す client が Task<string> に変換して返すのは全然難しくないし

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

将来的には非同期クライアントになってるほうがいいと思うので、内部的には半同期みたいな感じでもシグニチャはこのままでいいと思う


IApiClientCore SetApiKey(string apiKey);
string GetApiKey();

}
}
5 changes: 2 additions & 3 deletions BuffettCodeAPIClient/IBuffettCodeApiClient.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using BuffettCodeCommon.Config;
using BuffettCodeCommon.Period;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;


namespace BuffettCodeAPIClient
{
public interface IBuffettCodeApiClient
{
Task<JObject> Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache);
Task<JObject> GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache);
JObject Get(DataTypeConfig dataType, string ticker, IPeriod period, bool useOndemand, bool isConfigureAwait, bool useCache);
JObject GetRange(DataTypeConfig dataType, string ticker, IPeriod from, IPeriod to, bool useOndemand, bool isConfigureAwait, bool useCache);
string GetApiKey();
void UpdateApiKey(string apiKey);

Expand Down
12 changes: 12 additions & 0 deletions BuffettCodeAPIClientTests/ApiClientCoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,17 @@ var clentCore
Assert.AreEqual(1, xApiKeyHeaders.Count);
Assert.AreEqual(apiKey, xApiKeyHeaders[0]);
}

[TestMethod()]
public void SetGetApiKeyTest()
{
var initKey = "init_api_key";
var newKey = "new_api_key";
var baseUri = new Uri(@"https://example.com");
var clientCore
= new ApiClientCore(initKey, baseUri);
Assert.AreEqual(initKey, clientCore.GetApiKey());
Assert.AreEqual(newKey, clientCore.SetApiKey(newKey).GetApiKey());
}
}
}
80 changes: 79 additions & 1 deletion BuffettCodeAPIClientTests/ApiClientCoreWithCacheTests.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,101 @@
using BuffettCodeCommon.Exception;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Runtime.Caching;
using System.Threading.Tasks;

namespace BuffettCodeAPIClient.Tests
{
class MockApiclientCore : IApiClientCore
{
private string apiKey;
public MockApiclientCore(string apiKey)
{
this.apiKey = apiKey;
}


public Task<string> Get(ApiGetRequest request, bool isConfigureAwait)
{
return Task<string>.FromResult(request.ToString());
}

public string GetApiKey() => this.apiKey;
public IApiClientCore SetApiKey(string apiKey)
{
this.apiKey = apiKey;
return this;
}
}

class ErrorMockApiClientCore : IApiClientCore
{
private string apiKey;
public ErrorMockApiClientCore(string apiKey)
{
this.apiKey = apiKey;
}


public Task<string> Get(ApiGetRequest request, bool isConfigureAwait)
{
throw new BuffettCodeApiClientException();
}

public string GetApiKey() => this.apiKey;
public IApiClientCore SetApiKey(string apikey)
{
apiKey = apikey;
return this;
}
}

[TestClass()]
public class ApiClientCoreWithCacheTests
{
private static ApiRequestCacheHelper CreateCheHelperForTest()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test ごとにcache helper を作ります。

{
var cacheKey = new Random().Next().GetHashCode().ToString();
return new ApiRequestCacheHelper(new MemoryCache(cacheKey), new Uri("http://example.com"));
}

[TestMethod()]
public void GetAndUpdateApiKeyTest()
{
var initApiKey = @"init-key";
var newApiKey = @"new-key";
var client = ApiClientCoreWithCache.Create(initApiKey, @"http://example.com", new MemoryCache(@"GetAndUpdateApiKeyTest"));
var mockApiClientCore = new MockApiclientCore(initApiKey);
var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest());

// test init value
Assert.AreEqual(initApiKey, client.GetApiKey());

client.UpdateApiKey(newApiKey);
Assert.AreEqual(newApiKey, client.GetApiKey());
}

[TestMethod()]
public void GetTest()
{
var mockApiClientCore = new MockApiclientCore("dummy");
var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest());
ApiGetRequest request = new ApiGetRequest("dummy endpoint", new Dictionary<string, string>());
Assert.AreEqual(request.ToString(), client.Get(request, false, true));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1回目のリクエスト

// use cache
Assert.AreEqual(request.ToString(), client.Get(request, false, true));

// don't use cache
Assert.AreEqual(request.ToString(), client.Get(request, false, false));
Comment on lines +86 to +89
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cahceを使っても使わなくても、同じ結果が返ることを確認しています

}

[TestMethod()]
public void GetExceptionTest()
{
var mockApiClientCore = new ErrorMockApiClientCore("dummy");
var client = ApiClientCoreWithCache.Create(mockApiClientCore, CreateCheHelperForTest());
ApiGetRequest request = new ApiGetRequest("dummy endpoint", new Dictionary<string, string>());
Assert.ThrowsException<BuffettCodeApiClientException>(() => client.Get(request, false, false));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clientCore が例外を投げた場合はそのまま投げることを確認しています

}
}
}
Loading