-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable access token caching #66
Conversation
RelationalAI/AccessToken.cs
Outdated
public int ExpiresIn | ||
{ | ||
get => _expiresIn; | ||
set => _expiresIn = value > 0 ? value : throw new ArgumentException("ExpiresIn should be greater than 0 "); | ||
} | ||
|
||
public bool IsExpired => (DateTime.Now - DateTimeOffset.FromUnixTimeSeconds(CreatedOn)).TotalSeconds >= _expiresIn - 5; // Anticipate access token expiration by 5 seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get the expiration timestamp exposed next to this method? I'd like to control the renewal not based on this flag, but on the actual expiration time
RelationalAI/IAccessTokenHandler.cs
Outdated
// Check DefaultAccessToken for an example | ||
public interface IAccessTokenHandler | ||
{ | ||
Rest Rest { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Rest client should not be part of the interface, nor the Logger. The interface is to simply provide token retrieval. Baking it into the interface conflates 1) The fact of even using the Rest client to do the implementation and 2) That the implementation logs at a ll, much less using the MS Logging extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense thanks
RelationalAI/Client.cs
Outdated
{ | ||
_context = context; | ||
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK"); | ||
_rest = new Rest(context, _logger); | ||
_rest = new Rest(context, _logger, accessTokenHandler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please implement the default access token handler creation here in the client, instead of in the REST layer, to keep the REST layer as thin as possible (think, as close to Curl as possible)
{ | ||
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1); | ||
|
||
public Rest Rest { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove these public REST/Logger properties after fixing the interface as described in the interface
{ | ||
try | ||
{ | ||
var data = File.ReadAllText(CacheName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this class should fallback to in-memory caching if it cannot find the cache folder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's right, this behavior is already handled in the caller method GetAccessTokenAsync()
in the Rest class but it should be part of the handler implementation, moving it here
dict.Add(creds.ClientId, token); | ||
} | ||
|
||
File.WriteAllText(CacheName(), JsonConvert.SerializeObject(dict)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar comment, should fallback to memory cache is no file location
RelationalAI/Rest.cs
Outdated
{ | ||
_context = context; | ||
HttpClient = new HttpClient(); | ||
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK"); | ||
|
||
// Init AccessTokenHandler | ||
AccessTokenHandler = accessTokenHandler ?? new DefaultAccessTokenHandler(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move default behavior into client
RelationalAI/Rest.cs
Outdated
} | ||
|
||
public HttpClient HttpClient { get; set; } | ||
|
||
public IAccessTokenHandler AccessTokenHandler { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this public?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the purpose is to make the Client class able to set the access token handler on the Rest class. Will change this field to make it internal
Otherwise we will need to use the Rest constructor the inject the AccessTokenHandler something like
public Client(Context context, ILogger logger = null, IAccessTokenHandler accessTokenHandler = null)
{
_context = context;
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");
_rest = new Rest(context, _logger, accessTokenHandler ?? new DefaultAccessTokenHandler(new Rest(context, null, null));
}
vs (current implementation)
public Client(Context context, ILogger logger = null, IAccessTokenHandler accessTokenHandler = null)
{
_context = context;
_logger = logger ?? new LoggerFactory().CreateLogger("RAI-SDK");
_rest = new Rest(context, _logger);
_rest.AccessTokenHandler = accessTokenHandler ?? new DefaultAccessTokenHandler(_rest);
}
I lean toward using the second option
{ "grant_type", "client_credentials" } | ||
}; | ||
var resp = await RequestHelperAsync("POST", creds.ClientCredentialsUrl, data); | ||
if (!(resp is string stringResponse)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what scenario does this happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not the one who implemented the body of this method RequestAccessTokenAsync
, just making it public so that the IAccessTokenHandler
implementations can use it to fetch new tokens
RelationalAI/AccessToken.cs
Outdated
_createdOn = DateTime.Now; | ||
_expiresIn = expiresIn; | ||
Scope = scope; | ||
CreatedOn = new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the token creation time, this is the objects creation time (which could be created during deserialization from a cache for example). Take the creation time as an argument instead.
Needs test for default and non-default behavior |
RelationalAI/Rest.cs
Outdated
@@ -41,21 +41,16 @@ public class Rest | |||
|
|||
private readonly ILogger _logger; | |||
|
|||
public Rest(Context context, ILogger logger, IAccessTokenHandler accessTokenHandler) | |||
public Rest(Context context, ILogger logger) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we relax the ctor API here to default to null so the arg is not required at all for logger, now that clients are much more likely to use the direct Rest ctor, it'd be nice to not force ugly calls of Rest(ctx, null)
} | ||
|
||
public HttpClient HttpClient { get; set; } | ||
|
||
public IAccessTokenHandler AccessTokenHandler { get; set; } | ||
internal IAccessTokenHandler AccessTokenHandler { get; set; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does the assembly need access to this prop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making the field internal so that the client class can set and manipulate the access token handler in the rest class
@@ -32,34 +31,40 @@ namespace RelationalAI | |||
public class DefaultAccessTokenHandler : IAccessTokenHandler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs handling for when filename not found -> INFO log msg, fallback to memory-based cache local ot the tokenhandler
CHANGELOG.md
Outdated
@@ -1,5 +1,10 @@ | |||
# Changelog | |||
|
|||
# main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you change to 0.9.13-alpha
|
||
namespace RelationalAI.Test | ||
{ | ||
[Collection("RelationalAI.Test")] | ||
public class DefaultAccessTokenHandlerTests : UnitTest | ||
{ | ||
private string TestCachePath() | ||
{ | ||
var envHome = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "HOMEPATH" : "HOME"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use the C# corelib env var for this for better portability: https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=net-7.0
(System.Environment.UserProfile)
@@ -59,12 +62,12 @@ public async Task<AccessToken> GetAccessTokenAsync(ClientCredentials creds) | |||
return creds.AccessToken; | |||
} | |||
|
|||
private string CachePath() | |||
private string DefaultCachePath() | |||
{ | |||
var envHome = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "HOMEPATH" : "HOME"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as in comment in test please use corelib portable environment enum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make the changes I mentioned
- changelog adding version,
- use corelib env enums everywhere
Enabling access token caching by: