Skip to content

Commit

Permalink
Support integration of IoT Models Repository metadata in preview SDK (#…
Browse files Browse the repository at this point in the history
…22521)

* Consumes new service metadata capability outlined [here](https://github.com/Azure/iot-plugandplay-models-tools/wiki/Publishing-Metadata).
  * Metadata for the global endpoint resides [here](https://devicemodels.azure.com/metadata.json).
  * Metadata is optional. Failure in processing repository metadata should not be a terminal failure.
* Simplifies client API surface area:
   * Removes model dependency resolution configuration in the `ModelsRepositoryClientOptions` (and the pattern of the client constructor setting a default resolution option).
   *  Removes `ModelDependencyResolution.TryFromExpanded`. TryFromExpanded becomes an internal processing concept. The metadata of a target repository will indicate if expanded model forms are available.
   * Model dependency resolution configuration becomes scoped to the service operation level. The `GetModels[Async]` service operations have their `dependencyResolution` parameter set to `ModelDependencyResolution.Enabled` by default.
   * Updates to tests and samples documentation reflecting the prior mentioned changes.
* Resolve PR feedback.
  • Loading branch information
digimaun authored Jul 12, 2021
1 parent 7903fd7 commit 3321330
Show file tree
Hide file tree
Showing 85 changed files with 5,878 additions and 614 deletions.
8 changes: 8 additions & 0 deletions sdk/modelsrepository/Azure.IoT.ModelsRepository/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## 1.0.0-preview.4 (Unreleased)

- Consumes new service metadata capability
- Simplifies client API surface area:
- Removes model dependency resolution configuration in the `ModelsRepositoryClientOptions`
(and the pattern of the client constructor setting a default resolution option).
- Removes `ModelDependencyResolution.TryFromExpanded`. TryFromExpanded becomes an internal processing concept.
The metadata of a target repository will indicate if expanded model forms are available.
- Model dependency resolution configuration becomes scoped to the service operation level.
The `GetModels[Async]` service operations have their `dependencyResolution` parameter set to `ModelDependencyResolution.Enabled` by default.

## 1.0.0-preview.3 (2021-04-12)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,21 @@ public enum ModelDependencyResolution
{
Disabled = 0,
Enabled = 1,
TryFromExpanded = 2,
}
public partial class ModelsRepositoryClient
{
public ModelsRepositoryClient() { }
public ModelsRepositoryClient(Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions options) { }
public ModelsRepositoryClient(System.Uri repositoryUri, Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions options = null) { }
public System.Uri RepositoryUri { get { throw null; } }
public virtual System.Collections.Generic.IDictionary<string, string> GetModels(System.Collections.Generic.IEnumerable<string> dtmis, Azure.IoT.ModelsRepository.ModelDependencyResolution? dependencyResolution = default(Azure.IoT.ModelsRepository.ModelDependencyResolution?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Collections.Generic.IDictionary<string, string> GetModels(string dtmi, Azure.IoT.ModelsRepository.ModelDependencyResolution? dependencyResolution = default(Azure.IoT.ModelsRepository.ModelDependencyResolution?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, string>> GetModelsAsync(System.Collections.Generic.IEnumerable<string> dtmis, Azure.IoT.ModelsRepository.ModelDependencyResolution? dependencyResolution = default(Azure.IoT.ModelsRepository.ModelDependencyResolution?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, string>> GetModelsAsync(string dtmi, Azure.IoT.ModelsRepository.ModelDependencyResolution? dependencyResolution = default(Azure.IoT.ModelsRepository.ModelDependencyResolution?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Collections.Generic.IDictionary<string, string> GetModels(System.Collections.Generic.IEnumerable<string> dtmis, Azure.IoT.ModelsRepository.ModelDependencyResolution dependencyResolution = Azure.IoT.ModelsRepository.ModelDependencyResolution.Enabled, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Collections.Generic.IDictionary<string, string> GetModels(string dtmi, Azure.IoT.ModelsRepository.ModelDependencyResolution dependencyResolution = Azure.IoT.ModelsRepository.ModelDependencyResolution.Enabled, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, string>> GetModelsAsync(System.Collections.Generic.IEnumerable<string> dtmis, Azure.IoT.ModelsRepository.ModelDependencyResolution dependencyResolution = Azure.IoT.ModelsRepository.ModelDependencyResolution.Enabled, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public virtual System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, string>> GetModelsAsync(string dtmi, Azure.IoT.ModelsRepository.ModelDependencyResolution dependencyResolution = Azure.IoT.ModelsRepository.ModelDependencyResolution.Enabled, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ModelsRepositoryClientOptions : Azure.Core.ClientOptions
{
public ModelsRepositoryClientOptions(Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions.ServiceVersion version = Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions.ServiceVersion.V2021_02_11, Azure.IoT.ModelsRepository.ModelDependencyResolution dependencyResolution = Azure.IoT.ModelsRepository.ModelDependencyResolution.Enabled) { }
public Azure.IoT.ModelsRepository.ModelDependencyResolution DependencyResolution { get { throw null; } }
public ModelsRepositoryClientOptions(Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions.ServiceVersion version = Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions.ServiceVersion.V2021_02_11) { }
public Azure.IoT.ModelsRepository.ModelsRepositoryClientOptions.ServiceVersion Version { get { throw null; } }
public enum ServiceVersion
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,26 @@ public static void ClientInitialization()
#region Snippet:ModelsRepositorySamplesCreateServiceClientWithGlobalEndpoint

// When no URI is provided for instantiation, the Azure IoT Models Repository global endpoint
// https://devicemodels.azure.com/ is used and the model dependency resolution
// configuration is set to TryFromExpanded.
// https://devicemodels.azure.com/ is used.
var client = new ModelsRepositoryClient(new ModelsRepositoryClientOptions());
Console.WriteLine($"Initialized client pointing to global endpoint: {client.RepositoryUri}");
Console.WriteLine($"Initialized client pointing to global endpoint: {client.RepositoryUri.AbsoluteUri}");

#endregion Snippet:ModelsRepositorySamplesCreateServiceClientWithGlobalEndpoint

#region Snippet:ModelsRepositorySamplesCreateServiceClientWithCustomEndpoint

// This form shows specifing a custom URI for the models repository with default client options.
// The default client options will enable model dependency resolution.
const string remoteRepoEndpoint = "https://contoso.com/models";
client = new ModelsRepositoryClient(new Uri(remoteRepoEndpoint));
Console.WriteLine($"Initialized client pointing to custom endpoint: {client.RepositoryUri}");
Console.WriteLine($"Initialized client pointing to custom endpoint: {client.RepositoryUri.AbsoluteUri}");

#endregion Snippet:ModelsRepositorySamplesCreateServiceClientWithCustomEndpoint

#region Snippet:ModelsRepositorySamplesCreateServiceClientWithLocalRepository

// The client will also work with a local filesystem URI. This example shows initalization
// with a local URI and disabling model dependency resolution.
client = new ModelsRepositoryClient(new Uri(ClientSamplesLocalModelsRepository),
new ModelsRepositoryClientOptions(dependencyResolution: ModelDependencyResolution.Disabled));
Console.WriteLine($"Initialized client pointing to local path: {client.RepositoryUri}");
// The client will also work with a local filesystem URI.
client = new ModelsRepositoryClient(new Uri(ClientSamplesLocalModelsRepository));
Console.WriteLine($"Initialized client pointing to local path: {client.RepositoryUri.LocalPath}");

#endregion Snippet:ModelsRepositorySamplesCreateServiceClientWithLocalRepository
}
Expand All @@ -65,6 +64,28 @@ public static async Task GetModelsFromGlobalRepoAsync()
#endregion Snippet:ModelsRepositorySamplesGetModelsFromGlobalRepoAsync
}

public static async Task GetModelsDisabledDependencyResolution()
{
#region Snippet:ModelsRepositorySamplesGetModelsDisabledDependencyResolution

// Global endpoint client
var client = new ModelsRepositoryClient();

// In this example model dependency resolution is disabled by passing in ModelDependencyResolution.Disabled
// as the value for the dependencyResolution parameter of GetModelsAsync(). By default the parameter has a value
// of ModelDependencyResolution.Enabled.
// When model dependency resolution is disabled, only the input dtmi(s) will be processed and
// model dependencies (if any) will be ignored.
var dtmi = "dtmi:com:example:TemperatureController;1";
IDictionary<string, string> models = await client.GetModelsAsync(dtmi, ModelDependencyResolution.Disabled).ConfigureAwait(false);

// In this case the above dtmi has 2 model dependencies but are not returned
// due to disabling model dependency resolution.
Console.WriteLine($"{dtmi} resolved in {models.Count} interfaces.");

#endregion Snippet:ModelsRepositorySamplesGetModelsDisabledDependencyResolution
}

public static async Task GetMultipleModelsFromGlobalRepoAsync()
{
#region Snippet:ModelsRepositorySamplesGetMultipleModelsFromGlobalRepoAsync
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ internal static class ModelsRepositoryClientExtensions
public static async Task<IEnumerable<string>> ParserDtmiResolver(this ModelsRepositoryClient client, IReadOnlyCollection<Dtmi> dtmis)
{
IEnumerable<string> dtmiStrings = dtmis.Select(s => s.AbsoluteUri);
IDictionary<string, string> result = await client.GetModelsAsync(dtmiStrings);

IDictionary<string, string> result = await client.GetModelsAsync(dtmiStrings, ModelDependencyResolution.Disabled);
return result.Values.ToList();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static async Task ParseAndGetModelsWithExtensionAsync()
{
#region Snippet:ModelsRepositorySamplesParserIntegrationParseAndGetModelsAsync

var client = new ModelsRepositoryClient(new ModelsRepositoryClientOptions(dependencyResolution: ModelDependencyResolution.Disabled));
var client = new ModelsRepositoryClient();
var dtmi = "dtmi:com:example:TemperatureController;1";
IDictionary<string, string> models = await client.GetModelsAsync(dtmi).ConfigureAwait(false);
var parser = new ModelParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static async Task Main(string[] args)
await ModelResolutionSamples.GetModelsFromGlobalRepoAsync();
await ModelResolutionSamples.GetModelsFromLocalRepoAsync();
await ModelResolutionSamples.GetMultipleModelsFromGlobalRepoAsync();
await ModelResolutionSamples.GetModelsDisabledDependencyResolution();
await ModelResolutionSamples.TryGetModelsFromGlobalRepoButNotFoundAsync();
await ModelResolutionSamples.TryGetModelsFromLocalRepoButNotFoundAsync();
await ModelResolutionSamples.TryGetModelsWithInvalidDtmiAsync();
Expand Down
43 changes: 34 additions & 9 deletions sdk/modelsrepository/Azure.IoT.ModelsRepository/samples/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,22 @@ The samples project demonstrates the following:

```C# Snippet:ModelsRepositorySamplesCreateServiceClientWithGlobalEndpoint
// When no URI is provided for instantiation, the Azure IoT Models Repository global endpoint
// https://devicemodels.azure.com/ is used and the model dependency resolution
// configuration is set to TryFromExpanded.
// https://devicemodels.azure.com/ is used.
var client = new ModelsRepositoryClient(new ModelsRepositoryClientOptions());
Console.WriteLine($"Initialized client pointing to global endpoint: {client.RepositoryUri}");
Console.WriteLine($"Initialized client pointing to global endpoint: {client.RepositoryUri.AbsoluteUri}");
```

```C# Snippet:ModelsRepositorySamplesCreateServiceClientWithCustomEndpoint
// This form shows specifing a custom URI for the models repository with default client options.
const string remoteRepoEndpoint = "https://contoso.com/models";
client = new ModelsRepositoryClient(new Uri(remoteRepoEndpoint));
Console.WriteLine($"Initialized client pointing to custom endpoint: {client.RepositoryUri.AbsoluteUri}");
```

```C# Snippet:ModelsRepositorySamplesCreateServiceClientWithLocalRepository
// The client will also work with a local filesystem URI. This example shows initalization
// with a local URI and disabling model dependency resolution.
client = new ModelsRepositoryClient(new Uri(ClientSamplesLocalModelsRepository),
new ModelsRepositoryClientOptions(dependencyResolution: ModelDependencyResolution.Disabled));
Console.WriteLine($"Initialized client pointing to local path: {client.RepositoryUri}");
// The client will also work with a local filesystem URI.
client = new ModelsRepositoryClient(new Uri(ClientSamplesLocalModelsRepository));
Console.WriteLine($"Initialized client pointing to local path: {client.RepositoryUri.LocalPath}");
```

### Override options
Expand Down Expand Up @@ -103,6 +107,27 @@ IDictionary<string, string> models = await client.GetModelsAsync(dtmis).Configur
Console.WriteLine($"Dtmis {string.Join(", ", dtmis)} resolved in {models.Count} interfaces.");
```

By default model dependency resolution is enabled. This can be changed by overriding the default
value for the `dependencyResolution` parameter of the `GetModels` operation.

```C# Snippet:ModelsRepositorySamplesGetModelsDisabledDependencyResolution
// Global endpoint client
var client = new ModelsRepositoryClient();

// In this example model dependency resolution is disabled by passing in ModelDependencyResolution.Disabled
// as the value for the dependencyResolution parameter of GetModelsAsync(). By default the parameter has a value
// of ModelDependencyResolution.Enabled.
// When model dependency resolution is disabled, only the input dtmi(s) will be processed and
// model dependencies (if any) will be ignored.
var dtmi = "dtmi:com:example:TemperatureController;1";
IDictionary<string, string> models = await client.GetModelsAsync(dtmi, ModelDependencyResolution.Disabled).ConfigureAwait(false);

// In this case the above dtmi has 2 model dependencies but are not returned
// due to disabling model dependency resolution.
Console.WriteLine($"{dtmi} resolved in {models.Count} interfaces.");
```


## Digital Twins Model Parser Integration

The samples provide two different patterns to integrate with the Digital Twins Model Parser.
Expand All @@ -122,7 +147,7 @@ Alternatively, the following snippet shows parsing a model, then fetching depend
This is achieved by configuring the `ModelParser` to use the sample [ParserDtmiResolver][modelsrepository_sample_extension] client extension.

```C# Snippet:ModelsRepositorySamplesParserIntegrationParseAndGetModelsAsync
var client = new ModelsRepositoryClient(new ModelsRepositoryClientOptions(dependencyResolution: ModelDependencyResolution.Disabled));
var client = new ModelsRepositoryClient();
var dtmi = "dtmi:com:example:TemperatureController;1";
IDictionary<string, string> models = await client.GetModelsAsync(dtmi).ConfigureAwait(false);
var parser = new ModelParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ public static Uri GetModelUri(string dtmi, Uri repositoryUri, bool expanded = fa
return new Uri(repositoryUriBuilder.Uri, dtmiPath);
}

internal static Uri GetMetadataUri(Uri repositoryUri)
{
var repositoryUriBuilder = new UriBuilder(repositoryUri);

// If the base URI (repositoryUri in this case) path segment does not end in slash
// that segment will be dropped and not properly anchored to by the intended relative URI.
if (!repositoryUriBuilder.Path.EndsWith("/", StringComparison.InvariantCultureIgnoreCase))
{
repositoryUriBuilder.Path += "/";
}
return new Uri(repositoryUriBuilder.Uri, ModelsRepositoryConstants.ModelsRepositoryMetadataFile);
}

internal static string DtmiToPath(string dtmi) => IsValidDtmi(dtmi) ? $"{dtmi.ToLowerInvariant().Replace(":", "/").Replace(";", "-")}.json" : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace Azure.IoT.ModelsRepository.Fetchers
{
/// <summary>
/// The FetchResult class has the purpose of containing key elements of
/// an IModelFetcher Fetch() operation including model definition, path and whether
/// an IModelFetcher FetchModel() operation including model definition, path and whether
/// it was from an expanded (pre-calculated) fetch.
/// </summary>
internal class FetchResult
internal class FetchModelResult
{
public string Definition { get; set; }

Expand Down
Loading

0 comments on commit 3321330

Please sign in to comment.