Skip to content

Commit

Permalink
Merge pull request #32 from AssemblyAI/niels/di-extensions
Browse files Browse the repository at this point in the history
Add dependency injection extensions
  • Loading branch information
Swimburger authored Jul 22, 2024
2 parents 81360ec + a26ecb8 commit 10b4059
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 24 deletions.
8 changes: 6 additions & 2 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

README.md
LICENSE.md
src/AssemblyAI/Realtime/RealtimeTranscriber.cs
src/AssemblyAI/Realtime/WebSocketClient
src/AssemblyAI/AssemblyAI.csproj
src/AssemblyAI/AssemblyAIClient.cs
src/AssemblyAI/ClientOptions.cs
src/AssemblyAI/DependencyInjectionExtensions.cs
src/AssemblyAI/Realtime/RealtimeTranscriber.cs
src/AssemblyAI/Realtime/WebSocketClient
src/AssemblyAI/Constants.cs
src/AssemblyAI/UserAgent.cs
src/AssemblyAI.Test

Samples
samples


Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,5 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk

.idea
.idea
.runsettings
2 changes: 2 additions & 0 deletions src/AssemblyAI.Test/AssemblyAI.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2"/>
Expand Down
84 changes: 84 additions & 0 deletions src/AssemblyAI.Test/DiClientOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Text.Json;
using AssemblyAI.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUnit.Framework;

namespace AssemblyAI.Test;

[TestFixture]
public class DependencyInjectionClientOptionsTests
{
private static readonly ClientOptions ValidAssemblyAIOptions = new()
{
ApiKey = "MyAssemblyAIApiKey",
BaseUrl = "https://api.assemblyai.com",
MaxRetries = 2,
TimeoutInSeconds = 30
};

[Test]
public void AddAssemblyAIClient_With_Callback_Should_Match_Configuration()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(BuildEmptyConfiguration());
serviceCollection.AddAssemblyAIClient((_, options) =>
{
options.ApiKey = ValidAssemblyAIOptions.ApiKey;
options.BaseUrl = ValidAssemblyAIOptions.BaseUrl;
options.MaxRetries = ValidAssemblyAIOptions.MaxRetries;
options.TimeoutInSeconds = ValidAssemblyAIOptions.TimeoutInSeconds;
});

var serviceProvider = serviceCollection.BuildServiceProvider();
var assemblyAIClientOptions = serviceProvider.GetService<IOptions<ClientOptions>>()?.Value;

var expectedJson = JsonSerializer.Serialize(ValidAssemblyAIOptions);
var actualJson = JsonSerializer.Serialize(assemblyAIClientOptions);

Assert.That(actualJson, Is.EqualTo(expectedJson));
}

[Test]
public async Task AddAssemblyAIClient_From_Configuration_Should_Reload_On_Change()
{
const string optionsFile = "ClientOptions.json";
if (File.Exists(optionsFile)) File.Delete(optionsFile);
var jsonText = JsonSerializer.Serialize(new { AssemblyAI = ValidAssemblyAIOptions });
await File.WriteAllTextAsync(optionsFile, jsonText);

var serviceCollection = new ServiceCollection();
var configuration = new ConfigurationBuilder()
.AddJsonFile(optionsFile, optional: false, reloadOnChange: true)
.Build();

serviceCollection.AddSingleton<IConfiguration>(configuration);
serviceCollection.AddAssemblyAIClient();

var serviceProvider = serviceCollection.BuildServiceProvider();

ClientOptions updatedOptions = new()
{
ApiKey = "UpdatedApiKey",
BaseUrl = "https://api.updated.assemblyai.com",
MaxRetries = 3,
TimeoutInSeconds = 45
};

jsonText = JsonSerializer.Serialize(new { AssemblyAI = updatedOptions });
await File.WriteAllTextAsync(optionsFile, jsonText);

// Simulate waiting for the option change to be detected
await Task.Delay(1000); // This is a simplification. In real tests, use a more reliable method to wait for changes.

var monitor = serviceProvider.GetRequiredService<IOptionsMonitor<ClientOptions>>();
var options = monitor.CurrentValue;

var expectedJson = JsonSerializer.Serialize(updatedOptions);
var actualJson = JsonSerializer.Serialize(options);
Assert.That(actualJson, Is.EqualTo(expectedJson));
}

private static IConfiguration BuildEmptyConfiguration() => new ConfigurationBuilder().Build();
}
55 changes: 55 additions & 0 deletions src/AssemblyAI.Test/DiClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using NUnit.Framework;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace AssemblyAI.Test;

[TestFixture]
public class DiClientTests
{
[Test]
public void AssemblyAIClient_Should_Be_Correctly_Configured_From_DI()
{
// Arrange
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
{ "AssemblyAI:ApiKey", "test_api_key" },
{ "AssemblyAI:BaseUrl", "https://api.test.assemblyai.com" },
{ "AssemblyAI:MaxRetries", "3" },
{ "AssemblyAI:TimeoutInSeconds", "60" }
}!)
.Build();

services.AddSingleton<IConfiguration>(configuration);
services.AddAssemblyAIClient();

// Act
var serviceProvider = services.BuildServiceProvider();
var client = serviceProvider.GetService<AssemblyAIClient>();

// Assert
Assert.That(client, Is.Not.Null);
Assert.That(client.Files, Is.Not.Null);
Assert.That(client.Transcripts, Is.Not.Null);
Assert.That(client.Realtime, Is.Not.Null);
Assert.That(client.Lemur, Is.Not.Null);
}

[Test]
public void AssemblyAIClient_Throws_Exception_When_Configuration_Missing()
{
var services = new ServiceCollection();
var configuration = new ConfigurationBuilder().Build(); // Empty configuration

services.AddSingleton<IConfiguration>(configuration);
services.AddAssemblyAIClient();

var serviceProvider = services.BuildServiceProvider();

var exception = Assert.Throws<OptionsValidationException>(() => serviceProvider.GetService<AssemblyAIClient>());
Assert.That(exception.Message, Is.EqualTo("AssemblyAI:ApiKey is required."));
}
}
10 changes: 9 additions & 1 deletion src/AssemblyAI/AssemblyAI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,18 @@
</PackageReference>
</ItemGroup>


<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net7.0' Or '$(TargetFramework)' == 'net6.0' Or '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="OneOf" Version="3.0.263" />
<PackageReference Include="OneOf.Extended" Version="3.0.263" />
<PackageReference Include="System.Text.Json" Version="8.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>

<ItemGroup>
Expand Down
30 changes: 19 additions & 11 deletions src/AssemblyAI/AssemblyAIClient.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Net.Http;
using AssemblyAI;
using AssemblyAI.Core;

Expand All @@ -7,19 +8,26 @@ namespace AssemblyAI;

public partial class AssemblyAIClient
{
private RawClient _client;

public AssemblyAIClient(string apiKey) : this(apiKey, new ClientOptions())
public AssemblyAIClient(string apiKey) : this(new ClientOptions
{
ApiKey = apiKey
})
{
}

public AssemblyAIClient(string apiKey, ClientOptions clientOptions)
public AssemblyAIClient(ClientOptions clientOptions)
{
_client = new RawClient(
if (string.IsNullOrEmpty(clientOptions.ApiKey))
{
throw new ArgumentException("AssemblyAI API Key is required.");
}

clientOptions.HttpClient ??= new HttpClient();
var client = new RawClient(
new Dictionary<string, string>(),
clientOptions
);
clientOptions.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", apiKey);
clientOptions.HttpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", clientOptions.ApiKey);
clientOptions.HttpClient.DefaultRequestHeaders.Add("X-Fern-Language", "C#");
clientOptions.HttpClient.DefaultRequestHeaders.Add("X-Fern-SDK-Name", "AssemblyAI");
clientOptions.HttpClient.DefaultRequestHeaders.Add("X-Fern-SDK-Version", Constants.Version);
Expand All @@ -29,10 +37,10 @@ public AssemblyAIClient(string apiKey, ClientOptions clientOptions)
clientOptions.UserAgent.ToAssemblyAIUserAgentString());
}

Files = new FilesClient(_client);
Transcripts = new TranscriptsClient(_client);
Realtime = new RealtimeClient(_client);
Lemur = new LemurClient(_client);
Files = new FilesClient(client);
Transcripts = new TranscriptsClient(client);
Realtime = new RealtimeClient(client);
Lemur = new LemurClient(client);
}

public FilesClient Files { get; init; }
Expand All @@ -42,4 +50,4 @@ public AssemblyAIClient(string apiKey, ClientOptions clientOptions)
public RealtimeClient Realtime { get; init; }

public LemurClient Lemur { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#nullable enable

using System.Net.Http;
using AssemblyAI.Core;

#nullable enable

namespace AssemblyAI.Core;
namespace AssemblyAI;

public partial class ClientOptions
public class ClientOptions
{
/// <summary>
/// The AssemblyAI API key
/// </summary>
public required string ApiKey { get; set; }

/// <summary>
/// The Base URL for the API.
/// </summary>
public string BaseUrl { get; init; } = Environments.DEFAULT;
public string BaseUrl { get; set; } = Environments.DEFAULT;

private UserAgent? _userAgent = UserAgent.Default;

Expand All @@ -34,15 +39,15 @@ public UserAgent? UserAgent
/// <summary>
/// The http client used to make requests.
/// </summary>
public HttpClient HttpClient { get; init; } = new HttpClient();
public HttpClient? HttpClient { get; set; }

/// <summary>
/// The http client used to make requests.
/// </summary>
public int MaxRetries { get; init; } = 2;
public int MaxRetries { get; set; } = 2;

/// <summary>
/// The timeout for the request in seconds.
/// </summary>
public int TimeoutInSeconds { get; init; } = 30;
}
public int TimeoutInSeconds { get; set; } = 30;
}
Loading

0 comments on commit 10b4059

Please sign in to comment.