Skip to content

Commit 6e1e902

Browse files
dbrattliclaude
andauthored
feat: add Fable.Types module for runtime type detection (#184)
* feat: add typed array support to Json serialization Add support for serializing F# typed arrays (Int32Array, Int64Array, etc.) to JSON by converting them to Python lists. Supported array types: - Generic arrays: FSharpArray, GenericArray - Typed arrays: Int8Array, Int16Array, Int32Array, Int64Array, UInt8Array, UInt16Array, UInt32Array, UInt64Array, Float32Array, Float64Array 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> * feat: add Fable.Types module for runtime type detection Add utilities for detecting Fable types at runtime in Python: - typeName: get Python type name of an object - isIntegralType: check for Int8-64, UInt8-64 - isNumericType: check for integral types + Float32/64 - isArrayType: check for FSharpArray, GenericArray, typed arrays Also includes code formatting improvements and test coverage. Release-As: 5.0.0-alpha.21.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> * chore: update release-please config for 21.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 29026c6 commit 6e1e902

File tree

15 files changed

+270
-54
lines changed

15 files changed

+270
-54
lines changed

.markdownlint.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"MD024": false
3+
}

CHANGELOG.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99

1010
## [5.0.0-alpha.21.1](https://github.com/fable-compiler/Fable.Python/compare/v5.0.0-alpha.21.0...v5.0.0-alpha.21.1) (2025-12-18)
1111

12-
1312
### Features
1413

1514
* Add Json static class with Fable-aware serialization ([#175](https://github.com/fable-compiler/Fable.Python/issues/175)) ([1eb5005](https://github.com/fable-compiler/Fable.Python/commit/1eb500523c6d6f43247bc3510a6045ec8d9d9d4e))
1615

17-
1816
### Bug Fixes
1917

2018
* correct manifest to last released version ([bb9cb88](https://github.com/fable-compiler/Fable.Python/commit/bb9cb881c00b7ea6809ad3a87a35a7ebc0ff9008))
@@ -23,7 +21,6 @@
2321

2422
## [5.0.0-alpha.21.0](https://github.com/fable-compiler/Fable.Python/compare/v5.0.0-alpha.21...v5.0.0-alpha.21.0) (2025-12-16)
2523

26-
2724
### Features
2825

2926
* Add exception types ([#173](https://github.com/fable-compiler/Fable.Python/issues/173)) ([72f09b2](https://github.com/fable-compiler/Fable.Python/commit/72f09b2ff6e208e3ab057430a355814611abd46c))
@@ -33,7 +30,6 @@
3330
* Fable v5 ([#147](https://github.com/fable-compiler/Fable.Python/issues/147)) ([abf8e6a](https://github.com/fable-compiler/Fable.Python/commit/abf8e6a1f7bbc2eae152431d04d6b8b5675f7795))
3431
* FastAPI async handlers ([#174](https://github.com/fable-compiler/Fable.Python/issues/174)) ([26cec1f](https://github.com/fable-compiler/Fable.Python/commit/26cec1f239f9244a7c1da1d00bbf1a479596bb3c))
3532

36-
3733
### Bug Fixes
3834

3935
* add missing models.py for pydantic example and update README ([#168](https://github.com/fable-compiler/Fable.Python/issues/168)) ([b76ce98](https://github.com/fable-compiler/Fable.Python/commit/b76ce989e0598d0007a8a1be9addfea97936eae7))
@@ -43,35 +39,30 @@
4339
* use plain int/string types for open ([b202a25](https://github.com/fable-compiler/Fable.Python/commit/b202a25bd7f48538fadc50125294a9252714d364))
4440
* use string types for open ([f211f8b](https://github.com/fable-compiler/Fable.Python/commit/f211f8bb9445dd6930926fe48aab6a69720dd30f))
4541

46-
4742
### Miscellaneous Chores
4843

4944
* sync with Fable 5.0.0-alpha.21 ([fd2685c](https://github.com/fable-compiler/Fable.Python/commit/fd2685c2f992c2d8058ac1bc1261d647271359df))
5045

5146
## [5.0.0-alpha.20.2](https://github.com/fable-compiler/Fable.Python/compare/v5.0.0-alpha.20.1...v5.0.0-alpha.20.2) (2025-12-09)
5247

53-
5448
### Features
5549

5650
* add Python stdlib bindings for logging, random, and expand string module ([#166](https://github.com/fable-compiler/Fable.Python/issues/166)) ([709d6c2](https://github.com/fable-compiler/Fable.Python/commit/709d6c2b29199965926ff639727fdcc1bb2e1fb8))
5751

5852
## [5.0.0-alpha.20.1](https://github.com/fable-compiler/Fable.Python/compare/v5.0.0-alpha.20...v5.0.0-alpha.20.1) (2025-12-08)
5953

60-
6154
### Bug Fixes
6255

6356
* relax FSharp.Core dependency to &gt;= 5.0.0 ([#163](https://github.com/fable-compiler/Fable.Python/issues/163)) ([10eb65b](https://github.com/fable-compiler/Fable.Python/commit/10eb65b22a157078e1b66bd8fb202b0cd2acbedc))
6457

6558
## [5.0.0-alpha.20](https://github.com/fable-compiler/Fable.Python/compare/v5.0.0-alpha.20...v5.0.0-alpha.20) (2025-12-08)
6659

67-
6860
### Features
6961

7062
* add write ([334e800](https://github.com/fable-compiler/Fable.Python/commit/334e80089c081bda25633f83dae037fc6c8fe6f5))
7163
* Added bindings for Pydantic and FastAPI + examples ([#151](https://github.com/fable-compiler/Fable.Python/issues/151)) ([826629e](https://github.com/fable-compiler/Fable.Python/commit/826629e465fca15d444a4ca37b851b8aab488f9a))
7264
* Fable v5 ([#147](https://github.com/fable-compiler/Fable.Python/issues/147)) ([abf8e6a](https://github.com/fable-compiler/Fable.Python/commit/abf8e6a1f7bbc2eae152431d04d6b8b5675f7795))
7365

74-
7566
### Bug Fixes
7667

7768
* handle return type correctly ([a634168](https://github.com/fable-compiler/Fable.Python/commit/a6341684ac8bb3f1b244f448c22e1fe6f208fbc0))

release-please-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"packages": {
44
".": {
55
"release-type": "simple",
6-
"release-as": "5.0.0-alpha.21.2",
6+
"release-as": "5.0.0-alpha.21.3",
77
"include-component-in-tag": false,
88
"include-v-in-tag": true,
99
"changelog-path": "CHANGELOG.md"

src/Fable.Python.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
<Compile Include="pydantic/Pydantic.fs" />
3838
<Compile Include="fastapi/FastAPI.fs" />
39+
40+
<Compile Include="fable/Types.fs" />
3941
</ItemGroup>
4042
<ItemGroup>
4143
<Content Include="pyproject.toml; *.fsproj; **\*.fs; **\*.fsi" PackagePath="fable\" />

src/fable/Types.fs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/// Utilities for detecting and working with Fable types at runtime in Python.
2+
/// These helpers are useful when you need to distinguish between native Python types
3+
/// and Fable-compiled F# types (e.g., Int32, Float64, FSharpArray).
4+
module Fable.Python.Fable.Types
5+
6+
open Fable.Core
7+
8+
/// Get the Python type name of an object
9+
[<Emit("type($0).__name__")>]
10+
let typeName (o: obj) : string = nativeOnly
11+
12+
/// Check if an object is a Fable integral type (Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64)
13+
let isIntegralType (o: obj) : bool =
14+
match typeName o with
15+
| "Int8"
16+
| "Int16"
17+
| "Int32"
18+
| "Int64"
19+
| "UInt8"
20+
| "UInt16"
21+
| "UInt32"
22+
| "UInt64" -> true
23+
| _ -> false
24+
25+
/// Check if an object is a Fable numeric type (integral types + Float32, Float64)
26+
let isNumericType (o: obj) : bool =
27+
match typeName o with
28+
| "Int8"
29+
| "Int16"
30+
| "Int32"
31+
| "Int64"
32+
| "UInt8"
33+
| "UInt16"
34+
| "UInt32"
35+
| "UInt64"
36+
| "Float32"
37+
| "Float64" -> true
38+
| _ -> false
39+
40+
/// Check if an object is a Fable array type (FSharpArray, GenericArray, or typed arrays)
41+
let isArrayType (o: obj) : bool =
42+
match typeName o with
43+
| "FSharpArray"
44+
| "GenericArray"
45+
| "Int8Array"
46+
| "Int16Array"
47+
| "Int32Array"
48+
| "Int64Array"
49+
| "UInt8Array"
50+
| "UInt16Array"
51+
| "UInt32Array"
52+
| "UInt64Array"
53+
| "Float32Array"
54+
| "Float64Array" -> true
55+
| _ -> false

src/fastapi/FastAPI.fs

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -136,33 +136,27 @@ type RouterWebSocketAttribute(path: string) =
136136

137137
/// JSON response class
138138
[<Import("JSONResponse", "fastapi.responses")>]
139-
type JSONResponse(content: obj, ?status_code: int) =
140-
class end
139+
type JSONResponse(content: obj, ?status_code: int) = class end
141140

142141
/// HTML response class
143142
[<Import("HTMLResponse", "fastapi.responses")>]
144-
type HTMLResponse(content: string, ?status_code: int) =
145-
class end
143+
type HTMLResponse(content: string, ?status_code: int) = class end
146144

147145
/// Plain text response class
148146
[<Import("PlainTextResponse", "fastapi.responses")>]
149-
type PlainTextResponse(content: string, ?status_code: int) =
150-
class end
147+
type PlainTextResponse(content: string, ?status_code: int) = class end
151148

152149
/// Redirect response class
153150
[<Import("RedirectResponse", "fastapi.responses")>]
154-
type RedirectResponse(url: string, ?status_code: int) =
155-
class end
151+
type RedirectResponse(url: string, ?status_code: int) = class end
156152

157153
/// Streaming response class
158154
[<Import("StreamingResponse", "fastapi.responses")>]
159-
type StreamingResponse(content: obj, ?media_type: string) =
160-
class end
155+
type StreamingResponse(content: obj, ?media_type: string) = class end
161156

162157
/// File response class
163158
[<Import("FileResponse", "fastapi.responses")>]
164-
type FileResponse(path: string, ?filename: string, ?media_type: string) =
165-
class end
159+
type FileResponse(path: string, ?filename: string, ?media_type: string) = class end
166160

167161
// ============================================================================
168162
// Request and WebSocket
@@ -256,8 +250,7 @@ type WebSocket() =
256250

257251
/// HTTP exception for returning error responses
258252
[<Import("HTTPException", "fastapi")>]
259-
type HTTPException(status_code: int, ?detail: string) =
260-
class end
253+
type HTTPException(status_code: int, ?detail: string) = class end
261254

262255
// ============================================================================
263256
// Dependency Injection
@@ -422,7 +415,7 @@ type UploadFile() =
422415
/// File parameter marker
423416
[<Import("File", "fastapi")>]
424417
[<Emit("$0()")>]
425-
let File() : UploadFile = nativeOnly
418+
let File () : UploadFile = nativeOnly
426419

427420
/// Form parameter marker
428421
[<Import("Form", "fastapi")>]
@@ -452,7 +445,10 @@ type APIRouter(?prefix: string, ?tags: ResizeArray<string>) =
452445

453446
/// Include another router with prefix and tags
454447
[<Emit("$0.include_router($1, prefix=$2, tags=$3)")>]
455-
member _.include_router_with_prefix_and_tags(_router: APIRouter, _prefix: string, _tags: ResizeArray<string>) : unit = nativeOnly
448+
member _.include_router_with_prefix_and_tags
449+
(_router: APIRouter, _prefix: string, _tags: ResizeArray<string>)
450+
: unit =
451+
nativeOnly
456452

457453
// ============================================================================
458454
// FastAPI Application
@@ -471,7 +467,10 @@ type FastAPI(?title: string, ?description: string, ?version: string) =
471467

472468
/// Include a router with prefix and tags
473469
[<Emit("$0.include_router($1, prefix=$2, tags=$3)")>]
474-
member _.include_router_with_prefix_and_tags(_router: APIRouter, _prefix: string, _tags: ResizeArray<string>) : unit = nativeOnly
470+
member _.include_router_with_prefix_and_tags
471+
(_router: APIRouter, _prefix: string, _tags: ResizeArray<string>)
472+
: unit =
473+
nativeOnly
475474

476475
/// Add middleware
477476
[<Emit("$0.add_middleware($1)")>]
@@ -571,8 +570,7 @@ type OAuth2PasswordRequestForm() =
571570

572571
/// HTTP Basic authentication
573572
[<Import("HTTPBasic", "fastapi.security")>]
574-
type HTTPBasic() =
575-
class end
573+
type HTTPBasic() = class end
576574

577575
/// HTTP Basic credentials
578576
[<Import("HTTPBasicCredentials", "fastapi.security")>]
@@ -587,8 +585,7 @@ type HTTPBasicCredentials() =
587585

588586
/// HTTP Bearer authentication
589587
[<Import("HTTPBearer", "fastapi.security")>]
590-
type HTTPBearer() =
591-
class end
588+
type HTTPBearer() = class end
592589

593590
/// HTTP Bearer credentials (token in Authorization header)
594591
[<Import("HTTPAuthorizationCredentials", "fastapi.security")>]
@@ -603,27 +600,23 @@ type HTTPAuthorizationCredentials() =
603600

604601
/// API Key in header
605602
[<Import("APIKeyHeader", "fastapi.security")>]
606-
type APIKeyHeader(name: string) =
607-
class end
603+
type APIKeyHeader(name: string) = class end
608604

609605
/// API Key in query parameter
610606
[<Import("APIKeyQuery", "fastapi.security")>]
611-
type APIKeyQuery(name: string) =
612-
class end
607+
type APIKeyQuery(name: string) = class end
613608

614609
/// API Key in cookie
615610
[<Import("APIKeyCookie", "fastapi.security")>]
616-
type APIKeyCookie(name: string) =
617-
class end
611+
type APIKeyCookie(name: string) = class end
618612

619613
// ============================================================================
620614
// CORS Middleware
621615
// ============================================================================
622616

623617
/// CORS middleware for handling Cross-Origin Resource Sharing
624618
[<Import("CORSMiddleware", "fastapi.middleware.cors")>]
625-
type CORSMiddleware =
626-
class end
619+
type CORSMiddleware = class end
627620

628621
/// CORS middleware configuration helper
629622
module CORSMiddleware =
@@ -634,7 +627,15 @@ module CORSMiddleware =
634627

635628
/// Add CORS middleware to app with all origins allowed
636629
[<Emit("$0.add_middleware($1, allow_origins=$2, allow_credentials=$3, allow_methods=$4, allow_headers=$5)")>]
637-
let addToApp (_app: FastAPI) (_middleware: obj) (_origins: string array) (_credentials: bool) (_methods: string array) (_headers: string array) : unit = nativeOnly
630+
let addToApp
631+
(_app: FastAPI)
632+
(_middleware: obj)
633+
(_origins: string array)
634+
(_credentials: bool)
635+
(_methods: string array)
636+
(_headers: string array)
637+
: unit =
638+
nativeOnly
638639

639640
// ============================================================================
640641
// Encoders

src/flask/Flask.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,7 @@ let make_response_with_status (_body: obj) (_status: int) : Response = nativeOnl
259259

260260
/// Flask Blueprint for modular applications
261261
[<Import("Blueprint", "flask")>]
262-
type Blueprint(name: string, import_name: string, ?url_prefix: string) =
263-
class end
262+
type Blueprint(name: string, import_name: string, ?url_prefix: string) = class end
264263

265264
// ============================================================================
266265
// Flask Application

src/pydantic/Pydantic.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,3 @@ type Base64Str = string
458458
/// JSON type - validates JSON string and parses it
459459
[<Import("Json", "pydantic")>]
460460
type JsonValue = obj
461-

0 commit comments

Comments
 (0)