From 019cf3a1c04dbdc8bd3a081fa15e563c0fc7c567 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Sun, 16 Nov 2025 16:48:06 +0100
Subject: [PATCH 1/8] docs: Update docs for Python in Fable v5
---
docs/docs/python/build-and-run.md | 22 +++-
docs/docs/python/features.md | 176 ++++++++++++++++++++++++++----
2 files changed, 174 insertions(+), 24 deletions(-)
diff --git a/docs/docs/python/build-and-run.md b/docs/docs/python/build-and-run.md
index 1c89b71d..f164a9ba 100644
--- a/docs/docs/python/build-and-run.md
+++ b/docs/docs/python/build-and-run.md
@@ -3,10 +3,30 @@ title: Build and Run
layout: standard
---
-When targetting python, you can use the output of Fable directly by running it with the python interpreter.
+## Python Version
+
+Fable targets Python 3.12 or higher. Python 3.14 is also supported.
+
+Python 3.10 and 3.11 are deprecated.
+
+## Running Python Code
+
+When targeting python, you can use the output of Fable directly by running it with the python interpreter.
For example:
```bash
python3 Program.py
```
+
+## Publishing to PyPI
+
+When building a library that uses Fable Python and you want to publish it to PyPI, you cannot bundle the Fable library the normal way. This is because the Fable library is partially written in Rust and needs to be built for all architectures.
+
+Instead, use the `--fableLib` option to reference a pre-built Fable library:
+
+```bash
+fable --lang python --fableLib fable-library
+```
+
+This will make any reference to the Fable library point to a package in the Python search path (e.g., site-packages) instead of the normally bundled library. Your package will then need to declare `fable-library` as a dependency so users can install it from PyPI.
diff --git a/docs/docs/python/features.md b/docs/docs/python/features.md
index c04858a9..c66075a9 100644
--- a/docs/docs/python/features.md
+++ b/docs/docs/python/features.md
@@ -18,7 +18,7 @@ Python target is in beta meaning that breaking changes can happen between minor
When targetting Python, Fable will automatically convert F# camelCase names to Python snake_case names.
```fs
-let addTwoNumber x y =
+let addTwoNumber x y =
x + y
```
@@ -29,6 +29,36 @@ def add_two_number(x: int, y: int) -> int:
return x + y
```
+Records snake-case all member fields:
+
+
+ Added in v5.0.0-alpha.14
+
+
+```fs
+type User = { FirstName: string }
+```
+
+generates:
+
+```py
+class User:
+ def __init__(self, first_name: str):
+ self.first_name = first_name
+```
+
+Anonymous records preserve original casing:
+
+```fs
+let user = {| FirstName = "John" |}
+```
+
+generates:
+
+```py
+user = {"FirstName": "John"}
+```
+
### `nativeOnly`
`nativeOnly` provide a dummy implementation that we use when writing bindings.
@@ -37,7 +67,7 @@ Here is its definition:
```fs
/// Alias of nativeOnly
- let inline nativeOnly<'T> : 'T =
+ let inline nativeOnly<'T> : 'T =
failwith "You've hit dummy code used for Fable bindings. This probably means you're compiling Fable code to .NET by mistake, please check."
```
@@ -80,7 +110,7 @@ There are 2 families of imports:
- Attribute-based imports
- Function-based imports
-They archieve the same goal, but with a slightly generated code.
+They achieve the same goal, but with a slightly generated code.
```fs
[]
@@ -107,7 +137,7 @@ say_hello: Callable[[], None] = say_hello_1
say_hello()
```
-Using the attribute-based imports is recommanded as it generates a smaller output.
+Using the attribute-based imports is recommended as it generates a smaller output.
### Attributes
@@ -211,7 +241,7 @@ This function takes two parameters:
let hi : unit -> unit = import "say_hello" "hello"
hi()
-// Generates:
+// Generates:
// from typing import Callable
// from hello import say_hello as say_hello_1
// say_hello: Callable[[], None] = say_hello_1
@@ -231,7 +261,7 @@ let hi : unit -> unit = importAll "./hello.js"
#### `importMember`
-`importMember` is used to import a specific member from a JavaScript module, the name is based on the name of the F# value.
+`importMember` is used to import a specific member from a Python module, the name is based on the name of the F# value.
```fs
let say_hello : unit -> unit = importMember "hello"
@@ -247,6 +277,16 @@ let sayHello : unit -> unit = importMember "hello"
// say_hello: Callable[[], None] = say_hello_1
```
+#### `importSideEffects`
+
+`importSideEffects` is used when you want to import a Python module for its side effects only.
+
+```fs
+importSideEffects "some_module"
+// Generates:
+// import some_module
+```
+
## Emit, when F# is not enough
Emit is a features offered by Fable, that allows you to write Python code directly in F#.
@@ -265,7 +305,7 @@ When using `emit`, you can use placeholders like `$0`, `$1`, `$2`, ... to refere
### `[]`
-You can use the `Emit` attribute to decorate function, methods,
+You can use the `Emit` attribute to decorate function, methods,
```fs
[]
@@ -343,11 +383,11 @@ Deconstruct a tuple of arguments and generate a Python statement.
```fs
open Fable.Core.PyInterop
-let add (a : int) (b : int) : int =
+let add (a : int) (b : int) : int =
emitStatement (a, b) "return $0 + $1;"
let repeatHello (count : int) : unit =
- emitStatement
+ emitStatement
count
"""cond = count;
while cond > 0:
@@ -373,10 +413,10 @@ def repeat_hello(count: int) -> None:
### `emitExpr` vs `emitStatement`
```fs
-let add1 (a : int) (b : int) =
+let add1 (a : int) (b : int) =
emitExpr (a, b) "$0 + $1"
-let add2 (a : int) (b : int) =
+let add2 (a : int) (b : int) =
emitStatement (a, b) "$0 + $1"
```
@@ -395,6 +435,96 @@ def add2(a: int, b: int) -> Any:
Note how `return` has been added to `add1` and not to `add2`. In this situation if you use `emitStatement`, you need to write `return $0 + $1"`
+### `Py.python`
+
+
+ Added in v5.0.0
+
+
+`Py.python` allows you to embed literal Python code directly in F#.
+
+```fs
+open Fable.Core
+
+let add a b = Py.python $"""return {a+b}"""
+```
+
+generates:
+
+```py
+def add(a, b):
+ return a + b
+```
+
+`Py.python` executes as statements, so use `return` keyword to return values.
+
+## Python Decorators
+
+
+ Added in v5.0.0
+
+
+`Py.Decorator` allows you to apply Python decorators to classes and functions.
+
+```fs
+open Fable.Core
+
+[]
+type User =
+ { Name: string
+ Age: int }
+```
+
+generates:
+
+```py
+@dataclasses.dataclass
+class User:
+ name: str
+ age: int
+```
+
+You can also pass parameters to decorators:
+
+```fs
+[]
+let expensiveFunction x = x * 2
+```
+
+generates:
+
+```py
+@functools.lru_cache(maxsize=128)
+def expensive_function(x):
+ return x * 2
+```
+
+## Class Attributes
+
+
+ Added in v5.0.0
+
+
+`Py.ClassAttributes` controls how class members are generated in Python.
+
+```fs
+open Fable.Core
+
+[]
+type Config() =
+ member val Name = "default" with get, set
+ member val Port = 8080 with get, set
+```
+
+generates:
+
+```py
+class Config:
+ name: str = "default"
+ port: int = 8080
+```
+
+Without `ClassAttributes`, members would be generated as properties with instance backing.
## `[]`
@@ -402,7 +532,7 @@ Note how `return` has been added to `add1` and not to `add2`. In this situation
You can decode a type with `[]` to tells fable to not emit code for that type.
-This is useful for creating DSL for examples or when trying to represent a virtual type
+This is useful for creating DSL for examples or when trying to represent a virtual type
for which you don't want to impact the size of the generated code.
```fs
@@ -472,13 +602,13 @@ let test(arg: U3) =
// to_console(printf("An int %i"))(arg)
// elif is_array_like(arg):
// to_console(printf("An array with sum %f"))(arg)
-// else:
+// else:
// to_console(printf("A string %s"))(arg)
```
### Erased types
-Decoring a type with `[]` allows you to instruct Fable to not generate any code for that type. This is useful when you want to use a type from a Python library that you don't want to generate bindings for.
+Decorating a type with `[]` allows you to instruct Fable to not generate any code for that type. This is useful when you want to use a type from a Python library that you don't want to generate bindings for.
```fs
open Fable.Core
@@ -555,7 +685,7 @@ The generated code is much smaller and doesn't include any reflection informatio
## `[]`
-:::info
+:::info
These union types must not have any data fields as they will be compiled to a string matching the name of the union case.
:::
@@ -585,7 +715,7 @@ on_event("click", ignore)
### `CaseRules`
-`StringEnum` accept a parameters allowing you to control the casing used to conver the union case name to a string.
+`StringEnum` accept a parameters allowing you to control the casing used to convert the union case name to a string.
- `CaseRules.None`: `MouseOver` becomes `MouseOver`
- `CaseRules.LowerFirst`: `MouseOver` becomes `mouseOver`
@@ -681,7 +811,7 @@ You then need to set each field manually.
open Fable.Core
open Fable.Core.PyInterop
-type IUser =
+type IUser =
abstract Name : string with get, set
abstract Age : int with get, set
@@ -801,7 +931,7 @@ class MinStack:
Added in v3.2.0
-If you are not planning to use an interface to interact with Python and want to have overloaded members, you can decorate the interface declaration with the `Mangle` attribute.
+If you are not planning to use an interface to interact with Python and want to have overloaded members, you can decorate the interface declaration with the `Mangle` attribute.
> Interfaces coming from .NET BCL (like System.Collections.IEnumerator) are mangled by default.
@@ -814,10 +944,10 @@ type IRenderer =
type Renderer() =
interface IRenderer with
- member this.Render() =
+ member this.Render() =
failwith "Not implemented"
- member this.Render(indentation) =
+ member this.Render(indentation) =
failwith "Not implemented"
```
@@ -913,12 +1043,12 @@ useEffect(Func<_,_> myEffect)
## Dynamic typing, proceed with caution
-Dynamic typing allows you to access an object property by name
+Dynamic typing allows you to access an object property by name
:::danger
-This feature is not type-safe and should be used with caution.
+This feature is not type-safe and should be used with caution.
-Adocate use case for this feature is when you are prototyping or don't have the time to write a proper type-safe interop.
+Advocate use case for this feature is when you are prototyping or don't have the time to write a proper type-safe interop.
:::
### Property access
From 6ae0bbb309d09489024386cc974be4d54fb75d1c Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Mon, 17 Nov 2025 20:00:29 +0100
Subject: [PATCH 2/8] doc: fix typo
---
docs/docs/python/features.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/python/features.md b/docs/docs/python/features.md
index c66075a9..ad5ad4d1 100644
--- a/docs/docs/python/features.md
+++ b/docs/docs/python/features.md
@@ -1048,7 +1048,7 @@ Dynamic typing allows you to access an object property by name
:::danger
This feature is not type-safe and should be used with caution.
-Advocate use case for this feature is when you are prototyping or don't have the time to write a proper type-safe interop.
+Adequate use case for this feature is when you are prototyping or don't have the time to write a proper type-safe interop.
:::
### Property access
From 346ab3da4455abea48e967b6956bd6f0c2c5d680 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Mon, 17 Nov 2025 20:50:18 +0100
Subject: [PATCH 3/8] doc: remove -alpha from tag
---
docs/docs/python/features.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/python/features.md b/docs/docs/python/features.md
index ad5ad4d1..8ca34c70 100644
--- a/docs/docs/python/features.md
+++ b/docs/docs/python/features.md
@@ -32,7 +32,7 @@ def add_two_number(x: int, y: int) -> int:
Records snake-case all member fields:
- Added in v5.0.0-alpha.14
+ Added in v5.0.0
```fs
From 58287f35b0105548513758e035a90b4a6cf2d72a Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Wed, 19 Nov 2025 12:49:31 +0100
Subject: [PATCH 4/8] Update docs/docs/python/build-and-run.md
Co-authored-by: Maxime Mangel
---
docs/docs/python/build-and-run.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/python/build-and-run.md b/docs/docs/python/build-and-run.md
index f164a9ba..e2f60f22 100644
--- a/docs/docs/python/build-and-run.md
+++ b/docs/docs/python/build-and-run.md
@@ -5,7 +5,7 @@ layout: standard
## Python Version
-Fable targets Python 3.12 or higher. Python 3.14 is also supported.
+Fable targets Python 3.12 or higher.
Python 3.10 and 3.11 are deprecated.
From 73835cbaa1b3e43a323017ec1ff1101b3ed04415 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Wed, 19 Nov 2025 12:50:05 +0100
Subject: [PATCH 5/8] Update docs/docs/python/build-and-run.md
Co-authored-by: Maxime Mangel
---
docs/docs/python/build-and-run.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/python/build-and-run.md b/docs/docs/python/build-and-run.md
index e2f60f22..74fddf35 100644
--- a/docs/docs/python/build-and-run.md
+++ b/docs/docs/python/build-and-run.md
@@ -26,7 +26,7 @@ When building a library that uses Fable Python and you want to publish it to PyP
Instead, use the `--fableLib` option to reference a pre-built Fable library:
```bash
-fable --lang python --fableLib fable-library
+dotnet fable --lang python --fableLib fable-library
```
This will make any reference to the Fable library point to a package in the Python search path (e.g., site-packages) instead of the normally bundled library. Your package will then need to declare `fable-library` as a dependency so users can install it from PyPI.
From 67c6d0db028330af74f3e0aeca89a863c71e3ef0 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Wed, 19 Nov 2025 12:50:38 +0100
Subject: [PATCH 6/8] Update docs/docs/python/build-and-run.md
Co-authored-by: Maxime Mangel
---
docs/docs/python/build-and-run.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/docs/docs/python/build-and-run.md b/docs/docs/python/build-and-run.md
index 74fddf35..a343c11c 100644
--- a/docs/docs/python/build-and-run.md
+++ b/docs/docs/python/build-and-run.md
@@ -21,9 +21,11 @@ python3 Program.py
## Publishing to PyPI
-When building a library that uses Fable Python and you want to publish it to PyPI, you cannot bundle the Fable library the normal way. This is because the Fable library is partially written in Rust and needs to be built for all architectures.
+If you want to publish a library to PyPI that uses Fable Python, you need to use `--fableLib` option to reference the pre-build Fable library:
-Instead, use the `--fableLib` option to reference a pre-built Fable library:
+:::info
+This is because the Fable library is partially written in Rust and needs to be built for all architectures
+:::
```bash
dotnet fable --lang python --fableLib fable-library
From 9d7ea03b663690472d763eec4926e87cfd697276 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Sat, 22 Nov 2025 09:34:13 +0100
Subject: [PATCH 7/8] doc: fix version tags for python features
---
docs/docs/python/features.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/docs/python/features.md b/docs/docs/python/features.md
index 8ca34c70..a2565c48 100644
--- a/docs/docs/python/features.md
+++ b/docs/docs/python/features.md
@@ -32,7 +32,7 @@ def add_two_number(x: int, y: int) -> int:
Records snake-case all member fields:
- Added in v5.0.0
+ Added in v5.0.0-alpha
```fs
@@ -438,7 +438,7 @@ Note how `return` has been added to `add1` and not to `add2`. In this situation
### `Py.python`
- Added in v5.0.0
+ Added in v4.0.0
`Py.python` allows you to embed literal Python code directly in F#.
@@ -461,7 +461,7 @@ def add(a, b):
## Python Decorators
- Added in v5.0.0
+ Added in v5.0.0-alpha
`Py.Decorator` allows you to apply Python decorators to classes and functions.
@@ -502,7 +502,7 @@ def expensive_function(x):
## Class Attributes
- Added in v5.0.0
+ Added in v5.0.0-alpha
`Py.ClassAttributes` controls how class members are generated in Python.
From 552596147d67346d3a6cfdf33f6b123fab551446 Mon Sep 17 00:00:00 2001
From: Dag Brattli
Date: Sat, 22 Nov 2025 10:48:03 +0100
Subject: [PATCH 8/8] doc: add missing types to generated code
---
docs/docs/python/features.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/docs/python/features.md b/docs/docs/python/features.md
index a2565c48..a39c159f 100644
--- a/docs/docs/python/features.md
+++ b/docs/docs/python/features.md
@@ -452,7 +452,7 @@ let add a b = Py.python $"""return {a+b}"""
generates:
```py
-def add(a, b):
+def add(a: int32, b: int32) -> Any:
return a + b
```
@@ -481,7 +481,7 @@ generates:
@dataclasses.dataclass
class User:
name: str
- age: int
+ age: int32
```
You can also pass parameters to decorators: