Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [Python] Add support for [<Py.Decorate>] attribute on methods (previously only worked on classes)
* [Python] Add new [<Py.ClassMethod>] attribute to emit @classmethod instead of @staticmethod
* [Python] Added support for Pydantic serialization of core numeric and array types (by @dbrattli)

## 5.0.0-alpha.18 - 2025-12-03
Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [Python] Add support for [<Py.Decorate>] attribute on methods (previously only worked on classes)
* [Python] Add new [<Py.ClassMethod>] attribute to emit @classmethod instead of @staticmethod
* [Python] Added support for Pydantic serialization of core numeric and array types (by @dbrattli)

## 5.0.0-alpha.17 - 2025-12-03
Expand Down
5 changes: 5 additions & 0 deletions src/Fable.Core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

* [Python] Add support for [<Py.Decorate>] attribute on methods (previously only worked on classes)
* [Python] Add new [<Py.ClassMethod>] attribute to emit @classmethod instead of @staticmethod

## 5.0.0-beta.2 - 2025-11-19

### Added
Expand Down
26 changes: 22 additions & 4 deletions src/Fable.Core/Fable.Core.Py.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
abstract Decorate: fn: Callable * info: Reflection.MethodInfo -> Callable

/// <summary>
/// Adds Python decorators to generated classes, enabling integration with Python
/// frameworks like dataclasses, attrs, functools, and any other decorator-based
/// Adds Python decorators to generated classes or methods, enabling integration with Python
/// frameworks like dataclasses, attrs, functools, Pydantic, and any other decorator-based
/// libraries.
/// </summary>
/// <remarks>
Expand All @@ -38,16 +38,34 @@
/// <para>Multiple [&lt;Decorate&gt;] attributes are applied in reverse order
/// (bottom to top), following Python's standard decorator stacking behavior.</para>
/// <para>Examples:</para>
/// <para>[&lt;Decorate("dataclasses.dataclass")&gt;] - Simple decorator</para>
/// <para>[&lt;Decorate("dataclasses.dataclass")&gt;] - Simple class decorator</para>
/// <para>[&lt;Decorate("functools.lru_cache", "maxsize=128")&gt;] - Decorator with
/// parameters</para>
/// <para>[&lt;Decorate("pydantic.field_validator", "'Name'")&gt;] - Method decorator for
/// Pydantic validators</para>
/// </remarks>
[<AttributeUsage(AttributeTargets.Class, AllowMultiple = true)>]
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Method, AllowMultiple = true)>]
type DecorateAttribute(decorator: string) =
inherit Attribute()
new(decorator: string, parameters: string) = DecorateAttribute(decorator)

/// <summary>
/// Marks a static member to be emitted as a Python @classmethod instead of @staticmethod.
/// </summary>
/// <remarks>
/// <para>Use this attribute on static members when you need the first parameter to receive
/// the class (cls) instead of being a regular static method.</para>
/// <para>This is commonly needed for Pydantic validators and other Python frameworks that
/// require @classmethod decorators.</para>
/// <para>Example:</para>
/// <para>[&lt;Py.ClassMethod&gt;]</para>
/// <para>static member validate_name(cls, v: string) = ...</para>
/// </remarks>
[<AttributeUsage(AttributeTargets.Method)>]
type ClassMethodAttribute() =
inherit Attribute()

[<RequireQualifiedAccess>]

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-dart

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-dart

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-dart

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-dart

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / analyzers (src/Fable.AST/Fable.AST.fsproj)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-typescript

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-typescript

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-typescript

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-typescript

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / analyzers (src/Fable.Transforms/Rust/AST/Rust.AST.fsproj)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / analyzers (src/Fable.Compiler/Fable.Compiler.fsproj)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (threaded)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (threaded)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (threaded)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (threaded)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-integration

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (no_std)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (no_std)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (no_std)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (no_std)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (default)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (default)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (default)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-rust (default)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / analyzers (src/Fable.Transforms/Fable.Transforms.fsproj)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-javascript (ubuntu-latest)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-javascript (ubuntu-latest)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-standalone

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-standalone

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-standalone

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-standalone

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-javascript (windows-latest)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-javascript (windows-latest)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.12)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class

Check warning on line 68 in src/Fable.Core/Fable.Core.Py.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This attribute cannot be applied to enum. Valid targets are: class
type ClassAttributeStyle =
// Translates to properties with instance attributes backing
| Properties = 0
Expand Down
31 changes: 26 additions & 5 deletions src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2906,8 +2906,14 @@ let transformAttachedProperty
[]
else
// Instance properties use the traditional approach (properties style)
// Get custom decorators from Py.Decorate attributes
let customDecorators =
let decoratorInfos = Util.getDecoratorInfo info.Attributes
Util.generateDecorators com ctx decoratorInfos

let decorators =
[
customDecorators
@ [
if isGetter then
Expression.name "property"
else
Expand Down Expand Up @@ -2947,11 +2953,26 @@ let transformAttachedMethod (com: IPythonCompiler) ctx (info: Fable.MemberFuncti

let isStatic = not info.IsInstance

// Check if this method has Py.ClassMethod attribute
let hasClassMethodAttribute =
info.Attributes
|> Seq.exists (fun att -> att.Entity.FullName = Atts.pyClassMethod)

// Get custom decorators from Py.Decorate attributes
let customDecorators =
let decoratorInfos = Util.getDecoratorInfo info.Attributes
Util.generateDecorators com ctx decoratorInfos

let decorators =
if isStatic then
[ Expression.name "staticmethod" ]
else
[]
// Custom decorators come first (outermost), then static/classmethod decorator
customDecorators
@ if isStatic then
if hasClassMethodAttribute then
[ Expression.name "classmethod" ]
else
[ Expression.name "staticmethod" ]
else
[]

let makeMethod name args body decorators returnType =
let key = memberFromName com ctx name |> nameFromKey com ctx
Expand Down
3 changes: 3 additions & 0 deletions src/Fable.Transforms/Transforms.Util.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ module Atts =
[<Literal>]
let pyClassAttributes = "Fable.Core.Py.ClassAttributes" // typeof<Fable.Core.Py.ClassAttributes>.FullName

[<Literal>]
let pyClassMethod = "Fable.Core.Py.ClassMethodAttribute" // typeof<Fable.Core.Py.ClassMethodAttribute>.FullName

[<Literal>]
let dartIsConst = "Fable.Core.Dart.IsConstAttribute" // typeof<Fable.Core.Dart.IsConstAttribute>.FullName

Expand Down
5 changes: 0 additions & 5 deletions src/fable-library-py/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1339,11 +1339,6 @@ def test_indexed():
# assert abs(result3 - 6.0) < 1e-10


# =============================================================================
# Pydantic Integration Tests
# =============================================================================


def test_pydantic_array_as_field_type():
"""Test that FSharpArray can be used directly as a Pydantic field type."""

Expand Down
102 changes: 102 additions & 0 deletions tests/Python/TestPyInterop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,24 @@ let ``test PydanticUser with byte and int16 types`` () =
model.Int16Val |> equal -32768s
model.UInt16Val |> equal 65535us

// Test Pydantic field_validator with @classmethod
[<Py.ClassAttributes(style=Py.ClassAttributeStyle.Attributes, init=false)>]
type PydanticUserWithValidator(Name: string) =
inherit BaseModel()
member val Name: string = Name with get, set

[<Py.Decorate("pydantic.field_validator", "'Name'")>]
[<Py.ClassMethod>]
static member validate_name(cls: obj, v: string) : string =
v.ToUpper()

[<Fact>]
let ``test Pydantic field_validator with classmethod`` () =
// Test that @field_validator and @classmethod decorators are applied correctly
// The validator should transform the Name to uppercase
let user = emitPyExpr<PydanticUserWithValidator> [] "PydanticUserWithValidator(Name='john')"
user.Name |> equal "JOHN"

[<Py.Decorate("dataclasses.dataclass")>]
[<Py.ClassAttributes(style=Py.ClassAttributeStyle.Attributes, init=false)>]
type DecoratedUser() =
Expand Down Expand Up @@ -590,6 +608,90 @@ let ``test PropertiesUserWithInit`` () =
user.Email |> equal (Some "[email protected]")
user.Enabled |> equal true

// Test Py.Decorate on static methods

[<AttachMembers>]
type ClassWithDecoratedStaticMethod() =
member val Value: int = 0 with get, set

[<Py.Decorate("functools.lru_cache")>]
static member cached_function(x: int) : int =
x * 2

[<Fact>]
let ``test Py.Decorate on static method`` () =
// Test that @lru_cache decorator is applied to static method
let result1 = ClassWithDecoratedStaticMethod.cached_function(5)
let result2 = ClassWithDecoratedStaticMethod.cached_function(5)
result1 |> equal 10
result2 |> equal 10

[<AttachMembers>]
type ClassWithDecoratedStaticMethodWithParams() =
[<Py.Decorate("functools.lru_cache", "maxsize=32")>]
static member cached_with_params(x: int) : int =
x * 3

[<Fact>]
let ``test Py.Decorate on static method with parameters`` () =
// Test that decorator with parameters is applied to static method
let result = ClassWithDecoratedStaticMethodWithParams.cached_with_params(4)
result |> equal 12

[<AttachMembers>]
type ClassWithClassMethod() =
member val Name: string = "" with get, set

// Note: With @classmethod, Python automatically passes `cls` as first argument
// So from F# we only pass the remaining arguments
[<Py.ClassMethod>]
static member create_instance(cls: obj, name: string) : ClassWithClassMethod =
let instance = ClassWithClassMethod()
instance.Name <- name
instance

[<Fact>]
let ``test Py.ClassMethod attribute`` () =
// Test that @classmethod decorator is applied instead of @staticmethod
// Note: cls is automatically passed by Python, so we only pass the name argument
// We use Emit to call the method without the cls argument from F#
let instance = emitPyExpr<ClassWithClassMethod> [] "ClassWithClassMethod.create_instance('TestName')"
instance.Name |> equal "TestName"


[<AttachMembers>]
type ClassWithMultipleDecoratedMethods() =
[<Py.Decorate("functools.lru_cache", "maxsize=16")>]
static member method_a(x: int) : int = x * 2

[<Py.Decorate("functools.lru_cache", "maxsize=32")>]
static member method_b(x: int) : int = x * 3

[<Fact>]
let ``test multiple static methods with decorators`` () =
// Test that decorators work on multiple static methods in same class
let resultA = ClassWithMultipleDecoratedMethods.method_a(5)
let resultB = ClassWithMultipleDecoratedMethods.method_b(5)
resultA |> equal 10
resultB |> equal 15

[<AttachMembers>]
type ClassWithDecoratedInstanceMethod() =
member val CallCount: int = 0 with get, set

[<Py.Decorate("functools.lru_cache", "maxsize=16")>]
member this.cached_instance_method(x: int) : int =
x * 4

[<Fact>]
let ``test Py.Decorate on instance method`` () =
// Test that decorator works on instance methods too
let obj = ClassWithDecoratedInstanceMethod()
let result1 = obj.cached_instance_method(3)
let result2 = obj.cached_instance_method(3)
result1 |> equal 12
result2 |> equal 12

// Import fable_library version to verify we're using local build, not PyPI
let fableLibraryVersion: string = import "__version__" "fable_library._version"

Expand Down
Loading