thds.core 1.45.20251007062722__py3-none-any.whl → 1.46.20251008224105__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of thds.core might be problematic. Click here for more details.

@@ -1,4 +1,5 @@
1
1
  import os
2
+ import stat
2
3
  import sys
3
4
  import typing as ty
4
5
  from functools import partial
@@ -43,20 +44,21 @@ def from_file(
43
44
  responsibility to ensure that your file has been uploaded to the URI you provide.
44
45
  """
45
46
  path = path_from_uri(filename) if isinstance(filename, str) else Path(filename)
46
- if not path.exists():
47
- raise FileNotFoundError(path)
47
+ stats = path.stat() # raises FileNotFoundError if file doesn't exist
48
+ if stat.S_ISDIR(stats.st_mode):
49
+ raise IsADirectoryError(path)
48
50
 
49
51
  file_hash = hash or hash_file(path) # use automatic hash algo if not specified!
50
52
  if uri:
51
- src = from_uri(uri, file_hash)
53
+ src = from_uri(uri, file_hash, stats.st_size)
52
54
  else:
53
- src = Source(to_uri(path), file_hash)
55
+ src = Source(to_uri(path), file_hash, stats.st_size)
54
56
  src._set_cached_path(path) # internally, it's okay to hack around immutability.
55
57
  return src
56
58
 
57
59
 
58
60
  class FromUri(ty.Protocol):
59
- def __call__(self, hash: ty.Optional[Hash]) -> Source:
61
+ def __call__(self, hash: ty.Optional[Hash], size: int = 0) -> Source:
60
62
  """Closure over a URI that creates a Source from a URI.
61
63
 
62
64
  The Hash may be used to short-circuit creation that would result in creating
@@ -84,13 +86,20 @@ def register_from_uri_handler(key: str, handler: FromUriHandler):
84
86
  _FROM_URI_HANDLERS[key] = handler
85
87
 
86
88
 
89
+ def _from_local_uri(uri: str, hash: ty.Optional[Hash], size: int = 0) -> Source:
90
+ """fulfill the FromUri interface"""
91
+ return from_file(path_from_uri(uri), hash)
92
+
93
+
94
+ def _local_file_uri_handler(uri: str) -> ty.Optional[FromUri]:
95
+ return partial(_from_local_uri, uri) if is_file_uri(uri) else None
96
+
97
+
87
98
  _FROM_URI_HANDLERS: ty.Dict[str, FromUriHandler] = dict()
88
- register_from_uri_handler(
89
- "local_file", lambda uri: partial(from_file, path_from_uri(uri)) if is_file_uri(uri) else None
90
- )
99
+ register_from_uri_handler("local_file", _local_file_uri_handler)
91
100
 
92
101
 
93
- def from_uri(uri: str, hash: ty.Optional[Hash] = None) -> Source:
102
+ def from_uri(uri: str, hash: ty.Optional[Hash] = None, size: int = 0) -> Source:
94
103
  """Create a read-only Source from a URI. The data should already exist at this remote
95
104
  URI, although Source itself can make no guarantee that it always represents real data
96
105
  - only that any data it does represent is read-only.
@@ -101,5 +110,5 @@ def from_uri(uri: str, hash: ty.Optional[Hash] = None) -> Source:
101
110
  """
102
111
  for handler in _FROM_URI_HANDLERS.values():
103
112
  if from_uri_ := handler(uri):
104
- return from_uri_(hash)
105
- return Source(uri=uri, hash=hash)
113
+ return from_uri_(hash, size)
114
+ return Source(uri=uri, hash=hash, size=size)
thds/core/source/serde.py CHANGED
@@ -36,6 +36,7 @@ def from_json(json_source: str, hash_parsers: ty.Collection[HashParser] = base_p
36
36
  return _construct.from_uri(
37
37
  uri=d["uri"],
38
38
  hash=next(filter(None, (p(d) for p in hash_parsers)), None),
39
+ size=d.get("size") or 0,
39
40
  )
40
41
 
41
42
 
@@ -57,7 +58,7 @@ def to_json(
57
58
  hash_dict = (
58
59
  next(filter(None, (ser(source.hash) for ser in hash_serializers if source.hash)), None)
59
60
  ) or dict()
60
- return json.dumps(dict(uri=source.uri, **hash_dict))
61
+ return json.dumps(dict(uri=source.uri, size=source.size, **hash_dict))
61
62
 
62
63
 
63
64
  def from_unknown_user_path(path: types.StrOrPath, desired_uri: str) -> Source:
thds/core/source/src.py CHANGED
@@ -45,6 +45,8 @@ class Source(os.PathLike):
45
45
  hash: ty.Optional[hashing.Hash] = None
46
46
  # hash and equality are based only on the _identity_ of the object,
47
47
  # not on the other properties that provide some caching functionality.
48
+ size: int = 0
49
+ # in bytes
48
50
 
49
51
  @property
50
52
  def cached_path(self) -> ty.Optional[Path]:
@@ -95,7 +97,7 @@ class Source(os.PathLike):
95
97
  return from_file(filename, hash, uri)
96
98
 
97
99
  @staticmethod
98
- def from_uri(uri: str, hash: ty.Optional[hashing.Hash] = None) -> "Source":
100
+ def from_uri(uri: str, hash: ty.Optional[hashing.Hash] = None, size: int = 0) -> "Source":
99
101
  from ._construct import from_uri
100
102
 
101
- return from_uri(uri, hash)
103
+ return from_uri(uri, hash, size)
@@ -1,4 +1,4 @@
1
- from . import connect, copy, ddl, functions, index, read, sqlmap, upsert # noqa: F401
1
+ from . import connect, copy, ddl, functions, index, read, sqlmap, types, upsert # noqa: F401
2
2
  from .merge import merge_databases # noqa: F401
3
3
  from .meta import ( # noqa: F401
4
4
  debug_errors,
thds/core/sqlite/meta.py CHANGED
@@ -37,11 +37,50 @@ def list_tables(connectable: Connectable, schema_name: str = "") -> ty.List[str]
37
37
 
38
38
 
39
39
  @autoconn_scope.bound
40
+ def get_sql_defs_by_name(
41
+ connectable: Connectable, types: ty.Collection[str], *, schema_name: str = "main"
42
+ ) -> ty.Dict[str, str]:
43
+ conn = autoconnect(connectable)
44
+ return {
45
+ row[0]: row[1]
46
+ for row in conn.execute(
47
+ f"""
48
+ SELECT name, sql
49
+ FROM {fullname('sqlite_master', schema_name)}
50
+ WHERE type IN ({', '.join('?' for _ in types)})
51
+ AND sql is not null
52
+ """,
53
+ tuple(types),
54
+ )
55
+ }
56
+
57
+
40
58
  def get_tables(connectable: Connectable, *, schema_name: str = "main") -> ty.Dict[str, str]:
41
59
  """Keys of the returned dict are the names of tables in the database.
42
60
 
43
61
  Values of the returned dict are the raw SQL that can be used to recreate the table.
44
62
  """
63
+ return get_sql_defs_by_name(connectable, ["table"], schema_name=schema_name)
64
+
65
+
66
+ def get_views(connectable: Connectable, *, schema_name: str = "main") -> ty.Dict[str, str]:
67
+ """Keys of the returned dict are the names of views in the database.
68
+
69
+ Values of the returned dict are the raw SQL that can be used to recreate the view.
70
+ """
71
+ return get_sql_defs_by_name(connectable, ["view"], schema_name=schema_name)
72
+
73
+
74
+ def get_triggers(connectable: Connectable, *, schema_name: str = "main") -> dict[str, str]:
75
+ """Keys of the returned dict are the names of triggers in the database.
76
+ Values of the returned dict are the raw SQL that can be used to recreate the trigger.
77
+ """
78
+ return get_sql_defs_by_name(connectable, ["trigger"], schema_name=schema_name)
79
+
80
+
81
+ @autoconn_scope.bound
82
+ def get_virtual_tables(connectable: Connectable, *, schema_name: str = "main") -> dict[str, str]:
83
+ """Get virtual tables (FTS, etc.)."""
45
84
  conn = autoconnect(connectable)
46
85
  return {
47
86
  row[0]: row[1]
@@ -49,8 +88,7 @@ def get_tables(connectable: Connectable, *, schema_name: str = "main") -> ty.Dic
49
88
  f"""
50
89
  SELECT name, sql
51
90
  FROM {fullname('sqlite_master', schema_name)}
52
- WHERE type = 'table'
53
- AND sql is not null
91
+ WHERE type='table' AND sql LIKE 'CREATE VIRTUAL TABLE%'
54
92
  """
55
93
  )
56
94
  }
thds/core/sqlite/types.py CHANGED
@@ -59,4 +59,4 @@ def maybe_t(
59
59
  return None
60
60
 
61
61
 
62
- Connectable = ty.Union[os.PathLike, sqlite3.Connection]
62
+ Connectable = ty.Union[str, os.PathLike, sqlite3.Connection]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thds.core
3
- Version: 1.45.20251007062722
3
+ Version: 1.46.20251008224105
4
4
  Summary: Core utilities.
5
5
  Author-email: Trilliant Health <info@trillianthealth.com>
6
6
  License: MIT
@@ -53,13 +53,13 @@ thds/core/log/kw_formatter.py,sha256=9-MVOd2r5NEkYNne9qWyFMeR5lac3w7mjHXsDa681i0
53
53
  thds/core/log/kw_logger.py,sha256=CyZVPnkUMtrUL2Lyk261AIEPmoP-buf_suFAhQlU1io,4063
54
54
  thds/core/log/logfmt.py,sha256=i66zoG2oERnE1P_0TVXdlfJ1YgUmvtMjqRtdV5u2SvU,10366
55
55
  thds/core/source/__init__.py,sha256=e-cRoLl1HKY3YrDjpV5p_i7zvr1L4q51-t1ISTxdig4,543
56
- thds/core/source/_construct.py,sha256=lt6OUOz_s9VBZMZJHXVpfIjmuTH7w1PBhpre0dlW1Zw,3914
56
+ thds/core/source/_construct.py,sha256=jtsh0Du67TslWjCLASZ3pAMeaiowfgm7Bt50zIhwx7k,4330
57
57
  thds/core/source/_construct_tree.py,sha256=5Zk3a5a0uVxklWw6q4JOvI_bErqwlBngUz4TyEAWn1g,616
58
58
  thds/core/source/_download.py,sha256=faKWxgzw1fTqOoyjtgi4IyhiZpBYTm_GZxwqC6LTiXU,2764
59
- thds/core/source/serde.py,sha256=zEAR24AewgDqqkIxcPpS7NZ-ZbEnQPK_A7siWlE3Q0E,3091
60
- thds/core/source/src.py,sha256=nTUBgsAES0J73enIEbc5BitgnxA5kBnf88oYZoMQGnM,4596
59
+ thds/core/source/serde.py,sha256=fCcsqER21uI41og24pDDVsksTYFIgOiz_2v3MJPpb2Y,3142
60
+ thds/core/source/src.py,sha256=Pbe8W_M1oaWwC1UKX3JtB25h3hzSNU9aCiVSkNz32Pk,4650
61
61
  thds/core/source/tree.py,sha256=iNCoCE655MwXQwc2Y0IIm1HMVk5Inj0NGVU9U8Wl_90,4317
62
- thds/core/sqlite/__init__.py,sha256=tDMzuO76qTtckJHldPQ6nPZ6kcvhhoJrVuuW42JtaSQ,606
62
+ thds/core/sqlite/__init__.py,sha256=xJbwCoIeiOvsZDc2PlkJukRKmv3fIgSny68O6lulGPw,613
63
63
  thds/core/sqlite/connect.py,sha256=l4QaSAI8RjP7Qh2FjmJ3EwRgfGf65Z3-LjtC9ocHM_U,977
64
64
  thds/core/sqlite/copy.py,sha256=y3IRQTBrWDfKuVIfW7fYuEgwRCRKHjN0rxVFkIb9VrQ,1155
65
65
  thds/core/sqlite/ddl.py,sha256=k9BvmDzb0rrlhmEpXkB6ESaZAUWtbL58x-70sPyoFk4,201
@@ -67,15 +67,15 @@ thds/core/sqlite/functions.py,sha256=AOIRzb7lNxmFm1J5JS6R8Nl-dSv3Dy47UNZVVjl1rvk
67
67
  thds/core/sqlite/index.py,sha256=Vc7qxPqQ69A6GO5gmVQf5e3y8f8IqOTHgyEDoVZxTFM,903
68
68
  thds/core/sqlite/insert_utils.py,sha256=BNI3VUdqwBdaqa0xqiJrhE6XyzPsTF8N4KKKdb4Vfes,884
69
69
  thds/core/sqlite/merge.py,sha256=NxettDMJ_mcrWfteQn_ERY7MUB5ETR-yJLKg7uvF6zA,3779
70
- thds/core/sqlite/meta.py,sha256=4P65PAmCjagHYO1Z6nWM-wkjEWv3hxw5qVa4cIpcH_8,5859
70
+ thds/core/sqlite/meta.py,sha256=8Gh4FhTzU86FK8oWosoyPfT0EVd-kfieThEQBrD-l30,7299
71
71
  thds/core/sqlite/read.py,sha256=5pWvrbed3XNWgSy-79-8ONWkkt4jWbTzFNW6SnOrdYQ,2576
72
72
  thds/core/sqlite/sqlmap.py,sha256=LeyiJtY0ww_mbeSp7LQM-YuWnckulQqropxwAfkt5To,6818
73
73
  thds/core/sqlite/structured.py,sha256=8t1B6XbM5NnudKEeBLsdjRVbSXXSr6iHOW0HwEAqtXU,4818
74
- thds/core/sqlite/types.py,sha256=Zb1FtSAmC1L7QPPCV1eRnWpJuF74t9R293ai1SHtdS4,1292
74
+ thds/core/sqlite/types.py,sha256=oq8m0UrvSn1IqWWcQ4FPptfAhdj6DllnCe7puVqSHlY,1297
75
75
  thds/core/sqlite/upsert.py,sha256=BmKK6fsGVedt43iY-Lp7dnAu8aJ1e9CYlPVEQR2pMj4,5827
76
76
  thds/core/sqlite/write.py,sha256=z0219vDkQDCnsV0WLvsj94keItr7H4j7Y_evbcoBrWU,3458
77
- thds_core-1.45.20251007062722.dist-info/METADATA,sha256=wsEC4MEoGuF97Kz9ciN_ZoglDcKbjOyOCKR4sqy-ZTE,2216
78
- thds_core-1.45.20251007062722.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
- thds_core-1.45.20251007062722.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
80
- thds_core-1.45.20251007062722.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
81
- thds_core-1.45.20251007062722.dist-info/RECORD,,
77
+ thds_core-1.46.20251008224105.dist-info/METADATA,sha256=0U7El82Q5euMyobeHWCgu9R5KN7tUKRO6qeHyAA7GUw,2216
78
+ thds_core-1.46.20251008224105.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ thds_core-1.46.20251008224105.dist-info/entry_points.txt,sha256=bOCOVhKZv7azF3FvaWX6uxE6yrjK6FcjqhtxXvLiFY8,161
80
+ thds_core-1.46.20251008224105.dist-info/top_level.txt,sha256=LTZaE5SkWJwv9bwOlMbIhiS-JWQEEIcjVYnJrt-CriY,5
81
+ thds_core-1.46.20251008224105.dist-info/RECORD,,