Skip to content

Commit 709d6c2

Browse files
dbrattliclaude
andauthored
feat: add Python stdlib bindings for logging, random, and expand string module (#166)
- Add logging module with Logger, Handler, Formatter classes and Level constants - Add random module with seed, random, uniform, choice, sample, shuffle, and distribution functions - Expand string module with Template class, constants (ascii_letters, digits, etc.), and capwords - Add builtins.bytes function for F# byte array to Python bytes conversion - Fix sys module to use ResizeArray for argv/path and add int conversion for exit - Add comprehensive tests for all new modules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 92f0b3d commit 709d6c2

File tree

14 files changed

+967
-5
lines changed

14 files changed

+967
-5
lines changed

src/Fable.Python.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
<Compile Include="stdlib/Builtins.fs" />
2020
<Compile Include="stdlib/Json.fs" />
2121
<Compile Include="stdlib/Html.fs" />
22+
<Compile Include="stdlib/Logging.fs" />
2223
<Compile Include="stdlib/Math.fs" />
24+
<Compile Include="stdlib/Random.fs" />
2325
<Compile Include="stdlib/Os.fs" />
2426
<Compile Include="stdlib/Queue.fs" />
2527
<Compile Include="stdlib/String.fs" />

src/stdlib/Builtins.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ type IExports =
200200
abstract int: obj -> int
201201
/// Object to float
202202
abstract float: obj -> float
203+
/// Convert to bytes
204+
abstract bytes: byte[] -> byte[]
205+
/// Convert string to bytes with encoding
206+
abstract bytes: string * encoding: string -> byte[]
203207

204208
/// Return the largest item in an iterable or the largest of two or more arguments.
205209
abstract max: 'T * 'T -> 'T

src/stdlib/Logging.fs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/// Type bindings for Python logging module: https://docs.python.org/3/library/logging.html
2+
module Fable.Python.Logging
3+
4+
open Fable.Core
5+
6+
// fsharplint:disable MemberNames
7+
8+
/// Logging levels
9+
[<RequireQualifiedAccess>]
10+
module Level =
11+
[<Literal>]
12+
let CRITICAL = 50
13+
14+
[<Literal>]
15+
let FATAL = 50
16+
17+
[<Literal>]
18+
let ERROR = 40
19+
20+
[<Literal>]
21+
let WARNING = 30
22+
23+
[<Literal>]
24+
let WARN = 30
25+
26+
[<Literal>]
27+
let INFO = 20
28+
29+
[<Literal>]
30+
let DEBUG = 10
31+
32+
[<Literal>]
33+
let NOTSET = 0
34+
35+
/// Formatter for log records
36+
[<Import("Formatter", "logging")>]
37+
type Formatter(fmt: string, ?datefmt: string) =
38+
member _.format(record: obj) : string = nativeOnly
39+
40+
/// Handler base type
41+
[<Import("Handler", "logging")>]
42+
type Handler() =
43+
/// Set the logging level
44+
member _.setLevel(level: int) : unit = nativeOnly
45+
/// Set the formatter for this handler
46+
member _.setFormatter(formatter: Formatter) : unit = nativeOnly
47+
48+
/// StreamHandler - logs to a stream (stderr by default)
49+
[<Import("StreamHandler", "logging")>]
50+
type StreamHandler(?stream: obj) =
51+
inherit Handler()
52+
53+
/// FileHandler - logs to a file
54+
[<Import("FileHandler", "logging")>]
55+
type FileHandler(filename: string, ?mode: string) =
56+
inherit Handler()
57+
58+
/// Logger instance
59+
[<Import("Logger", "logging")>]
60+
type Logger(name: string, ?level: int) =
61+
/// Log a message with severity DEBUG
62+
member _.debug(msg: string) : unit = nativeOnly
63+
/// Log a message with severity INFO
64+
member _.info(msg: string) : unit = nativeOnly
65+
/// Log a message with severity WARNING
66+
member _.warning(msg: string) : unit = nativeOnly
67+
/// Log a message with severity ERROR
68+
member _.error(msg: string) : unit = nativeOnly
69+
/// Log a message with severity CRITICAL
70+
member _.critical(msg: string) : unit = nativeOnly
71+
/// Log a message with severity ERROR and exception info
72+
member _.``exception``(msg: string) : unit = nativeOnly
73+
/// Log a message with the specified level
74+
[<Emit("$0.log(int($1), $2)")>]
75+
member _.log(level: int, msg: string) : unit = nativeOnly
76+
/// Set the logging level
77+
[<Emit("$0.setLevel(int($1))")>]
78+
member _.setLevel(level: int) : unit = nativeOnly
79+
/// Get the effective logging level
80+
[<Emit("$0.getEffectiveLevel()")>]
81+
member _.getEffectiveLevel() : int = nativeOnly
82+
/// Check if the logger is enabled for the specified level
83+
[<Emit("$0.isEnabledFor(int($1))")>]
84+
member _.isEnabledFor(level: int) : bool = nativeOnly
85+
/// Add the specified handler to this logger
86+
[<Emit("$0.addHandler($1)")>]
87+
member _.addHandler(handler: Handler) : unit = nativeOnly
88+
/// Remove the specified handler from this logger
89+
[<Emit("$0.removeHandler($1)")>]
90+
member _.removeHandler(handler: Handler) : unit = nativeOnly
91+
/// Check if this logger has any handlers configured
92+
[<Emit("$0.hasHandlers()")>]
93+
member _.hasHandlers() : bool = nativeOnly
94+
/// The name of the logger
95+
member _.name: string = nativeOnly
96+
97+
[<Erase>]
98+
type IExports =
99+
/// Log a message with severity DEBUG on the root logger
100+
abstract debug: msg: string -> unit
101+
/// Log a message with severity INFO on the root logger
102+
abstract info: msg: string -> unit
103+
/// Log a message with severity WARNING on the root logger
104+
abstract warning: msg: string -> unit
105+
/// Log a message with severity ERROR on the root logger
106+
abstract error: msg: string -> unit
107+
/// Log a message with severity CRITICAL on the root logger
108+
abstract critical: msg: string -> unit
109+
/// Log a message with severity ERROR and exception info on the root logger
110+
abstract ``exception``: msg: string -> unit
111+
/// Log a message with the specified level on the root logger
112+
abstract log: level: int * msg: string -> unit
113+
114+
/// Return a logger with the specified name
115+
[<Emit("$0.getLogger($1)")>]
116+
abstract getLogger: name: string -> Logger
117+
/// Return the root logger
118+
[<Emit("$0.getLogger()")>]
119+
abstract getLogger: unit -> Logger
120+
121+
/// Do basic configuration for the logging system
122+
[<Emit("$0.basicConfig($1...)")>]
123+
[<NamedParams(fromIndex = 0)>]
124+
abstract basicConfig:
125+
?filename: string *
126+
?filemode: string *
127+
?format: string *
128+
?datefmt: string *
129+
?style: string *
130+
?level: int *
131+
?stream: obj *
132+
?force: bool ->
133+
unit
134+
135+
/// Disable all logging calls of severity level and below
136+
[<Emit("$0.disable($1)")>]
137+
abstract disable: level: int -> unit
138+
139+
/// Logging level constants
140+
abstract DEBUG: int
141+
abstract INFO: int
142+
abstract WARNING: int
143+
abstract ERROR: int
144+
abstract CRITICAL: int
145+
146+
/// Logging facility for Python
147+
[<ImportAll("logging")>]
148+
let logging: IExports = nativeOnly

src/stdlib/Random.fs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/// Type bindings for Python random module: https://docs.python.org/3/library/random.html
2+
module Fable.Python.Random
3+
4+
open System.Collections.Generic
5+
open Fable.Core
6+
7+
// fsharplint:disable MemberNames
8+
9+
[<Erase>]
10+
type IExports =
11+
/// Initialize the random number generator
12+
/// See https://docs.python.org/3/library/random.html#random.seed
13+
[<Emit("$0.seed(int($1))")>]
14+
abstract seed: a: int -> unit
15+
/// Initialize the random number generator
16+
/// See https://docs.python.org/3/library/random.html#random.seed
17+
abstract seed: a: nativeint -> unit
18+
/// Initialize the random number generator
19+
/// See https://docs.python.org/3/library/random.html#random.seed
20+
abstract seed: a: float -> unit
21+
/// Initialize the random number generator
22+
/// See https://docs.python.org/3/library/random.html#random.seed
23+
abstract seed: a: string -> unit
24+
/// Initialize the random number generator with current time
25+
/// See https://docs.python.org/3/library/random.html#random.seed
26+
abstract seed: unit -> unit
27+
28+
/// Return a random floating point number in the range [0.0, 1.0)
29+
/// See https://docs.python.org/3/library/random.html#random.random
30+
abstract random: unit -> float
31+
32+
/// Return a random floating point number N such that a <= N <= b
33+
/// See https://docs.python.org/3/library/random.html#random.uniform
34+
abstract uniform: a: float * b: float -> float
35+
36+
/// Return a random floating point number N such that low <= N <= high with triangular distribution
37+
/// See https://docs.python.org/3/library/random.html#random.triangular
38+
abstract triangular: low: float * high: float * mode: float -> float
39+
/// Return a random floating point number N such that 0 <= N <= 1 with triangular distribution
40+
/// See https://docs.python.org/3/library/random.html#random.triangular
41+
abstract triangular: unit -> float
42+
43+
/// Return a random integer N such that a <= N <= b
44+
/// See https://docs.python.org/3/library/random.html#random.randint
45+
abstract randint: a: int * b: int -> int
46+
47+
/// Return a randomly selected element from range(start, stop, step)
48+
/// See https://docs.python.org/3/library/random.html#random.randrange
49+
abstract randrange: stop: int -> int
50+
/// Return a randomly selected element from range(start, stop, step)
51+
/// See https://docs.python.org/3/library/random.html#random.randrange
52+
abstract randrange: start: int * stop: int -> int
53+
/// Return a randomly selected element from range(start, stop, step)
54+
/// See https://docs.python.org/3/library/random.html#random.randrange
55+
abstract randrange: start: int * stop: int * step: int -> int
56+
57+
/// Return a random element from the non-empty sequence
58+
/// See https://docs.python.org/3/library/random.html#random.choice
59+
[<Emit("$0.choice(list($1))")>]
60+
abstract choice: seq: 'T[] -> 'T
61+
/// Return a random element from the non-empty sequence
62+
/// See https://docs.python.org/3/library/random.html#random.choice
63+
[<Emit("$0.choice(list($1))")>]
64+
abstract choice: seq: 'T list -> 'T
65+
/// Return a random element from the non-empty sequence
66+
/// See https://docs.python.org/3/library/random.html#random.choice
67+
abstract choice: seq: ResizeArray<'T> -> 'T
68+
69+
/// Return a k length list of unique elements chosen from the population sequence
70+
/// See https://docs.python.org/3/library/random.html#random.sample
71+
[<Emit("$0.sample(list($1), int($2))")>]
72+
abstract sample: population: 'T[] * k: int -> ResizeArray<'T>
73+
/// Return a k length list of unique elements chosen from the population sequence
74+
/// See https://docs.python.org/3/library/random.html#random.sample
75+
[<Emit("$0.sample(list($1), int($2))")>]
76+
abstract sample: population: 'T list * k: int -> ResizeArray<'T>
77+
/// Return a k length list of unique elements chosen from the population sequence
78+
/// See https://docs.python.org/3/library/random.html#random.sample
79+
[<Emit("$0.sample($1, int($2))")>]
80+
abstract sample: population: ResizeArray<'T> * k: int -> ResizeArray<'T>
81+
82+
/// Return a k sized list of elements chosen from the population with replacement
83+
/// See https://docs.python.org/3/library/random.html#random.choices
84+
[<Emit("$0.choices(list($1), k=int($2))")>]
85+
abstract choices: population: 'T[] * k: int -> ResizeArray<'T>
86+
/// Return a k sized list of elements chosen from the population with replacement
87+
/// See https://docs.python.org/3/library/random.html#random.choices
88+
[<Emit("$0.choices(list($1), k=int($2))")>]
89+
abstract choices: population: 'T list * k: int -> ResizeArray<'T>
90+
/// Return a k sized list of elements chosen from the population with replacement
91+
/// See https://docs.python.org/3/library/random.html#random.choices
92+
[<Emit("$0.choices($1, k=int($2))")>]
93+
abstract choices: population: ResizeArray<'T> * k: int -> ResizeArray<'T>
94+
95+
/// Shuffle the sequence x in place
96+
/// See https://docs.python.org/3/library/random.html#random.shuffle
97+
abstract shuffle: x: 'T[] -> unit
98+
/// Shuffle the sequence x in place
99+
/// See https://docs.python.org/3/library/random.html#random.shuffle
100+
abstract shuffle: x: ResizeArray<'T> -> unit
101+
102+
/// Return a random integer with k random bits
103+
/// See https://docs.python.org/3/library/random.html#random.getrandbits
104+
abstract getrandbits: k: int -> int
105+
106+
/// Beta distribution
107+
/// See https://docs.python.org/3/library/random.html#random.betavariate
108+
abstract betavariate: alpha: float * beta: float -> float
109+
110+
/// Exponential distribution
111+
/// See https://docs.python.org/3/library/random.html#random.expovariate
112+
abstract expovariate: lambd: float -> float
113+
114+
/// Gamma distribution
115+
/// See https://docs.python.org/3/library/random.html#random.gammavariate
116+
abstract gammavariate: alpha: float * beta: float -> float
117+
118+
/// Gaussian distribution (same as normalvariate but faster)
119+
/// See https://docs.python.org/3/library/random.html#random.gauss
120+
abstract gauss: mu: float * sigma: float -> float
121+
122+
/// Log normal distribution
123+
/// See https://docs.python.org/3/library/random.html#random.lognormvariate
124+
abstract lognormvariate: mu: float * sigma: float -> float
125+
126+
/// Normal distribution
127+
/// See https://docs.python.org/3/library/random.html#random.normalvariate
128+
abstract normalvariate: mu: float * sigma: float -> float
129+
130+
/// Von Mises distribution
131+
/// See https://docs.python.org/3/library/random.html#random.vonmisesvariate
132+
abstract vonmisesvariate: mu: float * kappa: float -> float
133+
134+
/// Pareto distribution
135+
/// See https://docs.python.org/3/library/random.html#random.paretovariate
136+
abstract paretovariate: alpha: float -> float
137+
138+
/// Weibull distribution
139+
/// See https://docs.python.org/3/library/random.html#random.weibullvariate
140+
abstract weibullvariate: alpha: float * beta: float -> float
141+
142+
/// Random variable generators
143+
[<ImportAll("random")>]
144+
let random: IExports = nativeOnly

src/stdlib/String.fs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// Type bindings for Python string operations: https://docs.python.org/3/library/stdtypes.html#string-methods
1+
/// Type bindings for Python string module: https://docs.python.org/3/library/string.html
22
module Fable.Python.String
33

44
open System
@@ -10,3 +10,64 @@ type System.String with
1010

1111
[<Emit("$0.format($1...)")>]
1212
member _.format([<ParamArray>] args: Object[]) = nativeOnly
13+
14+
/// Template class for $-based string substitution
15+
[<Import("Template", "string")>]
16+
type Template(template: string) =
17+
/// The template string passed to the constructor
18+
member _.template: string = nativeOnly
19+
20+
/// Perform substitution, returning a new string
21+
/// Raises KeyError if placeholders are missing from mapping
22+
[<Emit("$0.substitute($1)")>]
23+
member _.substitute(mapping: obj) : string = nativeOnly
24+
25+
/// Perform substitution using keyword arguments
26+
[<Emit("$0.substitute(**$1)")>]
27+
member _.substituteKw(kwargs: obj) : string = nativeOnly
28+
29+
/// Like substitute(), but returns original placeholder if missing
30+
[<Emit("$0.safe_substitute($1)")>]
31+
member _.safe_substitute(mapping: obj) : string = nativeOnly
32+
33+
/// Like substitute(), but returns original placeholder if missing (keyword args)
34+
[<Emit("$0.safe_substitute(**$1)")>]
35+
member _.safe_substituteKw(kwargs: obj) : string = nativeOnly
36+
37+
/// Returns False if the template has invalid placeholders
38+
[<Emit("$0.is_valid()")>]
39+
member _.is_valid() : bool = nativeOnly
40+
41+
/// Returns a list of valid identifiers in the template
42+
[<Emit("$0.get_identifiers()")>]
43+
member _.get_identifiers() : ResizeArray<string> = nativeOnly
44+
45+
[<Erase>]
46+
type IExports =
47+
/// The concatenation of ascii_lowercase and ascii_uppercase
48+
abstract ascii_letters: string
49+
/// The lowercase letters 'abcdefghijklmnopqrstuvwxyz'
50+
abstract ascii_lowercase: string
51+
/// The uppercase letters 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
52+
abstract ascii_uppercase: string
53+
/// The string '0123456789'
54+
abstract digits: string
55+
/// The string '0123456789abcdefABCDEF'
56+
abstract hexdigits: string
57+
/// The string '01234567'
58+
abstract octdigits: string
59+
/// String of ASCII characters considered punctuation
60+
abstract punctuation: string
61+
/// String of ASCII characters considered printable
62+
abstract printable: string
63+
/// String containing all ASCII whitespace characters
64+
abstract whitespace: string
65+
66+
/// Split the argument into words, capitalize each word, and join them
67+
abstract capwords: s: string -> string
68+
/// Split the argument into words using sep, capitalize each word, and join them
69+
abstract capwords: s: string * sep: string -> string
70+
71+
/// Python string module
72+
[<ImportAll("string")>]
73+
let pyString: IExports = nativeOnly

0 commit comments

Comments
 (0)