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
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ root = true
fsharp_multiline_bracket_style = stroustrup
fsharp_record_multiline_formatter = number_of_items
fsharp_max_record_number_of_items = 1
fsharp_max_infix_operator_expression = 60
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"aiohttp",
"alfonsogarciacaro",
"boto",
"Buildalyzer",
"coro",
"destructures",
Expand All @@ -19,9 +20,13 @@
"nativeint",
"ncave",
"pathlib",
"pdfplumber",
"Plotly",
"pyfile",
"pyname",
"Pythonistas",
"Pyxpecto",
"scikit",
"Sergey",
"stroustrup",
"Tihon",
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ docs/

- [Fable.Python docs](https://fable.io/docs/getting-started/python.html)
- [Fable.Python GitHub](https://github.com/fable-compiler/Fable.Python/)
- [Content Plan](CONTENT-PLAN.md) - Chapter structure and TODO items
- [Content Plan](CONTENT-PLAN.md) - Chapter structure and TODO items (important!)
15 changes: 12 additions & 3 deletions Fable.Literate/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ module Utils =
if m.Value.Length = 1 then
m.Value.ToLowerInvariant()
else
m.Value.Substring(0, 1) + "_" + m.Value.Substring(1, 1).ToLowerInvariant()
m.Value.Substring(0, 1)
+ "_"
+ m.Value.Substring(1, 1).ToLowerInvariant()
)
else
name
Expand Down Expand Up @@ -328,7 +330,10 @@ module MarkdownPrinter =
| IncludePython symbols ->
// Unresolved - should have been transformed
let symbolList = String.concat ", " symbols
"\n<!-- include-python: " + symbolList + " (unresolved) -->\n"

"\n<!-- include-python: "
+ symbolList
+ " (unresolved) -->\n"
| Hidden _ -> "" // Should have been filtered

/// Render a document to markdown string.
Expand All @@ -348,7 +353,11 @@ module MarkdownPrinter =
| s when s.StartsWith "#" -> inCodeBlock, ("#" + line) :: acc
| _ -> inCodeBlock, line :: acc

lines |> Array.fold folder (false, []) |> snd |> List.rev |> String.concat "\n"
lines
|> Array.fold folder (false, [])
|> snd
|> List.rev
|> String.concat "\n"

(**
## Pipeline Module
Expand Down
2 changes: 1 addition & 1 deletion Fable.Literate/Fable.Literate.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

<ItemGroup>
<PackageReference Include="Fable.Core" Version="5.0.0-beta.4" />
<PackageReference Include="Fable.Python" Version="5.0.0-alpha.21.0" />
<PackageReference Include="Fable.Python" Version="5.0.0-alpha.21.5" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> Write F#, run Python - a practical guide to Fable.Python

This is a comprehensive guide to [Fable.Python](https://github.com/fable-compiler/Fable.Python/), written as literate F# that transpiles to Python and generates its own documentation.
This blog-post is a guide to [Fable.Python](https://github.com/fable-compiler/Fable.Python/), written as literate F# that transpiles to Python and generates its own documentation.

## Chapters

Expand All @@ -23,9 +23,10 @@ This is a comprehensive guide to [Fable.Python](https://github.com/fable-compile

## The Strange Loop

This guide is self-documenting: each chapter is an `.fs` file with embedded Markdown comments. **Fabletext** (the final chapter) processes these files to generate the documentation you're reading - including itself.
This guide is self-documenting: each chapter is an `.fs` file with embedded Markdown comments. **Fable.Literate** (the final chapter) processes these files to generate the documentation you're reading - including itself.

The chain:

1. Write F# with embedded Markdown (`chapters/*.fs`)
2. Compile to Python with Fable
3. Run Fabletext (F# compiled to Python) to extract documentation
Expand Down
8 changes: 6 additions & 2 deletions chapters/AsyncProgramming.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ F# async shines when composing multiple operations:

let fetchMultipleAsync () =
async {
let! results = [ fetchDataAsync (); fetchDataAsync (); fetchDataAsync () ] |> Async.Parallel
let! results =
[ fetchDataAsync (); fetchDataAsync (); fetchDataAsync () ]
|> Async.Parallel

return results |> Array.toList
}
Expand Down Expand Up @@ -398,7 +400,9 @@ When you need rich composition primitives:
let complexWorkflow () =
async {
// Run three operations in parallel
let! results = [ fetchDataAsync (); fetchDataAsync (); fetchDataAsync () ] |> Async.Parallel
let! results =
[ fetchDataAsync (); fetchDataAsync (); fetchDataAsync () ]
|> Async.Parallel

// Then do something sequential
do! Async.Sleep 100
Expand Down
13 changes: 8 additions & 5 deletions chapters/Bindings.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ module Bindings
(**
# Creating Python Bindings

When a Python library doesn't have F# bindings, you can create your own.
This chapter covers the patterns and best practices for writing type-safe
bindings that feel natural in F#.
When a Python library doesn't have F# bindings, you can create your own. This chapter
covers the patterns and best practices for writing type-safe bindings that feel natural
in F#.

> Writing bindings have long been a major pain point, spending countless hours wrestling
> with interop details. With AI-assisted coding tools, generating initial binding code
> for your favorite Python libraries has become much easier.

## Core Principles

Expand Down Expand Up @@ -273,6 +277,5 @@ let createClient url = myLibrary.createClient url

## What's Next?

Now you know how to create bindings. The **Compatibility** chapter covers
which F# features work with Fable.Python and any limitations to be aware of.
With bindings covered, the **Compatibility** chapter shows which F# features work with Fable.Python and any limitations to be aware of.
*)
128 changes: 99 additions & 29 deletions chapters/FastAPI.fs
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,35 @@ let items = ResizeArray<Item>()
type API() =
/// GET /items - List all items
[<Get("/items")>]
static member get_items() : ResizeArray<Item> =
items
static member get_items() : ResizeArray<Item> = items

/// GET /items/{item_id} - Get item by ID
[<Get("/items/{item_id}")>]
static member get_item(item_id: int) : Task<obj> = task {
match items |> Seq.tryFind (fun i -> i.Id = item_id) with
| Some item -> return item :> obj
| None -> return {| error = "Item not found" |}
}
static member get_item(item_id: int) : Task<obj> =
task {
match items |> Seq.tryFind (fun i -> i.Id = item_id) with
| Some item -> return item :> obj
| None -> return {| error = "Item not found" |}
}

/// POST /items - Create a new item
[<Post("/items")>]
static member create_item(request: CreateItemRequest) : Task<obj> = task {
let newId =
if items.Count = 0 then 1
else (items |> Seq.map (fun i -> i.Id) |> Seq.max) + 1
let newItem = Item(newId, request.Name, request.Price, request.InStock)
items.Add(newItem)
return {| status = "created"; item = newItem |}
}
static member create_item(request: CreateItemRequest) : Task<obj> =
task {
let newId =
if items.Count = 0 then
1
else
(items |> Seq.map (fun i -> i.Id) |> Seq.max) + 1

let newItem = Item(newId, request.Name, request.Price, request.InStock)
items.Add(newItem)

return {|
status = "created"
item = newItem
|}
}

(**
This generates Python with proper FastAPI decorators:
Expand All @@ -143,8 +151,10 @@ F# anonymous records compile to Python dictionaries, perfect for JSON responses:
[<APIClass>]
type HealthAPI() =
[<Get("/health")>]
static member health() =
{| status = "healthy"; version = "1.0.0" |}
static member health() = {|
status = "healthy"
version = "1.0.0"
|}

(**
## Async Endpoints
Expand All @@ -155,11 +165,12 @@ For I/O-bound operations, use `task { }` to create async endpoints:
[<APIClass>]
type AsyncAPI() =
[<Get("/slow")>]
static member slow_operation() = task {
// Simulate async work (e.g., database query)
do! Task.Delay(100)
return {| message = "Done!" |}
}
static member slow_operation() =
task {
// Simulate async work (e.g., database query)
do! Task.Delay(100)
return {| message = "Done!" |}
}

(**
The `task { }` computation expression compiles to Python's `async def`,
Expand All @@ -175,12 +186,16 @@ Path parameters are extracted from the URL:
[<APIClass>]
type UsersAPI() =
[<Get("/users/{user_id}")>]
static member get_user(user_id: int) =
{| id = user_id; name = "User " + string user_id |}
static member get_user(user_id: int) = {|
id = user_id
name = "User " + string user_id
|}

[<Get("/users/{user_id}/posts/{post_id}")>]
static member get_user_post(user_id: int, post_id: int) =
{| user_id = user_id; post_id = post_id |}
static member get_user_post(user_id: int, post_id: int) = {|
user_id = user_id
post_id = post_id
|}

(**
### Query Parameters
Expand All @@ -191,8 +206,10 @@ Query parameters are function arguments not in the path:
[<APIClass>]
type SearchAPI() =
[<Get("/search")>]
static member search(q: string, limit: int) =
{| query = q; limit = limit |}
static member search(q: string, limit: int) = {|
query = q
limit = limit
|}

(**
A request to `/search?q=hello&limit=10` maps to `search("hello", 10)`.
Expand All @@ -213,7 +230,11 @@ type UserCrudAPI() =
[<Post("/users")>]
static member create_user(request: CreateUserRequest) =
// FastAPI automatically validates the request body
{| status = "created"; name = request.name; email = request.email |}
{|
status = "created"
name = request.name
email = request.email
|}

(**
FastAPI validates the incoming JSON against the Pydantic model and returns
Expand All @@ -230,8 +251,10 @@ type ErrorAPI() =
static member protected_route() =
// Check authentication (simplified example)
let isAuthenticated = false

if not isAuthenticated then
raise (System.Exception("Not authenticated"))

{| message = "Secret data" |}

(**
Expand Down Expand Up @@ -336,4 +359,51 @@ This combination gives you:
4. **Familiar ecosystem** - Deploy with standard Python tools

You write type-safe F# code, but deploy and run it like any Python web service.

## Hybrid Architecture: F# Backend with Python Endpoints

Another compelling use case is when you have an existing web service written in F# (using
ASP.NET Core, Giraffe, or Oxpecker) but need access to the Python ecosystem for specific
functionality. You can use FastAPI to expose endpoints that leverage Python libraries,
while your main service remains in F#.

This hybrid approach works well when you need:

### AI/ML Libraries

- **LangChain** / **LlamaIndex** - LLM orchestration and RAG pipelines
- **Hugging Face Transformers** - Pre-trained models for NLP, vision, audio
- **OpenAI SDK** / **Anthropic SDK** - LLM API integration with structured outputs
- **scikit-learn** - Classical machine learning models
- **PyTorch** / **TensorFlow** - Deep learning inference

### Data Science & Analytics

- **Pandas** / **Polars** - Data manipulation and analysis
- **NumPy** - Numerical computing
- **Matplotlib** / **Plotly** - Chart and visualization generation
- **Apache Arrow** - Efficient cross-language data interchange

### Document Processing

- **PyMuPDF** / **pdfplumber** - PDF text and table extraction
- **python-docx** - Word document generation
- **Pillow** - Image processing and manipulation
- **OpenCV** - Computer vision operations

### Specialized APIs

- **boto3** - AWS services (S3, Lambda, SQS, etc.)
- **google-cloud-*** - GCP services (BigQuery, Cloud Storage, Vertex AI)

### Scientific Computing

- **SciPy** - Scientific algorithms and optimization
- **SymPy** - Symbolic mathematics
- **NetworkX** - Graph algorithms and analysis

The pattern is straightforward: your F# service handles core domain logic and type-safe
business rules, while specific endpoints delegate to a FastAPI service for capabilities
where Python dominates. This is especially powerful for AI/ML workloads where the Python
ecosystem is unmatched.
*)
14 changes: 8 additions & 6 deletions chapters/GettingStarted.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module GettingStarted
(**
# Getting Started with Fable.Python

Let's set up a Fable.Python project from scratch and get our first F# code running as Python.
In this section we will set up a Fable.Python project from scratch and get our first F# code running as Python.

## Prerequisites

Expand All @@ -12,7 +12,8 @@ You'll need:
- [.NET SDK](https://dotnet.microsoft.com/download) (6.0 or later. We recommend
installing the latest LTS version, currently .NET 10)
- [Python 3.12+](https://www.python.org/downloads/) (Fable targets Python 3.12 or higher)
- [uv](https://docs.astral.sh/uv/) (recommended) - A fast Python package manager written in Rust
- [uv](https://docs.astral.sh/uv/) (recommended) - A fast Python package manager written in Rust that
simplifies dependency management, virtual environments, and the installation of Python itself.

If you don't have `uv` installed:

Expand Down Expand Up @@ -60,8 +61,9 @@ pip install "fable-library==5.0.0a21"

---

**Note:** Version pinning matters. The fable-library version must match
your Fable compiler version. PyPI uses `5.0.0a21` format instead of `5.0.0-alpha.21`.
**Note:** Version pinning matters. The fable-library version must match your Fable
compiler version. Note that PyPI uses `5.0.0a21` format instead of `5.0.0-alpha.21` for
prerelease alpha releases.

---

Expand Down Expand Up @@ -136,6 +138,6 @@ my-fable-python/

## Next Steps

Now that you have a working setup, let's explore how to interact with Python
libraries in the next chapter on **Bindings**.
Now that you have a working setup, let's see how we can interact with Python
libraries by using **Bindings**.
*)
9 changes: 4 additions & 5 deletions chapters/Interop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ module Interop
(**
# Python Interop

Now that you have a Fable.Python project set up, let's explore how to work
with Python libraries and the existing bindings in the Fable.Python ecosystem.
With a Fable.Python project set up, we can start to work with Python libraries and the existing bindings in the Fable.Python ecosystem.

## The Fable.Python Library

Expand Down Expand Up @@ -427,7 +426,7 @@ let loadConfig (path: string) =
(**
## What's Next?

Now you know how to use existing Python bindings and core interop features.
In the next chapter, we'll learn how to create your own bindings for
Python libraries that don't have F# bindings yet.
Now you know how to use existing Python bindings and core interop features. In the next
chapter we will see how you can create your own bindings for Python libraries that don't
have F# bindings yet.
*)
Loading