Skip to content

Commit 7d63cf1

Browse files
authored
feat: basic definition of metrics (#175)
1 parent f75fe9d commit 7d63cf1

File tree

8 files changed

+251
-11
lines changed

8 files changed

+251
-11
lines changed

package-delta-schemas.ps1

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
$ErrorActionPreference = 'Stop'
22

3+
$baseUrl = "https://datapackage.org/profiles/2.0/"
34
$schemas = @(
4-
@{ Class = "Packata.Core.TableDialect"; Url = "https://datapackage.org/profiles/2.0/tabledialect.json"; Output = ".\.schemas\tabledialect.json"; Error = "Failed to generate TableDialect schema" },
5-
@{ Class = "Packata.Core.Schema"; Url = "https://datapackage.org/profiles/2.0/tableschema.json"; Output = ".\.schemas\tableschema.json"; Error = "Failed to generate Table schema" },
6-
@{ Class = "Packata.Core.Resource"; Url = "https://datapackage.org/profiles/2.0/dataresource.json"; Output = ".\.schemas\dataresource.json"; Error = "Failed to generate Resource schema" },
7-
@{ Class = "Packata.Core.DataPackage"; Url = "https://datapackage.org/profiles/2.0/datapackage.json"; Output = ".\.schemas\datapackage.json"; Error = "Failed to generate DataPackage schema" }
5+
@{ Class = "Packata.Core.TableDialect"; Value = "tabledialect.json"; Error = "Failed to generate TableDialect schema" },
6+
@{ Class = "Packata.Core.Schema"; Value = "tableschema.json"; Error = "Failed to generate Table schema" },
7+
@{ Class = "Packata.Core.Resource"; Value = "dataresource.json"; Error = "Failed to generate Resource schema" },
8+
@{ Class = "Packata.Core.DataPackage"; Value = "datapackage.json"; Error = "Failed to generate DataPackage schema" }
89
)
910

1011
$assemblyPath = ".\src\Packata.Core\bin\Release\net8.0\Packata.Core.dll"
@@ -18,7 +19,7 @@ $dir = ".\.schemas"
1819
foreach ($schema in $schemas) {
1920
try {
2021
Write-Host "Generating schema for $($schema.Class)..."
21-
schemathief delta -a $assemblyPath -c $schema.Class -b $schema.Url -x "paths|profile" -o $schema.Output
22+
schemathief delta -a $assemblyPath -c $schema.Class -b ($schema.Url + $schema.Value) -x "paths|profile" -o ($dir + $schema.Value)
2223
} catch {
2324
Write-Error $schema.Error
2425
exit 1

src/Packata.Core.Testing/Packata.Core.Testing.csproj

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2+
<ItemGroup>
3+
<Compile Remove="PathHandling\**" />
4+
<EmbeddedResource Remove="PathHandling\**" />
5+
<None Remove="PathHandling\**" />
6+
</ItemGroup>
27
<ItemGroup>
38
<None Remove="Serialization\Json\Resources\example.json" />
9+
<None Remove="Serialization\Json\Resources\extension.json" />
10+
<None Remove="Serialization\Yaml\Resources\extension.yaml" />
411
</ItemGroup>
512
<ItemGroup>
613
<EmbeddedResource Include="Serialization\Json\Resources\example.json" />
14+
<EmbeddedResource Include="Serialization\Json\Resources\extension.json" />
715
<EmbeddedResource Include="Serialization\Yaml\Resources\example.yaml" />
16+
<EmbeddedResource Include="Serialization\Yaml\Resources\extension.yaml" />
817
</ItemGroup>
918
<ItemGroup>
1019
<PackageReference Include="Chrononuensis" Version="0.23.6" />
@@ -38,7 +47,4 @@
3847
<ItemGroup>
3948
<ProjectReference Include="..\..\src\Packata.Core\Packata.Core.csproj" />
4049
</ItemGroup>
41-
<ItemGroup>
42-
<Folder Include="PathHandling\" />
43-
</ItemGroup>
4450
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces;
9+
using Newtonsoft.Json;
10+
using NUnit.Framework;
11+
using Packata.Core.Serialization;
12+
using SerJson = Packata.Core.Serialization.Json;
13+
using SerYaml = Packata.Core.Serialization.Yaml;
14+
using Packata.Core.Storage;
15+
16+
namespace Packata.Core.Testing.Serialization;
17+
18+
public class ExtensionSerializerTests
19+
{
20+
private static Stream GetDataPackageProperties(string format)
21+
{
22+
var uformat = format.ToUpper()[0] + format.Substring(1);
23+
var assembly = Assembly.GetExecutingAssembly();
24+
var resourceName = $"{assembly.GetName().Name}.Serialization.{uformat}.Resources.extension.{format}";
25+
var stream = assembly.GetManifestResourceStream(resourceName)
26+
?? throw new FileNotFoundException($"The embedded file {resourceName} doesn't exist.");
27+
return stream;
28+
}
29+
30+
private static IDataPackageSerializer GetSerializer(string format)
31+
=> format switch
32+
{
33+
"json" => new SerJson.DataPackageSerializer(),
34+
"yaml" => new SerYaml.DataPackageSerializer(),
35+
_ => throw new NotSupportedException($"The format '{format}' is not supported.")
36+
};
37+
38+
public static IEnumerable<(Stream stream, IDataPackageSerializer serializer)> GetData()
39+
{
40+
yield return (GetDataPackageProperties("json"), GetSerializer("json"));
41+
yield return (GetDataPackageProperties("yaml"), GetSerializer("yaml"));
42+
}
43+
44+
[TestCaseSource(nameof(GetData))]
45+
public void Deserialize_Package_Success((Stream Stream, IDataPackageSerializer Serializer) value)
46+
{
47+
using var streamReader = new StreamReader(value.Stream);
48+
var dataPackage = value.Serializer.Deserialize(streamReader, new LocalDirectoryDataPackageContainer(), new StorageProvider());
49+
Assert.That(dataPackage, Is.Not.Null);
50+
Assert.Multiple(() =>
51+
{
52+
Assert.That(dataPackage.Name, Is.EqualTo("extension_package"));
53+
});
54+
}
55+
56+
[TestCaseSource(nameof(GetData))]
57+
public void Deserialize_Metrics_Success((Stream Stream, IDataPackageSerializer Serializer) value)
58+
{
59+
using var streamReader = new StreamReader(value.Stream);
60+
var dataPackage = value.Serializer.Deserialize(streamReader, new LocalDirectoryDataPackageContainer(), new StorageProvider());
61+
Assert.That(dataPackage.Resources[0].Schema?.Metrics, Is.Not.Null);
62+
var metrics = dataPackage.Resources[0].Schema?.Metrics;
63+
Assert.Multiple(() =>
64+
{
65+
Assert.That(metrics, Has.Count.EqualTo(2));
66+
Assert.That(metrics?[0].Name, Is.EqualTo("average_temperature"));
67+
Assert.That(metrics?[0].Type, Is.EqualTo("numeric"));
68+
Assert.That(metrics?[0].Title, Is.EqualTo("Average temperature"));
69+
Assert.That(metrics?[0].Description, Is.EqualTo("Average temperature of the sensor"));
70+
Assert.That(metrics?[0].Aggregation, Is.EqualTo("avg"));
71+
Assert.That(metrics?[0].Expression, Is.EqualTo("temperature"));
72+
});
73+
}
74+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"$schema": "https://packata.dev/profiles/1.0/datapackage.json",
3+
"name": "extension_package",
4+
"id": "116f49c1-8603-463e-a908-68de98327266",
5+
"version": "1.0",
6+
"created": "2025-05-04T12:45:21Z",
7+
"temporal": {
8+
"start": "2020-01-01",
9+
"end": "2021-01-01"
10+
},
11+
"resources": [
12+
{
13+
"name": "measures",
14+
"path": "sensor_measures.csv",
15+
"type": "table",
16+
"$schema": "https://packata.dev/profiles/1.0/dataresource.json",
17+
"title": "Sensors' temperatures & hygrometry",
18+
"format": "csv",
19+
"mediatype": "text/csv",
20+
"encoding": "utf-8",
21+
"schema": {
22+
"fields": [
23+
{
24+
"name": "sensor_id",
25+
"type": "string"
26+
},
27+
{
28+
"name": "building",
29+
"type": "string"
30+
},
31+
{
32+
"name": "manufacturer",
33+
"type": "string"
34+
},
35+
{
36+
"name": "timestamp",
37+
"type": "datetime"
38+
},
39+
{
40+
"name": "temperature",
41+
"type": "numeric"
42+
},
43+
{
44+
"name": "hygrometry",
45+
"type": "numeric"
46+
}
47+
],
48+
"metrics": [
49+
{
50+
"name": "average_temperature",
51+
"type": "numeric",
52+
"title": "Average temperature",
53+
"description": "Average temperature of the sensor",
54+
"aggregation": "avg",
55+
"expression": "temperature"
56+
},
57+
{
58+
"name": "max_temperature",
59+
"type": "numeric",
60+
"title": "Maximum temperature",
61+
"aggregation": "max",
62+
"expression": "temperature"
63+
}
64+
],
65+
"$schema": "https://packata.dev/profiles/1.0/tableschema.json"
66+
}
67+
}
68+
]
69+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"$schema": https://packata.dev/profiles/2.0/datapackage.json
2+
name: extension_package
3+
id: 116f49c1-8603-463e-a908-68de98327266
4+
version: 1.0
5+
created: 2025-05-04T12:45:21Z
6+
temporal:
7+
start: "2020-01-01"
8+
end: "2021-01-01"
9+
resources:
10+
- name: measures
11+
path: sensor_measures.csv
12+
type: table
13+
"$schema": https://packata.dev/profiles/1.0/dataresource.json
14+
title: "Sensors' temperatures & hygrometry"
15+
format: csv
16+
mediatype: text/csv
17+
encoding: utf-8
18+
schema:
19+
fields:
20+
- name: sensor_id
21+
type: string
22+
- name: building
23+
type: string
24+
- name: manufacturer
25+
type: string
26+
- name: timestamp
27+
type: datetime
28+
- name: temperature
29+
type: numeric
30+
- name: hygrometry
31+
type: numeric
32+
metrics:
33+
- name: average_temperature
34+
type: numeric
35+
title: Average temperature
36+
description: Average temperature of the sensor
37+
aggregation: avg
38+
expression: temperature
39+
- name: max_temperature
40+
type: numeric
41+
title: Maximum temperature
42+
aggregation: max
43+
expression: temperature
44+
"$schema": https://packata.dev/profiles/1.0/tableschema.json

src/Packata.Core/Field.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class Field
1313
public string? Name { get; set; }
1414

1515
/// <summary>
16-
/// The type keyword, which identifies the class inheriting from `Field` to deserialize",
16+
/// The type keyword, which identifies the class inheriting from `Field` to deserialize
1717
/// </summary>
1818
public string? Type { get; set; }
1919

@@ -23,12 +23,12 @@ public class Field
2323
public string? Format { get; set; }
2424

2525
/// <summary>
26-
/// "A human-readable title.
26+
/// A human-readable title.
2727
/// </summary>
2828
public string? Title { get; set; }
2929

3030
/// <summary>
31-
/// "A text description. Markdown is encouraged.
31+
/// A text description. Markdown is encouraged.
3232
/// </summary>
3333
public string? Description { get; set; }
3434

src/Packata.Core/Metric.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Packata.Core;
8+
public class Metric
9+
{
10+
/// <summary>
11+
/// The name of the metric.
12+
/// </summary>
13+
public string? Name { get; set; }
14+
15+
/// <summary>
16+
/// The type of the metric.
17+
/// </summary>
18+
public string? Type { get; set; }
19+
20+
/// <summary>
21+
/// A human-readable title.
22+
/// </summary>
23+
public string? Title { get; set; }
24+
25+
/// <summary>
26+
/// A text description. Markdown is encouraged.
27+
/// </summary>
28+
public string? Description { get; set; }
29+
30+
/// <summary>
31+
/// The aggregation function to be used for the metric (e.g. SUM, MAX, COUNT).
32+
/// </summary>
33+
public string? Aggregation { get; set; }
34+
35+
/// <summary>
36+
/// The expression to aggregate the metric. It can be a simple field name or a more complex expression.
37+
/// </summary>
38+
public string? Expression { get; set; }
39+
40+
/// <summary>
41+
/// The dimension(s) to be used for the metric. It can be a single field name or an array of field names.
42+
/// </summary>
43+
public List<string>? Dimensions { get; set; }
44+
}

src/Packata.Core/Schema.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,6 @@ public class Schema
4848
/// {"foreignKeys": [ { "fields": "state", "reference": { "fields": "id" } } ] }
4949
/// ]
5050
public List<ForeignKey>? ForeignKeys { get; set; } = null;
51+
52+
public List<Metric>? Metrics { get; set; }
5153
}

0 commit comments

Comments
 (0)