Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed code to fix issue #2198 finding the spatialite dll in Windows #2339

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

hcarter333
Copy link

@hcarter333 hcarter333 commented Apr 28, 2024

Code fix addressing #2198 . The pull request modifies datasette /utils/__init__.py by adding code to handle the presence of : characters in Windows\DOS paths. The proposed fix maintains handling of : separated entry points in both Windows and Linux. This is demonstrated by the added pytest test cases. Documentation has been modified to include a line about installing Spatialite on Windows taken from the django documentation.


📚 Documentation preview 📚: https://datasette--2339.org.readthedocs.build/en/2339/

Comment on lines +876 to 885
#:\ indicates we're on a Windows machine study the argument a bit more
if ":\\" in r"%r" % value:
path_entry = value.split(":", 2)
if len(path_entry) < 3:
return value
# argument contains a Windows/DOS path and an entry point
path = path_entry[0] + ":" + path_entry[1]
entrypoint = path_entry[-1]
return path, entrypoint
if ":" not in value:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be something in the Python standard library - perhaps in the os module - that can handle this here.

@simonw simonw mentioned this pull request Aug 15, 2024
3 tasks
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

I'm trying to figure out a good way to test this. It looks like I need spatialite-loadable-modules-5.0.0-win-amd64.7z from http://www.gaia-gis.it/gaia-sins/windows-bin-amd64-latest/ - which I should be able to run in a windows-latest on GitHub Actions.

Here's an example workflow that seems to use a hosted copy of that: https://github.com/tyrylu/feel-the-streets/blob/d951a9fadbfecc8724e91b84e10e8f52747e4bde/.github/workflows/ci.yml#L47

Or this script: https://github.com/AequilibraE/mapmatcher/blob/3407a6b172b2e06d03c319b8a1ffb56a0fc1f9b2/tests/setup_windows_spatialite.py

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Ran into this problem:

Currently, when attempting to enable extension loading, we get an error that such a feature is not available.

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Hah, I posted a workaround on that thread:

Here's a workaround: if you pip install pysqlite3-binary you can do import pysqlite3 as sqlite3 and get a much more recent version of SQLite that allows you to load extensions - thanks to this project: https://github.com/coleifer/pysqlite3

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

That workaround didn't work, not sure if it could EVER have worked since https://pypi.org/project/pysqlite3-binary/#files does not list any wheels for Windows.

Oh - it's because I suggested the workaround in that other thread for Linux, not for Windows.

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Filed an issue here:

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Wow, yeah it turns out Claude wrote me the wrong code for that. My mistake. Moving on.

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Here's what's in that uncompressed archive (partly):

CleanShot 2024-08-15 at 15 41 12@2x

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

CleanShot 2024-08-15 at 15 42 52@2x

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Still getting this error:

CleanShot 2024-08-15 at 15 45 58@2x

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Maybe I'm using spatialite-loadable-modules-5.0.0-win-amd64.7z but I should be using a x86 one instead like http://www.gaia-gis.it/gaia-sins/windows-bin-x86/mod_spatialite-5.1.0-win-x86.7z

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

That failed too, same error.

Looks like the machine is running this:

CleanShot 2024-08-15 at 15 56 31@2x

Hard to tell if that's AMD64 or x86 - so I downloaded https://github.com/actions/runner-images/releases/download/win22%2F20240811.1/sbom.windows-2022.json.zip to see if that had any clues.

Unzipped that's a 345M JSON file! And I couldn't see clues in that either.

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Proved to myself that loading extensions on Windows CAN work, using sqlite-vec which can pip install the correct binary:

name: sqlite-vec on Windows

on:
  push:
    branches: [ main ]

jobs:
  setup:
    runs-on: windows-latest
    steps:
    - name: Set up Python 3.12
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'
    - name: Install sqlite-vec
      run: |
        pip install sqlite-vec
    - name: Verify installation
      run: |
        python -c @"
        import sqlite3, os
        import sqlite_vec
        db = sqlite3.Connection(':memory:')
        db.enable_load_extension(True)
        db.load_extension(sqlite_vec.loadable_path())
        print(db.execute('select vec_version()').fetchall())
        "@

Output:

[('v0.1.1',)]

simonw referenced this pull request in simonw/playing-with-actions-single Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

https://github.com/mxschmitt/action-tmate supports Windows, I'm going to try that next.

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

OK, this worked in a SSH session to that windows-latest machine:

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single
# wget 'http://www.gaia-gis.it/gaia-sins/windows-bin-amd64/mod_spatialite-5.1.0-win-amd64.7z'

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single
# 7z.exe x mod_spatialite-5.1.0-win-amd64.7z 

7-Zip 24.07 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-06-19

Scanning the drive for archives:
1 file, 10516044 bytes (11 MiB)

Extracting archive: mod_spatialite-5.1.0-win-amd64.7z
--
Path = mod_spatialite-5.1.0-win-amd64.7z
Type = 7z
Physical Size = 10516044
Headers Size = 846
Method = LZMA2:24 BCJ
Solid = +
Blocks = 2

Everything is Ok

Folders: 1
Files: 28
Size:       46358154
Compressed: 10516044

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single
# ls
mod_spatialite-5.1.0-win-amd64  mod_spatialite-5.1.0-win-amd64.7z

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single
# cd mod_spatialite-5.1.0-win-amd64

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single/mod_spatialite-5.1.0-win-amd64
# ls
libcrypto-3-x64.dll  libgcc_s_seh-1.dll  libjpeg-62.dll    libreadline8.dll   libssl-3-x64.dll  libwebp-7.dll        mod_spatialite.dll
libcurl-4.dll        libgeos.dll         liblzma-5.dll     librttopo-1.dll    libstdc++-6.dll   libwinpthread-1.dll  proj.db
libexpat-1.dll       libgeos_c.dll       libminizip-1.dll  libsharpyuv-0.dll  libtermcap-0.dll  libxml2.dll          sqlite3.exe
libfreexl-1.dll      libiconv-2.dll      libproj_9_2.dll   libsqlite3-0.dll   libtiff-6.dll     libzstd.dll          zlib1.dll

runneradmin@fv-az972-554 MINGW64 /d/a/playing-with-actions-single/playing-with-actions-single/mod_spatialite-5.1.0-win-amd64
# python
Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sqlite3
>>> db = sqlite3.Connection(":memory:")
>>> db.enable_load_extension(True)
>>> db.load_extension("mod_spatialite.dll")
>>> db.execute('select spatialite_version()').fetchall()
[('5.1.0',)]

simonw added a commit to simonw/playing-with-actions-single that referenced this pull request Aug 15, 2024
@simonw
Copy link
Owner

simonw commented Aug 15, 2024

That worked! I have successfully run SpatiaLite on Windows on GitHub Actions.

Testing it like this:

datasette --load-extension mod_spatialite.dll --get /-/versions.json | jq

Returned output that included this:

    "extensions": {
      "json1": null,
      "spatialite": {
        "spatialite_version": "5.1.0",
        "spatialite_target_cpu": "x86_64-w64-mingw32",
        "check_strict_sql_quoting": 0,

@hcarter333
Copy link
Author

This is a lot of very awesome work we'll use going forward testing with Windows on our plugins! Thank you!

Just in case this helps, there are test cases for the proposed Python fix that I believe show it doesn't break the path:

https://github.com/hcarter333/datasette/blob/main/tests/test_loadextension.py

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

When I SSH into the window-latest runner the only way I can get SpatiaLite is to work is like this:

PS D:\a\playing-with-actions-single\playing-with-actions-single> cd .\mod_spatialite-5.1.0-win-amd64\
PS D:\a\playing-with-actions-single\playing-with-actions-single\mod_spatialite-5.1.0-win-amd64> sqlite-utils memory --load-extension mod_spatialite.dll 'select spatialite_version()'
[{"spatialite_version()": "5.1.0"}]

The trick is to run sqlite-utils or datasette in the same directory as both mod_spatialite.dll and all of this stuff:

-a----         7/14/2023   7:14 AM        6086563 libcrypto-3-x64.dll
-a----         7/14/2023   7:47 AM         611759 libcurl-4.dll
-a----         7/14/2023   9:53 AM         732480 libexpat-1.dll
-a----         7/25/2023   8:43 AM          89328 libfreexl-1.dll
-a----         6/22/2023   9:04 PM         117427 libgcc_s_seh-1.dll
-a----         7/14/2023   9:21 AM        2705920 libgeos.dll
-a----         7/14/2023   9:21 AM         268800 libgeos_c.dll
-a----         7/14/2023   4:32 AM        1570911 libiconv-2.dll                                                                      
-a----         7/14/2023   4:52 AM        1061376 libjpeg-62.dll
-a----         7/14/2023   5:17 AM         180404 liblzma-5.dll
-a----         7/14/2023  10:02 AM          50343 libminizip-1.dll
-a----         7/14/2023   8:41 AM        3970048 libproj_9_2.dll
-a----         1/10/2023   9:10 AM         559599 libreadline8.dll
-a----         7/14/2023  11:00 AM         468676 librttopo-1.dll
-a----         7/14/2023   6:16 AM          29324 libsharpyuv-0.dll
-a----         7/14/2023   8:29 AM        1306245 libsqlite3-0.dll                                                                    
-a----         7/14/2023   7:14 AM         979451 libssl-3-x64.dll
-a----         6/22/2023   9:04 PM        2264558 libstdc++-6.dll
-a----          8/4/2020   8:15 AM          45347 libtermcap-0.dll
-a----         7/14/2023   5:58 AM         555296 libtiff-6.dll
-a----         7/14/2023   6:16 AM         452454 libwebp-7.dll
-a----         5/29/2023  10:09 AM          60356 libwinpthread-1.dll
-a----         7/14/2023  10:52 AM        1658880 libxml2.dll
-a----         7/14/2023   5:25 AM        1181184 libzstd.dll
-a----          8/5/2023   7:02 AM        9044865 mod_spatialite.dll                                                                  
-a----         7/14/2023   8:42 AM        8544256 proj.db
-a----         7/14/2023   8:29 AM        1623552 sqlite3.exe
-a----         7/14/2023   4:40 AM         138752 zlib1.dll

If I try to run it from anywhere else using a path to mod_spatialite.dll I get the sqlite3.OperationalError: The specified module could not be found error.

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

The problem I'm having here is that this fix is meant to allow you to run something like this:

python -m datasette counties.db --load-extension=C:\Windows\System32\mod_spatialite.dll

But I can't figure out how to have --load-extension work with a path to mod_spatialite.dll - I can only get it to work for --load-extension mod_spatialite.dll in the same folder as where I am running the command.

(I'm not at all Windows literate so it's very likely I'm missing something basic here.)

@simonw
Copy link
Owner

simonw commented Aug 15, 2024

Maybe that's because C:\Windows\System32 is on the path already?

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

With Claude's help this worked:

set PATH=%PATH%;D:\a\playing-with-actions-single\playing-with-actions-single\mod_spatialite-5.1.0-win-amd64
sqlite-utils memory --load-extension mod_spatialite.dll 'select spatialite_version()'
[{"spatialite_version()": "5.1.0"}]

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Am I right in thinking that mod_spatialite.dll can only be successfully loaded on Windows if the other DLLs it needs, such as libgeos.dll, are already located on somewhere that is on the Windows PATH?

If so, does datasette --load-extension mod_spatialite.dll work without needing a path at all provided that extension is ALSO in a folder that is on the Windows path, such as c:\Windows\System32?

If that's the case then we should document it - tell people on Windows that they can either install SpatiaLite plus its dependencies in a folder-on-the-path, OR they can cd into the folder that all of those files are in first and then SpatiaLite should load.

Or we could show people how to temporarily set their PATH while running Datasette, which I haven't quite 100% confidently figured out how to do yet.

@hcarter333
Copy link
Author

We added the standard Windows location to SPATIALITE_PATHS

#2198 (comment)

as in

SPATIALITE_PATHS = (
    "/usr/lib/x86_64-linux-gnu/mod_spatialite.so",
    "/usr/local/lib/mod_spatialite.dylib",
    "/usr/local/lib/mod_spatialite.so",
    "/opt/homebrew/lib/mod_spatialite.dylib",
    "C:\Windows\System32\mod_spatialite.dll",
)

@hcarter333
Copy link
Author

hcarter333 commented Aug 16, 2024

Here it is starting up with the run command at the bottom of https://github.com/hcarter333/rm-rbn-history/blob/main/docs/tool_suite.md

spatialitewin.mp4

@hcarter333
Copy link
Author

I just removed
C:\Program Files\spatialite
from both of my PATH variables (user and system) and

nopath.mp4

followed by

afternopath.mp4

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Do you know if it’s possible to use SpatiaLite on Windows with this Datasette branch if the mod_spatialite.dll stays in another folder that isn’t on your Windows PATH?

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

I’m looking to add definitive documentation describing how to run SpatiaLite with Datasette on Windows, so I’m trying to understand if you need to copy a bunch of files into C:\Windows\System32 or if there’s some other installation methods you need to follow.

@hcarter333
Copy link
Author

hcarter333 commented Aug 16, 2024

I believe it will work. the videos shown above are with the spatialite directory removed from my path. The only file that's in C:\Windows\System32 is mod_spatialite.dll.

I'm going to do the following experiment:

  1. remove mod_spatialite.dll from C:\Windows\System32
  2. relocate it somewhere in a folder not listed in the PATH variable
  3. start datasette pointing to that path

so far, so good

dirpathshort.mp4

Checking for actual querying now:

worksdirpathquery.mp4

Works well!

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Where did you get your SpatiaLite DLL from? Could it be different from the one I’ve been using, and hence not need additional DLLs to work?

@hcarter333
Copy link
Author

With the System32 path added in the pull request, the user doesn't need to add the path on the command line.

If the user would like to place the file in a different location, they can and then define the path to the file on the command line.

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

After an extensive conversation with Claude my current mental model is this:

The version of SpatiaLite I have been using comes as an archive containing a bunch of different files - mod_spatialite.dll plus a whole lot of supporting additional DLLs.

To successfully run the SpatiaLite extension, Windows needs to be able to find those other DLLs too.

To find those, they need to be on the DLL search path - which by default includes the current directory in which the program is running, plus C:\Windows\System32, plus a bunch of other places.

if those extra DLLs made it into C:\Windows\System32 (maybe put there by an installer?) everything will work fine.

As of Python 3.8 there is a os.add_dll_directoory() method that could be used here too, but Datasette would need to grow a new feature, maybe a —add-dll-directory CLI option that only works on Windows: https://docs.python.org/3/library/os.html#os.add_dll_directory

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

With the System32 path added in the pull request, the user doesn't need to add the path on the command line.

Does that only works if both mod_spatialite.dll and a bunch of those other DLLs (libgeos_c.dll and libcurl-4.dll and so on - see #2339 (comment)) have been copied into C:\Windows\System32 - is there a SpatiaLite installer that does that?

What happens if that installer overwrites an existing slightly incompatible libcurl-4.dll that was put there by some other program? Will it break things?

@hcarter333
Copy link
Author

I had forgotten about all the other files. What I would suggest, but am not currently doing, is to place the spatialite install files in their own directory. I was just reading through my TIL as I was installing all of this I found:

===================================================================

This is a note for myself for later when I try to construct a better set of instructions for adding Spatialite to Windows for use with Datasette.

You can instruct Windows to look for DLLs in a new directory simply by adding that directory to your PATH envrionment variable

set PATH=C:\path to your dll;%PATH%

via.

============end of TIL====================

However, at some point I moved the files into my System32 directory.

So, it seems like placing the dlls in a separate directory of their own and then adding that directory via $PATH will work.

Would it be possible to try that using the setup from #2339 ?

@hcarter333
Copy link
Author

Sorry, I'm a little discombobulated. I got my install files from

https://www.gaia-gis.it/gaia-sins/windows-bin-x86/

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

image

Which of those two files did you use? Did you have to unzip the files and then manually copy them into the C:\Windows\System32 directory?

(I’m completely new to installing this kind of thing on Windows, so I’m asking a ton of questions to make sure I fully understand the process for the future.)

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Another extremely basic question: does Windows come with the ability to decompress those .7z files or are Windows users expected to install an additional program for that?

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

If I’m right in that SpatiaLite only works on Windows if you ensure that all of the files from that http://www.gaia-gis.it/gaia-sins/windows-bin-amd64/mod_spatialite-5.1.0-win-amd64.7z file have been somehow added to the Windows system path, then maybe Datasette itself needs to learn that if the user passes --load-extension=spatialite and the platform is Windows then it should attempt to call db.load_extension("mod_spatialite.dll") while ignoring the SPATIALITE_PATHS list entirely - since on Windows what matters is finding SpatiaLite and its dependencies, which can only be done (at the moment) if those dependencies are on the PATH.

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Running an informal survey on Mastodon to see if there are any other ways people install SpatiaLite on Windows that are worth knowing about for the docs: https://fedi.simonwillison.net/@simon/112969302245132317

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

I created https://github.com/simonw/spatialite-windows-binaries to provide stable URLs for SpatiaLite Windows binaries so I can download them from GitHub Actions workflows without worrying that the URLs on the official site might change in the future.

@simonw
Copy link
Owner

simonw commented Aug 16, 2024

Now when I SSH into a Windows GitHub worker (via https://github.com/simonw/playing-with-actions-single/blob/main/.github/workflows/windows-tmate.yml) I can run this:

wget https://raw.githubusercontent.com/simonw/spatialite-windows-binaries/main/windows-bin-amd64/mod_spatialite-5.1.0-win-amd64.7z
7z.exe x mod_spatialite-5.1.0-win-amd64.7z

git clone https://github.com/simonw/datasette
cd datasette
pip install -e .

Unfortunately I can still only run SpatiaLite using this:

cd mod_spatialite-5.1.0-win-amd64
datasette --load-extension mod_spatialite.dll

I cannot figure out how to run it NOT from inside that directory. All of my attempts at adding that directory to the Windows PATH have failed:

powershell
$env:PATH += ";$PWD\mod_spatialite-5.1.0-win-amd64"
cd
datasette --load-extension mod_spatialite.dll

That returns an error, but I was hoping it would pick up the .dll since it is now on the path.

I've also failed to get that to work using raw Python:

import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("mod_spatialite.dll")

This works when I'm in the D:\a\playing-with-actions-single\playing-with-actions-single\mod_spatialite-5.1.0-win-amd64 directory and fails otherwise.

My goal here is to demonstrate to myself that you can run datasette --load-extension mod_spatialite.dll and have it work provided you have added the folder with all of those weird DLLs to your Windows PATH in some way. But... I'm stuck doing this over SSH to a tmate Windows machine because I don't have a GUI Windows to test on.

@hcarter333
Copy link
Author

hcarter333 commented Aug 16, 2024

I am using the mod_spatialite 7z file, not the tools 7z file.

It was July of 2023 when I first installed all this, so I think, but don't know, that I did manually unzip the file and copy the contents into my System32 folder. Except: I noticed that there's a sqlite3.exe file in the archive and that is definitely not in my System32 folder.

Ha :) You may have come up with a reason for me to finally try Mastodon. :)

If anything cool comes up, I'd love to know more because I've also been coming up to speed on adding dlls to Windows for the last year or so.

Windows 11 will unzip .7z files using Windows Explorer.

Just to throw out an idea, what if Datasette used py7zr to unzip the spatialite file in place, and then re-zipped the files when Datasette turned off? It would be a bit messy in the interim, but it sounds like it would fit the 'current directory for dlls' search option. We've been looking at a similar option of using pytar to unzip satellite data, (~4000 files per day of interest), into a directory for our satellite mapping Datasette app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants