diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9e9800d..5af0293 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,11 +10,11 @@ "rollForward": false }, "fable": { - "version": "4.19.3", + "version": "5.1.0", "commands": [ "fable" ], "rollForward": false } } -} \ No newline at end of file +} diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9df20a1..fea5086 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -43,6 +43,12 @@ jobs: python-version: '3.12' - name: Setup Virtual Environment run: python -m venv .venv + - name: Install Python dependencies Windows + if: matrix.os == 'windows-latest' + run: .\.venv\Scripts\python.exe -m pip install --disable-pip-version-check . + - name: Install Python dependencies Unix + if: matrix.os == 'ubuntu-latest' + run: ./.venv/bin/python -m pip install --disable-pip-version-check . # BUILD - name: make script executable @@ -54,4 +60,4 @@ jobs: run: ./build.sh runtests - name: Test (Windows) if: matrix.os == 'windows-latest' - run: .\build.cmd runtests \ No newline at end of file + run: .\build.cmd runtests diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e706bfc..563f9ed 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,13 @@ +### 8.0.0 (Unreleased) + +Breaking changes. + +- Update Fable tooling and test dependencies to Fable 5.1.0, `fable-library` 5.1.0, and `Fable.Pyxpecto` 2.0.0. +- JavaScript and Python builds now store dynamic members through the internal `Properties` dictionary instead of mirroring them as native object attributes. Use `SetProperty`, `TryGetDynamicPropertyHelper`, `GetPropertyHelpers`, or `GetProperties` to work with dynamic members. +- Fix JavaScript and Python dynamic-property enumeration so compiler-generated backing fields are not returned as dynamic members. +- Fix Python deep copy for `ResizeArray` values by handling native Python lists before the DynamicObj-only collection cases. +- Fix Python native `==` and `hash()` interop so it matches `DynamicObj` structural equality and hashing. + ### 7.1.0+96eef97 (Released 2025-10-31) Extend and test deepHash through guard handling option values. FSharp did not correctly calculate hash codes for equal option values. diff --git a/global.json b/global.json index 989a69c..35bf0f0 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "10.0.204", "rollForward": "latestMinor" } } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..83fb48b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "DynamicObj" +version = "8.0.0" +requires-python = ">=3.12" +dependencies = [ + "fable-library==5.1.0", +] diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 2822c02..f00860e 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -44,13 +44,6 @@ type DynamicObj() = /// /// The name of the property to get the PropertyHelper for member this.TryGetDynamicPropertyHelper (propertyName: string) : PropertyHelper option = - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - FableJS.tryGetDynamicPropertyHelper this propertyName - #endif - #if FABLE_COMPILER_PYTHON - FablePy.tryGetDynamicPropertyHelper this propertyName - #endif - #if !FABLE_COMPILER match properties.TryGetValue propertyName with | true,_ -> Some { @@ -64,7 +57,6 @@ type DynamicObj() = RemoveValue = fun o -> properties.Remove(propertyName) |> ignore } | _ -> None - #endif /// /// Returns Some(PropertyHelper) if a property (static or dynamic) with the given name exists, otherwise None. @@ -131,18 +123,10 @@ type DynamicObj() = else failwith $"Cannot set value for static, immutable property \"{propertyName}\"" | None -> - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - FableJS.setPropertyValue this propertyName propertyValue - #endif - #if FABLE_COMPILER_PYTHON - FablePy.setPropertyValue this propertyName propertyValue - #endif - #if !FABLE_COMPILER // Next check the Properties collection for member match properties.TryGetValue propertyName with | true,_ -> properties.[propertyName] <- propertyValue | _ -> properties.Add(propertyName,propertyValue) - #endif /// /// Removes any dynamic property with the given name from the input DynamicObj. @@ -167,19 +151,6 @@ type DynamicObj() = /// /// whether to include instance properties (= 'static' properties on the class) member this.GetPropertyHelpers (includeInstanceProperties: bool) = - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - FableJS.getPropertyHelpers this - |> Seq.filter (fun pd -> - includeInstanceProperties || pd.IsDynamic - ) - #endif - #if FABLE_COMPILER_PYTHON - FablePy.getPropertyHelpers this - |> Seq.filter (fun pd -> - includeInstanceProperties || pd.IsDynamic - ) - #endif - #if !FABLE_COMPILER seq [ if includeInstanceProperties then yield! ReflectionUtils.getStaticProperties (this) @@ -195,7 +166,6 @@ type DynamicObj() = RemoveValue = fun o -> properties.Remove(key) |> ignore } ] - #endif |> Seq.filter (fun p -> p.Name.ToLower() <> "properties") /// @@ -402,6 +372,18 @@ type DynamicObj() = this.StructurallyEquals(other) | _ -> false + #if FABLE_COMPILER_PYTHON + // Python calls these dunder methods for native `hash(x)` and `x == y`. + member this.``__hash__``() = + HashUtils.deepHash this + |> FablePy.toPythonInt + + member this.``__eq__``(o: obj) = + match o with + | :? DynamicObj as other -> this.StructurallyEquals(other) + | _ -> false + #endif + and HashUtils = static member deepHash (o:obj) = @@ -995,6 +977,12 @@ and CopyUtils = #endif // These collections of DynamicObj can be cloned recursively + #if FABLE_COMPILER_PYTHON + | o when FablePy.ResizeArrays.isResizeArray o -> + let o = o |> unbox> + ResizeArray([for item in o -> tryDeepCopyObj item]) + |> box + #endif | :? ResizeArray as dyns -> box (ResizeArray([for dyn in dyns -> tryDeepCopyObj dyn :?> DynamicObj])) #if !FABLE_COMPILER @@ -1024,4 +1012,4 @@ and CopyUtils = box newDyn | _ -> o - tryDeepCopyObj o \ No newline at end of file + tryDeepCopyObj o diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs index ceb22a7..03047b1 100644 --- a/src/DynamicObj/FablePy.fs +++ b/src/DynamicObj/FablePy.fs @@ -7,6 +7,10 @@ open Fable.Core open System.Collections.Generic module FablePy = + + [] + let toPythonInt (value:int) : int = + nativeOnly module Dictionary = @@ -88,17 +92,19 @@ module FablePy = [] - let getOwnMemberObjects (o:obj) : Dictionary = + let getOwnMemberObjects (o:obj) : seq = nativeOnly [] let getClass (o:obj) : obj = nativeOnly - let getStaticPropertyObjects (o:obj) : Dictionary = + let getStaticPropertyObjects (o:obj) : seq = getClass o |> getOwnMemberObjects - |> Dictionary.choose PropertyObject.tryProperty + |> Seq.choose (fun (name, value) -> + PropertyObject.tryProperty value + |> Option.map (fun property -> name, property)) let removeStaticPropertyValue (o:obj) (propName:string) = setPropertyValue o propName null @@ -165,8 +171,7 @@ module FablePy = let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = getOwnMemberObjects o - |> Seq.choose (fun kv -> - let n = kv.Key + |> Seq.choose (fun (n, _) -> if isTranspiledPropertyHelper n then None else @@ -187,9 +192,7 @@ module FablePy = let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = getStaticPropertyObjects o - |> Seq.map (fun kv -> - let n = kv.Key - let po = kv.Value + |> Seq.map (fun (n, po) -> { Name = n IsStatic = true @@ -226,4 +229,9 @@ module FablePy = let isDict (o:obj) : bool = nativeOnly -#endif \ No newline at end of file + module ResizeArrays = + [] + let isResizeArray (o:obj) : bool = + nativeOnly + +#endif diff --git a/tests/DynamicObject.Tests/DynamicObj/Equals.fs b/tests/DynamicObject.Tests/DynamicObj/Equals.fs index 7a3cec3..0c89a5a 100644 --- a/tests/DynamicObject.Tests/DynamicObj/Equals.fs +++ b/tests/DynamicObject.Tests/DynamicObj/Equals.fs @@ -1,9 +1,21 @@ module Equals open Fable.Pyxpecto +open Fable.Core open DynamicObj open TestUtils +#if FABLE_COMPILER_PYTHON +module Native = + [] + let equals (left: obj) (right: obj) : bool = + nativeOnly + + [] + let hash (value: obj) : int = + nativeOnly +#endif + let tests_Equals = testList "Equals" [ testCase "Same Object" <| fun _ -> let a = DynamicObj() @@ -35,4 +47,15 @@ let tests_Equals = testList "Equals" [ a2.SetProperty("b", b2) Expect.isTrue (a.Equals(a2)) "Values should be equal" -] \ No newline at end of file + #if FABLE_COMPILER_PYTHON + testCase "Python native equality and hash use structural dynamic properties" <| fun _ -> + let a = DynamicObj() + a.SetProperty("b", 2) + let a2 = DynamicObj() + a2.SetProperty("b", 2) + + Expect.isTrue (Native.equals a a2) "Python == should use structural equality" + Expect.equal (Native.hash a) (Native.hash a2) "Python hash() should use structural hashing" + #endif + +] diff --git a/tests/DynamicObject.Tests/DynamicObj/GetProperties.fs b/tests/DynamicObject.Tests/DynamicObj/GetProperties.fs index 3c7fc18..18383cf 100644 --- a/tests/DynamicObject.Tests/DynamicObj/GetProperties.fs +++ b/tests/DynamicObject.Tests/DynamicObj/GetProperties.fs @@ -1,9 +1,25 @@ module GetProperties open Fable.Pyxpecto +open Fable.Core open DynamicObj open TestUtils +#if FABLE_COMPILER +module Native = + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + [] + let setMember (o: obj) (propertyName: string) (propertyValue: obj) : unit = + jsNative + #endif + + #if FABLE_COMPILER_PYTHON + [] + let setMember (o: obj) (propertyName: string) (propertyValue: obj) : unit = + nativeOnly + #endif +#endif + let tests_GetProperties = testList "GetProperties" [ testCase "GetProperties" <| fun _ -> let a = DynamicObj() @@ -25,4 +41,16 @@ let tests_GetProperties = testList "GetProperties" [ ] |> Seq.sortBy (fun kv -> kv.Key) Expect.sequenceEqual properties expected "Should have all properties" -] \ No newline at end of file + + testCase "ignores compiler backing fields when returning dynamic properties" <| fun _ -> + let a = DynamicObj() + a.SetProperty("dyn", "dyn") + #if FABLE_COMPILER + Native.setMember a "_stat" "stat" + #endif + let properties = a.GetProperties(false) |> List.ofSeq + let expected = [ + System.Collections.Generic.KeyValuePair("dyn", box "dyn") + ] + Expect.sequenceEqual properties expected "Should only return explicit dynamic properties" +] diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index 30c418e..efb4b21 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -42,7 +42,7 @@ - + diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..de743f0 --- /dev/null +++ b/uv.lock @@ -0,0 +1,80 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "dynamicobj" +version = "8.0.0" +source = { virtual = "." } +dependencies = [ + { name = "fable-library" }, +] + +[package.metadata] +requires-dist = [{ name = "fable-library", specifier = "==5.1.0" }] + +[[package]] +name = "fable-library" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/e0/65fe5e6b0b3816b2807d9c445b1de8264727ec31aefdaa9aa442652db84e/fable_library-5.1.0.tar.gz", hash = "sha256:e07861cfe8679e12eaa05756fce5e09a4f8aaae3b417f14071dba9398573a4ae", size = 225011, upload-time = "2026-05-28T18:51:31.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/5c/d598285de7019d8ef076323bbcb5b0ce9b7880404cbb746f97372c73c664/fable_library-5.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:23beacb2ccfb2b686ea4749c0e6c132e69b611cd8b3e7a8d33f7b4dc09ac4752", size = 1722992, upload-time = "2026-05-28T18:50:41.142Z" }, + { url = "https://files.pythonhosted.org/packages/50/a7/6efdbb24dfbffee7789fd8c0c4caa0be1be89f66cc96a8714f6603c4c216/fable_library-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c8ed6a0e891a817ba1d3fd37d03c540ea7e3f9773dcdaf83cdede1cda2e72c5", size = 1637357, upload-time = "2026-05-28T18:50:35.695Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5b/fd3469754753f7c27949c6ba5dffc14bc65afb9936ae6d4a077a647a3508/fable_library-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d798a452193ab21c7f53316de4fd6eebdd56564b4d2f4a6b5235092121698354", size = 1747274, upload-time = "2026-05-28T18:49:31.983Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0e/f4e2e2e4b68e52a55f26199c04e3d7ba79b2704f7f06cfbf1d50189cb19a/fable_library-5.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03038e88fce4ce2d04a89ba7f223f99e1c2ee04ce3cca8e4f770ccc072b88c85", size = 1710915, upload-time = "2026-05-28T18:49:43.149Z" }, + { url = "https://files.pythonhosted.org/packages/5c/79/e9efc3e9e27689978c0d03c0f5f7b7abe77767a0acae5c3bfc8882251a27/fable_library-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cf09736782c66915a0d46f90ec3efd9590afb24619e7d706997d97fa823d538", size = 1914928, upload-time = "2026-05-28T18:49:54.23Z" }, + { url = "https://files.pythonhosted.org/packages/4a/90/0566cfa959361981bea50c89b7f680ecbaf34cfe2017013f123ae2f3d0b9/fable_library-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c99c79a4186b59b93b0dea60dca6df492cab75af7291205b85e132552afcffec", size = 1851601, upload-time = "2026-05-28T18:50:05.372Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/8218e10419644d2bb6fd69bf6b2e6449c67615c58cf435e6026666f0d864/fable_library-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a215eab1e89cc04ad6e9efb070687d98f02e8b801d00f1abbce430ddd9ac5", size = 1758906, upload-time = "2026-05-28T18:50:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fa/30dbafec3b835425f1dad2884373dc9122b880cadb8d0b772cffd4045cb6/fable_library-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:35162ed32b195890fe4b1b3b1a366a4c4638bdab85a95fcae8e9ca74bfb599a3", size = 1857268, upload-time = "2026-05-28T18:50:16.911Z" }, + { url = "https://files.pythonhosted.org/packages/72/d9/a706c7e1f312cfb7f5a3da079ec1f348e0cfed1139535a3cdfd3c93aa8df/fable_library-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a840498a15c9f301b51db4c39abfaa9d62741620e8230dbc0e5b7d2aa4b26105", size = 1922221, upload-time = "2026-05-28T18:50:48.721Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e3/8d5ce2cf8b33d8f227de09674521a1dc71f65a3a423015e46405aa0b1755/fable_library-5.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8e9d047247ea9806a2f07d77b5eb266b58396f1877d9d386d3054ef22a79e4a6", size = 1987093, upload-time = "2026-05-28T18:50:59.967Z" }, + { url = "https://files.pythonhosted.org/packages/a7/27/9b854ebb135a523210271033149ec4061cc061161fb6b02578a58b7ac77c/fable_library-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9c74c8bb937a75995f081d45985ec75ebbacc5314fdec48594e71276b4d564ef", size = 2009125, upload-time = "2026-05-28T18:51:11.793Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/537826692478e97cf5a2feede9b6b5b0612ea6d801432db1ea2aed9f0707/fable_library-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7da4971bc03a648d99a64558ac9d3e04b2822612686b7ce53f3f11d9c5a41b90", size = 2021234, upload-time = "2026-05-28T18:51:22.802Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ca/a6f32aa397accf7a2d9ade026d60a080b026357c122f7d9b911c5d90aa2b/fable_library-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c2fff0f730c060f9efef23e2c168a6edb889b06f3a11969e20c13ba0fb75d1f8", size = 1498373, upload-time = "2026-05-28T18:51:36.021Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/fc75965c4a2002efcfcafe967b82003ea579b407eae0b9e5f18e17c10b77/fable_library-5.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:739227603ccc73fdb3b11b2ba903ca15eb76795e47e66b5facc9da53ece2c04a", size = 1722609, upload-time = "2026-05-28T18:50:42.759Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b3/8297bdc4c1011511805ac2a5276d31895e711548d10596c61e6aadb3ce25/fable_library-5.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:042d3fd496d592325fa13b978e15c9293245bc630b71e082e1714b5e5807612a", size = 1636661, upload-time = "2026-05-28T18:50:37.019Z" }, + { url = "https://files.pythonhosted.org/packages/41/19/4108b03da4b1fd447d7f925899442ea3872d7d9ce07af7a68616b3387c57/fable_library-5.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e58febfaf6f4ebc8f8a40a1dd500eac40ab94b8a073e5b66996a23757b3ca168", size = 1746022, upload-time = "2026-05-28T18:49:33.35Z" }, + { url = "https://files.pythonhosted.org/packages/38/a1/6348c12c1d191e965dcd64f6aa65b9250a82d6438298f9c09cf85b3adaf2/fable_library-5.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f72d4f776e4019e66c8275fe38897521c56db629dde05130e3d8836482a8464", size = 1710791, upload-time = "2026-05-28T18:49:44.486Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c2/8cab73c01e530e4656df6ff3e770bba849b8aca61623837a5744388ba36e/fable_library-5.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cd77ab45ceadcf68e18bb45c3765b9e75e3522c36d19f841e60569fcd51ac11", size = 1913574, upload-time = "2026-05-28T18:49:55.655Z" }, + { url = "https://files.pythonhosted.org/packages/34/08/ee044e4d39a5dd636b3ba1d5758bfd92f9cfe8bfcdcee1c35bf629c5eea2/fable_library-5.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49c67ab8c55674e0680c4cecbfce71a02e552ca407e0ed1a80cf6164a6bbf9b3", size = 1850931, upload-time = "2026-05-28T18:50:06.663Z" }, + { url = "https://files.pythonhosted.org/packages/fd/aa/2aafd8b94d8b59e593e305809b9d06c00e3ba1718aec6dba99b8896ee066/fable_library-5.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abb6c08c472a2b9cae4b99f6c711190bbcca93064118d78cae392c2cae95bdd", size = 1757996, upload-time = "2026-05-28T18:50:28.397Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f8/6aa1140f8a7cf285e3899d80ba7b472e7da04dedd3c90ff6b24b9073f22a/fable_library-5.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1670a0081b9b4bbe49f4f10ecd6c044c0df6f82f8b36cbb54d1c43d817d4cb5e", size = 1857172, upload-time = "2026-05-28T18:50:18.22Z" }, + { url = "https://files.pythonhosted.org/packages/80/11/859dda31260317b144bc1abca51568cc1fc86c6c172f9d5cee78fd0bbb63/fable_library-5.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dcb5be21b793322bcd358eadb1867d19ef6b54860d6115296278a79967e8aeb9", size = 1920763, upload-time = "2026-05-28T18:50:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a2/761c7f609ff9538fc39c48d1c2ea7c43b1f44928a806aac34ea73787d23f/fable_library-5.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3582211ab399c5a1422dcc9a04b0759b4281bee77aba1b93779fe76124d845f8", size = 1986168, upload-time = "2026-05-28T18:51:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/f6/41/5883a418408dceb88c62bed1e3d0ba38af10d134801179086fcbeb2aacc1/fable_library-5.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:558acb844da16b4c5f843ea257fe364a0c51bd08ac5c7728918a2c3c30bda233", size = 2008892, upload-time = "2026-05-28T18:51:13.259Z" }, + { url = "https://files.pythonhosted.org/packages/d6/88/618b4e5115cd71f6478ad8596bf24998b2098092d254f1d2eb89a8ad9335/fable_library-5.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c997598e0e44e2031026799bbb7346a3b07a1bce60820229d8fa3b9455d72263", size = 2019660, upload-time = "2026-05-28T18:51:24.27Z" }, + { url = "https://files.pythonhosted.org/packages/ff/58/952d53c9c33eed6be7ba4efe0321c7fedeeeb440f7c9f02eee21f1e78ddd/fable_library-5.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:93d11e83b378e4a1f2a2e5a15e5abb04a8637dc0c8be488a38d6285fdbd35ee8", size = 1497949, upload-time = "2026-05-28T18:51:37.524Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9d/b32a6b82fa7274e3a44d9d3c25e6b60b3e193bd98567e75be415f0b22290/fable_library-5.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e502084d8f959e00a2f131e9fcd3f61a1065812f3f942cc1d5ed5ee783f1bd", size = 1760793, upload-time = "2026-05-28T18:49:34.918Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f2/53ced59f9a9772452283c7e7512a92b01c1c3e643d836a0c6787af604029/fable_library-5.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7fd9010ed6ceb13567f06a35ac45197a3f476877667d908d13a0283773aed0fd", size = 1731682, upload-time = "2026-05-28T18:49:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/94/bb/5b7fee29e7f7c7ce7188df29fe09516207ec5917cca3120093da776a60ab/fable_library-5.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fca3c48e2925a0f878807f472d29d0d939503c06bfc51cb4bf9e4ddfe3e2c7a", size = 1930440, upload-time = "2026-05-28T18:49:57.258Z" }, + { url = "https://files.pythonhosted.org/packages/51/93/d9984c7011f41d944d8332cfbc8e2a691776a1a8ddd8cce5114b2dfd8dfe/fable_library-5.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4a45baaefaa1b5ae447fc8cb1084e0e03da74d77a3332beb9b1ad8ec777d518", size = 1874462, upload-time = "2026-05-28T18:50:08.065Z" }, + { url = "https://files.pythonhosted.org/packages/be/22/02f68b047c36268b211f29b4b90d007eff1437b21d75de22c4c9322d3d91/fable_library-5.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b899c5dbe1f3d697e8416925207e695ece767c39c9b74a00dbb2929b20e5f1ab", size = 1933462, upload-time = "2026-05-28T18:50:51.393Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/d37767bdd28ea30d4c864b4eb8652ec46c86cd8eea2dcff60f3732a6ebd4/fable_library-5.1.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:9deeb7cb1c0ac7b221cd0f50478174f107a8ea19975674161ee3bb7dd689f856", size = 2007580, upload-time = "2026-05-28T18:51:03.054Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c2/2ef1724ca523f067944c0a57290d7b31cfcc85a10764c76d135c04c18e37/fable_library-5.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:dc8cc244b78f69654573f6ea20c00cfdc0f1555ad3c3329d0556660fa2773ee0", size = 2029248, upload-time = "2026-05-28T18:51:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/25/14/79255fd317248292a29ed789248c11a374e0c839cde22db3d56ba26ca353/fable_library-5.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7d8fd0e6fe80094de1e6ce96a62ff5b5ad00f78f8cacd6c16b6649304bab4e67", size = 2038702, upload-time = "2026-05-28T18:51:25.72Z" }, + { url = "https://files.pythonhosted.org/packages/82/73/aae7f2e77091486f88a38fa26506f44bc6ce90cbed903c3ad93ba51f8500/fable_library-5.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:111e2831bd7e7d2cc58cdc12d3c45486512cbeb807118749ae63275ea52223ef", size = 1722758, upload-time = "2026-05-28T18:50:44.328Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e7/480fdab9e2bedf37e58939b362e9dac2e50301989ff29600c6cb08efe5ee/fable_library-5.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0ec49c6dfa3c0dfb9d7688cac92beb138ccfeaca8837655f8ac3fc8e7be10edc", size = 1636037, upload-time = "2026-05-28T18:50:38.395Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/e73528f386a323f1e9beef34434fe771e2b378c025cda13103b203075f0d/fable_library-5.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c452d2482ad28f9fc8c97c5ed2bc47ffabd85c87b40fb5ea49af22071725de9e", size = 1746075, upload-time = "2026-05-28T18:49:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/98/b5/37d700f956325a4782a2b0cf3a0749dbb2955c676180d8af96cc405633bb/fable_library-5.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2fa8228739efc17ae3a5e7829252ae80c3a44cc696d5fee7d09a9bfaf43056e", size = 1710106, upload-time = "2026-05-28T18:49:47.011Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c7/f38ff7579197ad580c08677b11c29a0ea2c566d6f64c6a5cc267eff41faa/fable_library-5.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f64e25f298c85f9d777ae478aace5121d22ff9789fac6f4933c3fec60ecab17", size = 1915406, upload-time = "2026-05-28T18:49:58.534Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ab/b76423937e09f41fcff5096f116baa23ec9ec349c9f6084c3558216c8017/fable_library-5.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:842ca3fd1b8d04386564fbe547713f7bf1db66cc4159469dedd3a1db453a5ebf", size = 1850598, upload-time = "2026-05-28T18:50:09.495Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/373ca04d371a49542cca40e425060cf29d0de7789d32cc03bd850a98038c/fable_library-5.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd91146e762771d9d14bd4cfd8ccf2541a872487ff2e613d7b8d52792e107231", size = 1758102, upload-time = "2026-05-28T18:50:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/f5/35/6516299fe5357674f034e98283c97e886cca7bf46a9f2a12b45d39502b1e/fable_library-5.1.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89ccb55ef768bc7fce6c514c999c35e5c0d4cdc3ef63bf6ee98c48342413619b", size = 1854610, upload-time = "2026-05-28T18:50:19.843Z" }, + { url = "https://files.pythonhosted.org/packages/97/ef/e22cdb6ef00ca2291fc8e945f6d125e41258be1a699aaa761dfbb6f7c70c/fable_library-5.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:42c59cb9c568327290f7481f7f1e5697d90b1d179b70f25a10ab890b27be12dd", size = 1920332, upload-time = "2026-05-28T18:50:52.735Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2b/0aedc0df31fc64b9b331bdf5025bdf32921ebc2c72c9d1694e53c55d1e42/fable_library-5.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8a5fda73f39fff62514030084b93ca49b2f8ed187f5a5e04449298975074f0ff", size = 1986482, upload-time = "2026-05-28T18:51:04.546Z" }, + { url = "https://files.pythonhosted.org/packages/a3/44/66fb39d4df35d65fa0cc5976c31a61662fb15f2cedc7b980c2a70702a8e4/fable_library-5.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:795f943625ce92ee5bb6d3e37710e6740a92bcc31b1ea83179e46528d5405e86", size = 2007798, upload-time = "2026-05-28T18:51:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/68/19/eb1f9fd98baa433b0b5c8ca3ae95b5fbdb6324b6b1155fd46399413010c3/fable_library-5.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7265d3d0fbc5a9c8239b094cd050801af7299fefaae056f0b8bc1f34162a3719", size = 2020126, upload-time = "2026-05-28T18:51:27.262Z" }, + { url = "https://files.pythonhosted.org/packages/41/8e/38840f14d96c85bc2944d1c63491fd862f4c17f1b00b43a0de993975b62f/fable_library-5.1.0-cp314-cp314-win32.whl", hash = "sha256:df85120f94020fb98ce4781866fdfccda04a9472ddfcab0c8196e14be06c31ef", size = 1343795, upload-time = "2026-05-28T18:51:40.326Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a3/7795ab9395df60df808e4a5bd67855890d652a527d6a0883624a70d59272/fable_library-5.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:cb712670710ea74e4c854d248c8427f8659145f28ff75f4c2558c927ea2fd7ff", size = 1499482, upload-time = "2026-05-28T18:51:38.806Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0a/f50c09550cc4f6fed55c41aafa8e3b86e65296c688d9a35a73e4084ce4e2/fable_library-5.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b69a4a7926ee5e1e0e39098b487bc3cf0064ccad4870486607aeacdd22a180b", size = 1758447, upload-time = "2026-05-28T18:49:37.797Z" }, + { url = "https://files.pythonhosted.org/packages/be/66/e1b0d5cb3351ad9e84feef9d9e09050f978aeb90fd31ccc4b327665bfb7b/fable_library-5.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c01ba79da927d0bd4e8e82744d23d55a3f49d131296320534a01b919aa9c728", size = 1730305, upload-time = "2026-05-28T18:49:48.542Z" }, + { url = "https://files.pythonhosted.org/packages/a3/f2/3a03ccdf8884d8ab84c429ff1c91ced5756bfb5a58b5c51a395fdbb1031b/fable_library-5.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15e8c882a87fd1b22706bf7f8631fa96dfc69dff6743a7746cba176fbddeeb6c", size = 1930084, upload-time = "2026-05-28T18:49:59.83Z" }, + { url = "https://files.pythonhosted.org/packages/32/71/b2bc2d77034e37ab43cef914e2a9c8f0740bdb709f6b76d62e9ed3a631fe/fable_library-5.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a47acfbba112a8f9a4ee1e500c0aa480dff8870c7c7a83cd3447eee6c15381f3", size = 1872523, upload-time = "2026-05-28T18:50:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/2f/1b/6fffb5aae6a1c3bf879173e7ac6529c36ebcddc4d780aa9f0e2ed5fbd1ab/fable_library-5.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b3ef8ac594a5c2b576e62f5fbb8ab9a868f49c9fdf9b0a4aeeaa93cf369a78b", size = 1932424, upload-time = "2026-05-28T18:50:54.254Z" }, + { url = "https://files.pythonhosted.org/packages/48/f2/03a37c4d77b06f331fde4aa48a3f9afa503fdf81a67d644b4ed8092a8d63/fable_library-5.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:7fe3bcc7d713db7627890915c0b2feed3620c44033e0969c7f88cb7d31c365fc", size = 2006593, upload-time = "2026-05-28T18:51:05.868Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/3f96d42fe6c2c9dcfb2ac7332dc7edc63f8a5705b3c34206e05bf168b5fb/fable_library-5.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ab5e4ff1c167107147b91d8743e6a52314e08f8ea6461faf62adbe5d79537cbf", size = 2028146, upload-time = "2026-05-28T18:51:17.325Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d6/52dd2d2c23e4b11fc4a6d78bb470b0b0e2683fe982fc836212f829a369cc/fable_library-5.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:94c449469250189a72d0a04f5049d7c1065201542f393084a24689df78fc9ed1", size = 2038626, upload-time = "2026-05-28T18:51:28.627Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5d/5c3fa9eeb4807b9c66422d67ccfb333a4b1b52f308a1c920064d6b20954d/fable_library-5.1.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:250f7230afbc25e339b90fbb9b761eee12056cecf20fe14e1132afbbb965de90", size = 1758568, upload-time = "2026-05-28T18:50:31.247Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d1/21e9ec870f10093cbb3de0c7314e3d0857c337d7339147b16272bf1024c3/fable_library-5.1.0-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ea1cd14828b377b4e1900da8c76381bae3f7cd60f92c9e82aef577c33f84a19", size = 1854747, upload-time = "2026-05-28T18:50:21.32Z" }, +]