Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*.yml text

# Code
*.py text
*.manifest text
*.vsixmanifest text
*.vstemplate text
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/Harp.Generators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ jobs:
- name: Test .NET 8
run: dotnet test --no-restore --no-build --configuration ${{matrix.configuration}} --verbosity normal --framework net8.0

# ----------------------------------------------------------------------- Python interop test
# The .NET test run generates Harp binaries from the auto-generated C# interface; this step
# reads them back through the auto-generated Python interface to verify the two stacks agree.
- name: Set up uv
if: matrix.configuration == 'release'
uses: astral-sh/setup-uv@v5

- name: Test Python interop
if: matrix.configuration == 'release'
env:
HARP_INTEROP_OUTPUT: ${{github.workspace}}/artifacts/python-interop
run: uv run --project tests/Python pytest tests/Python/test_interop.py

# ----------------------------------------------------------------------- Collect artifacts
- name: Collect NuGet packages
uses: actions/upload-artifact@v4
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
.vs/
/artifacts/

# Python
__pycache__/
*.py[cod]
uv.lock
193 changes: 193 additions & 0 deletions src/PyDevice.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<#@ template debug="true" hostspecific="false" visibility="internal" language="C#" inherits="TemplateBase" #>
<#@ parameter name="DeviceMetadata" type="DeviceInfo" #>
<#@ import namespace="Harp.Generators" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".py" #>
<#
var module = TemplateHelper.BuildPythonModule(DeviceMetadata);
var usesNumpy = module.Payloads.Count > 0;
#>
# This file was automatically generated and should not be edited directly.
# To make changes, edit the device metadata and regenerate the interface.

<#
if (module.Enums.Count > 0)
{
#>
import enum
<#
}
#>
from typing import Any, ClassVar
<#
if (usesNumpy)
{
#>

import numpy as np
<#
}
if (module.UsesNDArray)
{
#>
from numpy.typing import NDArray
<#
}
#>
from harp.protocol import (
<#
foreach (var import in module.ProtocolImports)
{
#>
<#= import #>,
<#
}
#>
)
<#
if (module.IsApplicationDevice)
{
#>
from harp.device import REGISTER_MAP as _CORE_REGISTER_MAP
<#
}
if (module.ExtensionImports.Count > 0)
{
#>

from .converters import (
<#
foreach (var import in module.ExtensionImports)
{
#>
<#= import #>,
<#
}
#>
)
<#
}
#>
<#
foreach (var pythonEnum in module.Enums)
{
#>


class <#= pythonEnum.Name #>(enum.<#= pythonEnum.IsFlag ? "IntFlag" : "IntEnum" #>):
<#
if (!string.IsNullOrEmpty(pythonEnum.Description))
{
#>
"""<#= pythonEnum.Description #>"""

<#
}
foreach (var member in pythonEnum.Members)
{
#>
<#= member.Name #> = <#= member.Value #>
<#
if (!string.IsNullOrEmpty(member.Description))
{
#>
"""<#= member.Description #>"""
<#
}
}
}
#>
<#
foreach (var payload in module.Payloads)
{
#>


<#
if (payload.IsAnonymous)
{
#>
class <#= payload.Name #>(AnonymousPayload[<#= payload.ElementType #>]):
<#
}
else
{
var length = payload.Length.HasValue ? $", length={payload.Length.Value}" : string.Empty;
#>
class <#= payload.Name #>(StructPayload[<#= payload.ElementType #>]<#= length #>):
<#
}
if (!string.IsNullOrEmpty(payload.Description))
{
#>
"""<#= payload.Description #>"""
<#
if (payload.Fields.Count > 0) WriteLine("");
}
foreach (var field in payload.Fields)
{
#>
<#= field.Name #>: <#= field.Annotation #> = <#= field.Descriptor #>
<#
if (!string.IsNullOrEmpty(field.Description))
{
#>
"""<#= field.Description #>"""
<#
}
}
}
#>
<#
foreach (var register in module.Registers)
{
#>


class <#= register.Name #>(<#= register.BaseClass #>):
<#
if (!string.IsNullOrEmpty(register.Description))
{
#>
"""<#= register.Description #>"""

<#
}
#>
address: ClassVar[int] = <#= register.Address #>
<#
if (register.Kind == PythonRegisterKind.Array)
{
#>
length: ClassVar[int] = <#= register.Length #>
<#
}
else if (register.Kind == PythonRegisterKind.Struct)
{
#>
payload_type: ClassVar[PayloadType] = <#= register.PayloadType #>
payload_class = <#= register.PayloadClass #>
<#
}
}
#>


REGISTER_MAP: dict[int, type[RegisterBase[Any]]] = {
<#
if (module.IsApplicationDevice)
{
#>
**_CORE_REGISTER_MAP,
<#
}
foreach (var register in module.Registers)
{
#>
<#= register.Address #>: <#= register.Name #>,
<#
}
#>
}

Loading
Loading