Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
provision.py199 linesDownload Raw Back to sqlite
1# dialects/sqlite/provision.py
2# Copyright (C) 2005-2024 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9import os
10import re
11
12from ... import exc
13from ...engine import url as sa_url
14from ...testing.provision import create_db
15from ...testing.provision import drop_db
16from ...testing.provision import follower_url_from_main
17from ...testing.provision import generate_driver_url
18from ...testing.provision import log
19from ...testing.provision import post_configure_engine
20from ...testing.provision import run_reap_dbs
21from ...testing.provision import stop_test_class_outside_fixtures
22from ...testing.provision import temp_table_keyword_args
23from ...testing.provision import upsert
24
25
26# TODO: I can't get this to build dynamically with pytest-xdist procs
27_drivernames = {
28    "pysqlite",
29    "aiosqlite",
30    "pysqlcipher",
31    "pysqlite_numeric",
32    "pysqlite_dollar",
33}
34
35
36def _format_url(url, driver, ident):
37    """given a sqlite url + desired driver + ident, make a canonical
38    URL out of it
39
40    """
41    url = sa_url.make_url(url)
42
43    if driver is None:
44        driver = url.get_driver_name()
45
46    filename = url.database
47
48    needs_enc = driver == "pysqlcipher"
49    name_token = None
50
51    if filename and filename != ":memory:":
52        assert "test_schema" not in filename
53        tokens = re.split(r"[_\.]", filename)
54
55        new_filename = f"{driver}"
56
57        for token in tokens:
58            if token in _drivernames:
59                if driver is None:
60                    driver = token
61                continue
62            elif token in ("db", "enc"):
63                continue
64            elif name_token is None:
65                name_token = token.strip("_")
66
67        assert name_token, f"sqlite filename has no name token: {url.database}"
68
69        new_filename = f"{name_token}_{driver}"
70        if ident:
71            new_filename += f"_{ident}"
72        new_filename += ".db"
73        if needs_enc:
74            new_filename += ".enc"
75        url = url.set(database=new_filename)
76
77    if needs_enc:
78        url = url.set(password="test")
79
80    url = url.set(drivername="sqlite+%s" % (driver,))
81
82    return url
83
84
85@generate_driver_url.for_db("sqlite")
86def generate_driver_url(url, driver, query_str):
87    url = _format_url(url, driver, None)
88
89    try:
90        url.get_dialect()
91    except exc.NoSuchModuleError:
92        return None
93    else:
94        return url
95
96
97@follower_url_from_main.for_db("sqlite")
98def _sqlite_follower_url_from_main(url, ident):
99    return _format_url(url, None, ident)
100
101
102@post_configure_engine.for_db("sqlite")
103def _sqlite_post_configure_engine(url, engine, follower_ident):
104    from sqlalchemy import event
105
106    if follower_ident:
107        attach_path = f"{follower_ident}_{engine.driver}_test_schema.db"
108    else:
109        attach_path = f"{engine.driver}_test_schema.db"
110
111    @event.listens_for(engine, "connect")
112    def connect(dbapi_connection, connection_record):
113        # use file DBs in all cases, memory acts kind of strangely
114        # as an attached
115
116        # NOTE!  this has to be done *per connection*.  New sqlite connection,
117        # as we get with say, QueuePool, the attaches are gone.
118        # so schemes to delete those attached files have to be done at the
119        # filesystem level and not rely upon what attachments are in a
120        # particular SQLite connection
121        dbapi_connection.execute(
122            f'ATTACH DATABASE "{attach_path}" AS test_schema'
123        )
124
125    @event.listens_for(engine, "engine_disposed")
126    def dispose(engine):
127        """most databases should be dropped using
128        stop_test_class_outside_fixtures
129
130        however a few tests like AttachedDBTest might not get triggered on
131        that main hook
132
133        """
134
135        if os.path.exists(attach_path):
136            os.remove(attach_path)
137
138        filename = engine.url.database
139
140        if filename and filename != ":memory:" and os.path.exists(filename):
141            os.remove(filename)
142
143
144@create_db.for_db("sqlite")
145def _sqlite_create_db(cfg, eng, ident):
146    pass
147
148
149@drop_db.for_db("sqlite")
150def _sqlite_drop_db(cfg, eng, ident):
151    _drop_dbs_w_ident(eng.url.database, eng.driver, ident)
152
153
154def _drop_dbs_w_ident(databasename, driver, ident):
155    for path in os.listdir("."):
156        fname, ext = os.path.split(path)
157        if ident in fname and ext in [".db", ".db.enc"]:
158            log.info("deleting SQLite database file: %s", path)
159            os.remove(path)
160
161
162@stop_test_class_outside_fixtures.for_db("sqlite")
163def stop_test_class_outside_fixtures(config, db, cls):
164    db.dispose()
165
166
167@temp_table_keyword_args.for_db("sqlite")
168def _sqlite_temp_table_keyword_args(cfg, eng):
169    return {"prefixes": ["TEMPORARY"]}
170
171
172@run_reap_dbs.for_db("sqlite")
173def _reap_sqlite_dbs(url, idents):
174    log.info("db reaper connecting to %r", url)
175    log.info("identifiers in file: %s", ", ".join(idents))
176    url = sa_url.make_url(url)
177    for ident in idents:
178        for drivername in _drivernames:
179            _drop_dbs_w_ident(url.database, drivername, ident)
180
181
182@upsert.for_db("sqlite")
183def _upsert(
184    cfg, table, returning, *, set_lambda=None, sort_by_parameter_order=False
185):
186    from sqlalchemy.dialects.sqlite import insert
187
188    stmt = insert(table)
189
190    if set_lambda:
191        stmt = stmt.on_conflict_do_update(set_=set_lambda(stmt.excluded))
192    else:
193        stmt = stmt.on_conflict_do_nothing()
194
195    stmt = stmt.returning(
196        *returning, sort_by_parameter_order=sort_by_parameter_order
197    )
198    return stmt
199 
codekingpro/portable-devtools · Team Ai