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
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,28 @@ reportUnusedVariable = false
reportUnnecessaryIsInstance = true
reportUnnecessaryComparison = true
reportUnnecessaryCast = true
reportPrivateUsage = true
reportPrivateUsage = false # Getters/setters reference private members from outside the class
reportImportCycles = true
reportDuplicateImport = true
reportConstantRedefinition = true
reportOverlappingOverload = true
reportInconsistentConstructor = true
reportImplicitStringConcatenation = true

pythonVersion = "3.12"
typeCheckingMode = "standard"

[tool.ruff]
# Keep in sync with .pre-commit-config.yaml
line-length = 120
lint.ignore = []
lint.select = ["E", "W", "F", "I", "T", "RUF", "TID", "UP"]
target-version = "py312"
include =["*.py"]

[tool.ruff.lint]
select = ["E", "W", "F", "I", "T", "RUF", "TID", "UP"]
ignore = [
"F841" # Ignore unused variable warnings
]

[tool.ruff.lint.flake8-bugbear]
# These Rust extension module types are immutable (frozen)
extend-immutable-calls = [
Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [Python] Add `Array.skipWhile` support (by @dbrattli)
* [Python] Allow `IEnumerator_1` as base class to fix typing issues (by @dbrattli)
* [Python] Add Pythonic import path syntax for relative imports (`.module`, `..parent`, `...grandparent`) (by @dbrattli)
* [Python] Add `[<Py.DecorateTemplate>]` attribute for creating custom decorator attributes (by @dbrattli)
* [Python] Add `[<Py.ClassAttributesTemplate>]` attribute for creating custom class attribute shortcuts (by @dbrattli)
Expand Down
2 changes: 2 additions & 0 deletions src/Fable.Compiler/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [Python] Add `Array.skipWhile` support (by @dbrattli)
* [Python] Allow `IEnumerator_1` as base class to fix typing issues (by @dbrattli)
* [Python] Add Pythonic import path syntax for relative imports (`.module`, `..parent`, `...grandparent`) (by @dbrattli)
* [Python] Add `[<Py.DecorateTemplate>]` attribute for creating custom decorator attributes (by @dbrattli)
* [Python] Add `[<Py.ClassAttributesTemplate>]` attribute for creating custom class attribute shortcuts (by @dbrattli)
Expand Down
9 changes: 8 additions & 1 deletion src/Fable.Transforms/Python/Fable2Python.Transforms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2606,13 +2606,20 @@ let declareClassType
let interfaces, stmts =
// We only use a few interfaces as base classes. The rest is handled as Python protocols (PEP 544) to avoid a massive
// inheritance tree that will prevent Python of finding a consistent method resolution order.
let allowedInterfaces = [ "IDisposable" ]
let allowedInterfaces = [ "IDisposable"; "IEnumerator_1" ]

// Check if class implements IEnumerator_1 (which already inherits from IDisposable)
let hasIEnumerator =
ent.AllInterfaces
|> Seq.exists (fun int -> Helpers.removeNamespace (int.Entity.FullName) = "IEnumerator_1")

ent.AllInterfaces
|> List.ofSeq
|> List.filter (fun int ->
let name = Helpers.removeNamespace (int.Entity.FullName)
// Filter out IDisposable if IEnumerator_1 is present to avoid diamond inheritance
allowedInterfaces |> List.contains name
&& not (hasIEnumerator && name = "IDisposable")
)
|> List.map (fun int ->
let genericArgs =
Expand Down
3 changes: 3 additions & 0 deletions src/fable-library-py/fable_library/array_.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

_T = TypeVar("_T")

# Define the typed array constructors.
Int8Array = array.FSharpCons[int8]("Int8")
UInt8Array = array.FSharpCons[uint8]("UInt8")
Int16Array = array.FSharpCons[int16]("Int16")
Expand Down Expand Up @@ -115,6 +116,7 @@
copy = array.copy
take = array.take
skip = array.skip
skip_while = array.skip_while
compare_to = array.compare_to
sort_with = array.sort_with
choose = array.choose
Expand Down Expand Up @@ -206,6 +208,7 @@
"set_slice",
"singleton",
"skip",
"skip_while",
"sort",
"sort_by",
"sort_by",
Expand Down
365 changes: 187 additions & 178 deletions src/fable-library-py/fable_library/core/array.pyi

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/fable-library-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ python-source = "." # Look at project root
module-name = "fable_library.core._core" # Full module path
features = ["pyo3/extension-module"]

[tool.pyright]
reportPrivateUsage = false

[tool.pytest.ini_options]
minversion = "8.0"
pythonpath = "."
Expand Down
56 changes: 52 additions & 4 deletions src/fable-library-py/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,32 @@ impl FSharpArray {
Ok(FSharpArray { storage: results })
}

#[pyo3(signature = (predicate, cons=None))]
pub fn skip_while(
&self,
py: Python<'_>,
predicate: &Bound<'_, PyAny>,
cons: Option<&Bound<'_, PyAny>>,
) -> PyResult<FSharpArray> {
let len = self.storage.len();
let mut count = 0;

while count < len {
let item = self.get_item_at_index(count as isize, py)?;
if !predicate.call1((item,))?.is_truthy()? {
break;
}
count += 1;
}

if count == len {
let fs_cons = FSharpCons::extract(cons, self.storage.get_type());
return fs_cons.allocate(py, 0);
}

self.skip(py, count as isize, cons)
}

pub fn chunk_by_size(&self, py: Python<'_>, chunk_size: usize) -> PyResult<Self> {
if chunk_size < 1 {
return Err(PyErr::new::<exceptions::PyValueError, _>(
Expand Down Expand Up @@ -2908,17 +2934,26 @@ impl FSharpArray {
}
}

#[pyo3(signature = (projection, comparer))]
#[pyo3(signature = (projection, comparer=None))]
pub fn sort_by(
&self,
py: Python<'_>,
projection: &Bound<'_, PyAny>,
comparer: &Bound<'_, PyAny>,
comparer: Option<&Bound<'_, PyAny>>,
) -> PyResult<FSharpArray> {
let mut result = self.clone();
// Use provided comparer or create a default one
let default_comparer;
let comparer_ref = match comparer {
Some(c) => c,
None => {
default_comparer = DefaultComparer::new()?.into_pyobject(py)?;
&default_comparer
}
};
result.storage = result
.storage
.sort_by_with_projection(py, projection, comparer)?;
.sort_by_with_projection(py, projection, comparer_ref)?;
Ok(result)
}

Expand Down Expand Up @@ -3188,6 +3223,17 @@ pub fn skip(
array.skip(py, count, cons)
}

#[pyfunction]
#[pyo3(signature = (predicate, array, cons=None))]
pub fn skip_while(
py: Python<'_>,
predicate: &Bound<'_, PyAny>,
array: &FSharpArray,
cons: Option<&Bound<'_, PyAny>>,
) -> PyResult<FSharpArray> {
array.skip_while(py, predicate, cons)
}

#[pyfunction]
pub fn chunk_by_size(
py: Python<'_>,
Expand Down Expand Up @@ -4042,11 +4088,12 @@ pub fn sort(
}

#[pyfunction]
#[pyo3(signature = (projection, array, comparer=None))]
pub fn sort_by(
py: Python<'_>,
projection: &Bound<'_, PyAny>,
array: &FSharpArray,
comparer: &Bound<'_, PyAny>,
comparer: Option<&Bound<'_, PyAny>>,
) -> PyResult<FSharpArray> {
array.sort_by(py, projection, comparer)
}
Expand Down Expand Up @@ -4492,6 +4539,7 @@ pub fn register_array_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()
m.add_function(wrap_pyfunction!(set_slice, &m)?)?;
m.add_function(wrap_pyfunction!(singleton, &m)?)?;
m.add_function(wrap_pyfunction!(skip, &m)?)?;
m.add_function(wrap_pyfunction!(skip_while, &m)?)?;
m.add_function(wrap_pyfunction!(sort, &m)?)?;
m.add_function(wrap_pyfunction!(sort_by, &m)?)?;
m.add_function(wrap_pyfunction!(sort_in_place, &m)?)?;
Expand Down
8 changes: 8 additions & 0 deletions tests/Python/TestArray.fs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,14 @@ let ``test Array.truncate works`` () =
try xs |> Array.truncate 20 |> Array.length with _ -> -1
|> equal 5

[<Fact>]
let ``test Array.skipWhile works`` () =
let xs = [|1; 2; 3; 4; 5|]
xs |> Array.skipWhile (fun x -> x < 3) |> equal [|3; 4; 5|]
xs |> Array.skipWhile (fun x -> x < 1) |> equal [|1; 2; 3; 4; 5|]
xs |> Array.skipWhile (fun x -> x < 6) |> equal [||]
[||] |> Array.skipWhile (fun x -> x < 3) |> equal [||]

// [<Fact>]
// let ``test Array.sortDescending works`` () =
// let xs = [|3; 4; 1; -3; 2; 10|]
Expand Down
Loading