diff --git a/README.md b/README.md index 7756a7f..8ce60fc 100644 --- a/README.md +++ b/README.md @@ -3,199 +3,244 @@ ![Build and Test](https://github.com/dbrattli/Fable.Python/workflows/Build%20and%20Test/badge.svg) [![Nuget](https://img.shields.io/nuget/vpre/Fable.Python)](https://www.nuget.org/packages/Fable.Python/) -[Fable](https://github.com/fable-compiler/Fable/tree/beyond) is a -compiler that translates F# source files to JavaScript and Python. +[Fable](https://github.com/fable-compiler/Fable) is a compiler that translates F# source files to JavaScript and Python. -This Fable Python repository is a community driven project that contains -the Python type bindings for Fable. The library will eventually contain -Python (stdlib) bindings for Fable based on Python -[typeshed](https://github.com/python/typeshed). It will also contain -type binding for many other 3rd party libraries such as Flask, MicroBit -and many more. Some bindings have already been added: +**Fable.Python** provides Python type bindings for Fable, enabling you to write type-safe F# code that compiles to Python. This community-driven library includes bindings for the Python standard library and popular frameworks like Flask, FastAPI, and Pydantic. -- Python Standard Libray -- Jupyter -- Flask -- CogniteSdk +## Requirements -## Version - -This library currently targets Python 3.10 or greater. Types bindings -for other versions of Python should not be added to this library until -we decide how to deal with Python version handling. +- Python 3.12 or greater +- .NET 8.0 or greater +- [Fable](https://fable.io/) compiler ## Installation -Prerequisite for compiling F# to Python using Fable: +Install the Fable compiler: ```sh -> dotnet tool install --global fable --prerelease -> dotnet add package Fable.Core --prerelease +dotnet tool install --global fable --prerelease +dotnet add package Fable.Core --prerelease ``` -To use the `Fable.Python` library in your Fable project: +Add Fable.Python to your project: ```sh -> dotnet add package Fable.Python +dotnet add package Fable.Python ``` -## Usage +## Quick Start -```fs +```fsharp open Fable.Python.Json -let object = {| A=10n; B=20n |} -let result = json.dumps object +let data = {| name = "Alice"; age = 30 |} +let jsonStr = dumps data ``` -To compile an F# Fable project to Python run e.g: +Compile to Python: ```sh -> fable --lang Python MyProject.fsproj +fable --lang Python MyProject.fsproj ``` -For more examples see the -[examples](https://github.com/dbrattli/Fable.Python/tree/main/examples) folder. -It contains example code for using Fable Python with: - -- [Flask](https://github.com/dbrattli/Fable.Python/tree/main/examples/flask). - References [Feliz.ViewEngine](https://github.com/dbrattli/Feliz.ViewEngine) - as a nuget package. -- [Timeflies](https://github.com/dbrattli/Fable.Python/tree/main/examples/timeflies), - Cool demo using Tkinter and references - [FSharp.Control.AsyncRx](https://github.com/dbrattli/AsyncRx) as a nuget - package. - -## Libraries that uses or works with Fable Python - -- [Femto](https://github.com/Zaid-Ajaj/Femto) -- [AsyncRx](https://github.com/dbrattli/AsyncRx) -- [Fable.Aether](https://xyncro.tech/aether/) -- [Fable.Giraffe](https://github.com/dbrattli/Fable.Giraffe), port of Giraffe to Fable.Python -- [Fable.Logging](https://github.com/dbrattli/Fable.logging), logging for Fable.Python -- [Fable.Requests](https://github.com/Zaid-Ajaj/Fable.Requests) -- [Fable.Jupyter](https://github.com/fable-compiler/Fable.Jupyter), Jupyter Notebook using Fable.Python -- [Fable.Pyexpecto](https://github.com/Freymaurer/Fable.Pyxpecto), Fable-python equivalent for Fable.Mocha -- [Fable.SimpleJson.Python](https://github.com/Zaid-Ajaj/Fable.SimpleJson.Python) -- [Fable.Sedlex](https://github.com/thautwarm/Fable.Sedlex) -- [Feliz.ViewEngine](https://github.com/dbrattli/Feliz.ViewEngine) -- [FsToolkit.ErrorHandling](https://demystifyfp.gitbook.io/fstoolkit-errorhandling/) -- [TypedCssClasses](https://github.com/zanaptak/TypedCssClasses) -- [Typed-BNF](https://github.com/thautwarm/Typed-BNF#readme) -- [Zanaptak.TypedCssClasses](https://github.com/zanaptak/TypedCssClasses) - -## Uv - -Fable.Python uses [Uv](https://docs.astral.sh/uv/) for package and -dependency management. To handle dependencies when adding Fable Python -compatible NuGet packages, you should use -[Femto](https://github.com/Zaid-Ajaj/Femto). - -## Development +## Available Bindings -This project uses [just](https://github.com/casey/just) as a command runner. +### Python Standard Library -```sh -# Install just (macOS) -> brew install just +| Module | Description | +| ----------------------- | ------------------------------------------- | +| `Fable.Python.Builtins` | Built-in functions (open, print, len, etc.) | +| `Fable.Python.Json` | JSON serialization with Fable type support | +| `Fable.Python.Os` | Operating system interfaces | +| `Fable.Python.Sys` | System-specific parameters | +| `Fable.Python.Math` | Mathematical functions | +| `Fable.Python.Random` | Random number generation | +| `Fable.Python.Logging` | Logging facilities | +| `Fable.Python.Time` | Time-related functions | +| `Fable.Python.String` | String operations | +| `Fable.Python.Base64` | Base64 encoding/decoding | +| `Fable.Python.Queue` | Queue data structures | +| `Fable.Python.Ast` | Abstract Syntax Tree | +| `Fable.Python.AsyncIO` | Async programming (Events, Futures, Tasks) | +| `Fable.Python.TkInter` | GUI toolkit | -# Show available commands -> just +### Web Frameworks -# Full setup (restore .NET and Python dependencies) -> just setup +| Package | Description | +| ----------------------- | ----------------------------------- | +| `Fable.Python.Flask` | Flask web framework | +| `Fable.Python.FastAPI` | FastAPI with automatic OpenAPI docs | +| `Fable.Python.Pydantic` | Data validation and settings | -# Build F# to Python -> just build +## JSON Serialization -# Run all tests (native .NET and Python) -> just test +Fable types (like `Int32`, F# records, unions) need special handling for JSON serialization. Use `Fable.Python.Json.dumps`: -# Format code -> just format +```fsharp +open Fable.Python.Json -# Create NuGet package -> just pack +type User = { Id: int; Name: string } +let user = { Id = 1; Name = "Bob" } +let json = dumps user // {"Id": 1, "Name": "Bob"} ``` -## Contributing +See [JSON.md](JSON.md) for detailed documentation on serialization patterns. -This project is community driven. If the type binding you are looking -for is currently missing, then you need to add them to the relevant -files (or add new ones). Open a -[PR](https://github.com/dbrattli/Fable.Python/pull/3/files) to get them -included. +## Web Framework Examples -### Commit Convention +### FastAPI -This project uses [Conventional Commits](https://www.conventionalcommits.org/) -and [release-please](https://github.com/googleapis/release-please) for automated -releases. PR titles must follow the format: +```fsharp +open Fable.Python.FastAPI +open Fable.Python.Pydantic +[] +type UserResponse(Id: int, Name: string) = + inherit BaseModel() + member val Id: int = Id with get, set + member val Name: string = Name with get, set + +[] +type API() = + [] + static member get_user(user_id: int) : UserResponse = + UserResponse(Id = user_id, Name = "Alice") ``` -type: description + +### Flask + +```fsharp +open Fable.Python.Flask +open Fable.Python.Json + +[] +type Routes() = + [] + static member hello() : string = + dumps {| message = "Hello, World!" |} ``` -Where `type` is one of: -- `feat` - New features (bumps minor version) -- `fix` - Bug fixes (bumps patch version) -- `docs` - Documentation changes -- `chore` - Maintenance tasks -- `refactor` - Code refactoring -- `test` - Adding or updating tests -- `ci` - CI/CD changes -- `build` - Build system changes -- `perf` - Performance improvements +## Examples -Breaking changes should include `!` after the type (e.g., `feat!: breaking change`) -and will bump the major version. +The [examples](examples/) directory contains working applications: -The `src/stdlib` directory contains type bindings for modules in the -Python 3 standard library. We also accept type bindings for 3rd party -libraries as long as: +| Example | Description | +| ------------------------------------------ | ---------------------------------------------- | +| [fastapi](examples/fastapi/) | REST API with Pydantic models and Swagger docs | +| [flask](examples/flask/) | Web app with Feliz.ViewEngine HTML rendering | +| [django](examples/django/) | Full Django project | +| [django-minimal](examples/django-minimal/) | Single-file Django app | +| [pydantic](examples/pydantic/) | Pydantic model examples | +| [timeflies](examples/timeflies/) | Tkinter GUI with AsyncRx | -- the package is publicly available on the [Python Package Index](https://pypi.org/); -- the package supports any Python version supported by Fable Python; and -- the package does not ship with its own stubs or type annotations +Run an example: -There's not much Python specific documentation yet, but the process of -adding type bindings for Python is similar to JS: +```sh +just example-fastapi # FastAPI with auto-reload +just example-flask # Flask web app +just example-timeflies # Tkinter desktop app +``` -- -- +## Development -## Differences from JS +This project uses [just](https://github.com/casey/just) as a command runner and [uv](https://docs.astral.sh/uv/) for Python package management. -Note that import all is different from JS. E.g: +### Setup -```fs -[] -let flask: IExports = nativeOnly +```sh +# Install just (macOS) +brew install just + +# Full setup (restore .NET and Python dependencies) +just setup +``` + +### Commands + +```sh +just # Show all available commands +just build # Build F# to Python +just test # Run all tests (native .NET and Python) +just test-python # Run only Python tests +just format # Format code with Fantomas +just pack # Create NuGet package +just clean # Clean build artifacts ``` -This will generate `import flask` and not a wildcard import `from flask import -*`. The latter version is discoraged anyways. +### Project Structure + +```txt +src/ +├── stdlib/ # Python standard library bindings +├── flask/ # Flask bindings +├── fastapi/ # FastAPI bindings +├── pydantic/ # Pydantic bindings +└── jupyter/ # Jupyter bindings +test/ # Test suite +examples/ # Example applications +build/ # Generated Python output (gitignored) +``` + +## Compatible Libraries + +These libraries work with Fable.Python: + +- [AsyncRx](https://github.com/dbrattli/AsyncRx) - Reactive programming +- [Fable.Giraffe](https://github.com/dbrattli/Fable.Giraffe) - Giraffe port +- [Fable.Logging](https://github.com/dbrattli/Fable.logging) - Logging +- [Fable.Requests](https://github.com/Zaid-Ajaj/Fable.Requests) - HTTP requests +- [Fable.Jupyter](https://github.com/fable-compiler/Fable.Jupyter) - Jupyter notebooks +- [Fable.Pyexpecto](https://github.com/Freymaurer/Fable.Pyxpecto) - Testing +- [Fable.SimpleJson.Python](https://github.com/Zaid-Ajaj/Fable.SimpleJson.Python) - JSON parsing +- [Fable.Sedlex](https://github.com/thautwarm/Fable.Sedlex) - Lexer generator +- [Feliz.ViewEngine](https://github.com/dbrattli/Feliz.ViewEngine) - HTML rendering +- [Femto](https://github.com/Zaid-Ajaj/Femto) - Package management +- [FsToolkit.ErrorHandling](https://demystifyfp.gitbook.io/fstoolkit-errorhandling/) - Error handling +- [TypedCssClasses](https://github.com/zanaptak/TypedCssClasses) - Type-safe CSS + +## Contributing + +Contributions are welcome! If a type binding you need is missing, open a [PR](https://github.com/dbrattli/Fable.Python/pulls) to add it. + +### Commit Convention + +This project uses [Conventional Commits](https://www.conventionalcommits.org/) for automated releases: + +| Type | Description | +| ---------- | ---------------------------------- | +| `feat` | New features (bumps minor version) | +| `fix` | Bug fixes (bumps patch version) | +| `docs` | Documentation changes | +| `chore` | Maintenance tasks | +| `refactor` | Code refactoring | +| `test` | Tests | + +Breaking changes use `!` (e.g., `feat!: breaking change`). -## Auto-generation +### Adding Bindings -Parts of this library could benefit from code-generation based on the type -annotations in Python [typeshed](https://github.com/python/typeshed) similar to -[ts2fable](https://github.com/fable-compiler/ts2fable). Even so we should keep -this library manually updated based on PRs to ensure the quality of the code. +The `src/stdlib/` directory contains Python standard library bindings. Third-party library bindings are accepted if: -Current plan: +- The package is publicly available on [PyPI](https://pypi.org/) +- The package supports Python 3.12+ +- The package doesn't ship with its own type stubs -1. Add bindings for Python `ast` module (in progress) -2. Use `ast` module to parse Python typeshed annotations -3. Generate F# bindings +For guidance on creating bindings, see: -## Road-map +- [Fable JS Interop](https://fable.io/docs/communicate/js-from-fable.html) (patterns apply to Python) +- [F# Interop Guide](https://medium.com/@zaid.naom/f-interop-with-javascript-in-fable-the-complete-guide-ccc5b896a59f) -- Use a dedicated List.fs for Python. List.fs currently depends on - Array.fs that is not an efficient list implementation for Python. +### Import Pattern + +Note that `ImportAll` generates a module import: + +```fsharp +[] +let flask: IExports = nativeOnly +``` -- Compile Fable.Library as a published library (done) +This generates `import flask`, not `from flask import *`. -- Use uv for Python references to Fable modules (done) +## License -- Update docs +MIT diff --git a/examples/fastapi/README.md b/examples/fastapi/README.md index 35a937b..cee91b0 100644 --- a/examples/fastapi/README.md +++ b/examples/fastapi/README.md @@ -1,6 +1,8 @@ -# FastAPI Example +# FastAPI Example (Generating Models from F#) -This example demonstrates how to build a FastAPI application using **Fable.Python** with the decorator-based API pattern. +This example demonstrates how to build a FastAPI application using **Fable.Python** with the decorator-based API pattern. The Pydantic models are **defined in F#** and compile to Python. + +> **Note:** For the opposite approach (importing Pydantic models defined in Python), see the [Pydantic example](../pydantic/). ## Features @@ -95,7 +97,7 @@ type User(Id: int, Name: string, Email: string) = member val Email: string = Email with get, set ``` -### API Endpoints +### Defining Endpoints The `[]` attribute marks a class for FastAPI routing, and method decorators define the HTTP methods: diff --git a/examples/pydantic/.gitignore b/examples/pydantic/.gitignore index c152436..3937db3 100644 --- a/examples/pydantic/.gitignore +++ b/examples/pydantic/.gitignore @@ -15,5 +15,8 @@ obj/ fable_modules/ # Generated Fable code -src -*.py \ No newline at end of file +build/ + +# Generated Python files (but not models.py which is handwritten) +app.py +!models.py \ No newline at end of file diff --git a/examples/pydantic/README.md b/examples/pydantic/README.md new file mode 100644 index 0000000..153d962 --- /dev/null +++ b/examples/pydantic/README.md @@ -0,0 +1,112 @@ +# Pydantic Example (Importing Python Models) + +This example demonstrates how to **import and use Pydantic models defined in Python** from F# code using Fable.Python. + +> **Note:** This is the opposite approach from the [FastAPI example](../fastapi/), which shows how to **define Pydantic models in F#** that compile to Python. + +## Use Cases + +This pattern is useful when you want to: + +- Use models generated from OpenAPI specs or other tools +- Use models maintained by a Python team +- Integrate with an existing Python codebase +- Share models between Python and F# code + +## Project Structure + +```txt +examples/pydantic/ +├── App.fs # F# application using the models +├── Models.fs # F# bindings for Python models +├── models.py # Handwritten Python Pydantic models +├── PydanticExample.fsproj +├── build/ # Generated Python code (git-ignored) +└── README.md +``` + +## How It Works + +### 1. Define Pydantic Models in Python + +Create your models in `models.py`: + +```python +from pydantic import BaseModel + +class User(BaseModel): + id: int + name: str + email: str | None = None + age: int | None = None +``` + +### 2. Create F# Bindings + +Import the Python models using `[]`: + +```fsharp +[] +type User = + abstract id: int with get, set + abstract name: string with get, set + abstract email: string option with get, set + abstract age: int option with get, set +``` + +### 3. Create Constructors + +Use `[]` to call the Python constructor with named arguments: + +```fsharp +[] +module User = + [] + [] + let create (id: int) (name: string) (email: string option) (age: int option) : User = nativeOnly +``` + +### 4. Use in F# Code + +```fsharp +let user = User.create 1 "Alice" (Some "alice@example.com") (Some 30) +printfn "User: %s (id=%d)" user.name user.id +``` + +## Building + +From the repository root: + +```bash +# Using justfile +just example-pydantic + +# Or manually +cd examples/pydantic +dotnet fable --lang python --outDir build +cp models.py build/ +cd build && python app.py +``` + +## Output + +```txt +User 1: Alice (id=1) +User 1 email: alice@example.com +User 1 age: 30 + +User 2: Bob (id=2) +User 2 email: None + +Product: Laptop - $1299.99 +In stock: true +Tags: ['electronics', 'computers'] + +Updated email: alice.updated@example.com + +Create request for: Charlie +``` + +## See Also + +- [FastAPI example](../fastapi/) - Shows the opposite pattern: defining Pydantic models in F# that compile to Python diff --git a/examples/pydantic/models.py b/examples/pydantic/models.py new file mode 100644 index 0000000..9f01793 --- /dev/null +++ b/examples/pydantic/models.py @@ -0,0 +1,38 @@ +"""Pydantic Models for the Pydantic Example + +This file contains Pydantic models that are imported by the F# code. +These models demonstrate how to integrate with existing Python Pydantic +models from F#. +""" + +from pydantic import BaseModel + + +class User(BaseModel): + id: int + name: str + email: str | None = None + age: int | None = None + + +class Product(BaseModel): + id: int + name: str + description: str + price: float + in_stock: bool + tags: list[str] + + +class CreateUserRequest(BaseModel): + name: str + email: str | None = None + age: int | None = None + + +class CreateProductRequest(BaseModel): + name: str + description: str + price: float + in_stock: bool + tags: list[str] diff --git a/justfile b/justfile index 5e9ece2..da452fc 100644 --- a/justfile +++ b/justfile @@ -133,4 +133,5 @@ dev-fastapi: # Run Pydantic example (importing Python Pydantic models from F#) example-pydantic: cd examples/pydantic && {{fable}} . --lang Python --outDir build + cp examples/pydantic/models.py examples/pydantic/build/ cd examples/pydantic/build && uv run python app.py