half-orm 1.0.0rc14__tar.gz → 1.0.0rc15__tar.gz

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.
Files changed (40) hide show
  1. {half_orm-1.0.0rc14/half_orm.egg-info → half_orm-1.0.0rc15}/PKG-INFO +1 -1
  2. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/cli.py +1 -0
  3. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/field.py +19 -3
  4. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/model.py +112 -0
  5. half_orm-1.0.0rc15/half_orm/version.txt +1 -0
  6. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15/half_orm.egg-info}/PKG-INFO +1 -1
  7. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/pyproject.toml +1 -1
  8. half_orm-1.0.0rc14/half_orm/version.txt +0 -1
  9. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/AUTHORS +0 -0
  10. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/LICENSE +0 -0
  11. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/README.md +0 -0
  12. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/__init__.py +0 -0
  13. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/__main__.py +0 -0
  14. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/cli_utils.py +0 -0
  15. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/field_errors.py +0 -0
  16. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/fkey.py +0 -0
  17. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/hotest.py +0 -0
  18. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/migrations/BREAKING_CHANGES-1.0.0.md +0 -0
  19. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/migrations/__init__.py +0 -0
  20. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/model_errors.py +0 -0
  21. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/null.py +0 -0
  22. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/pg_meta.py +0 -0
  23. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/py.typed +0 -0
  24. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/relation.py +0 -0
  25. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/relation_errors.py +0 -0
  26. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/relation_factory.py +0 -0
  27. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/sql_adapter.py +0 -0
  28. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/sql_ast.py +0 -0
  29. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/testing.py +0 -0
  30. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/transaction.py +0 -0
  31. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm/utils.py +0 -0
  32. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm.egg-info/SOURCES.txt +0 -0
  33. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm.egg-info/dependency_links.txt +0 -0
  34. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm.egg-info/entry_points.txt +0 -0
  35. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm.egg-info/requires.txt +0 -0
  36. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/half_orm.egg-info/top_level.txt +0 -0
  37. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/setup.cfg +0 -0
  38. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/test/test_cli.py +0 -0
  39. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/test/test_main.py +0 -0
  40. {half_orm-1.0.0rc14 → half_orm-1.0.0rc15}/test/test_sql_ast.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm
3
- Version: 1.0.0rc14
3
+ Version: 1.0.0rc15
4
4
  Summary: A database-first ORM for PostgreSQL
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License: GPL-3.0
@@ -98,6 +98,7 @@ OFFICIAL_EXTENSIONS = {
98
98
  'half_orm_test_extension',
99
99
  'half_orm_inspect',
100
100
  'half_orm_dev',
101
+ 'half_orm_gen'
101
102
  }
102
103
 
103
104
  def get_config_file():
@@ -6,6 +6,7 @@
6
6
  import re
7
7
  import sys
8
8
  import typing
9
+ import warnings
9
10
  import yaml
10
11
  from collections.abc import Iterable
11
12
  from half_orm.null import NULL
@@ -13,6 +14,9 @@ from half_orm.sql_adapter import SQL_ADAPTER
13
14
  from half_orm.sql_ast import FieldExpr
14
15
 
15
16
 
17
+ _TEXT_LIKE_TYPES = {'text', 'varchar', 'character varying', 'char', 'bpchar', 'name', 'citext'}
18
+
19
+
16
20
  class Expr:
17
21
  """A raw SQL expression for use with :meth:`Field.set`.
18
22
 
@@ -252,7 +256,7 @@ class Field():
252
256
  cast = f'::{self.__sql_type}'
253
257
  if col_is_array and comp == '=':
254
258
  where_repr = f'{comp_str} = ANY({self.__praf(query, ho_id)})'
255
- elif not self.unaccent:
259
+ elif not self.unaccent or not self._is_text_like:
256
260
  where_repr = f"{self.__praf(query, ho_id)} {comp} {comp_str}{cast}"
257
261
  else:
258
262
  where_repr = f"unaccent({self.__praf(query, ho_id)}) {comp} unaccent({comp_str}{cast})"
@@ -296,7 +300,7 @@ class Field():
296
300
  comp=comp,
297
301
  placeholder=f'{comp_str}{cast}',
298
302
  value=self,
299
- unaccent=self.unaccent,
303
+ unaccent=self.unaccent and self._is_text_like,
300
304
  array_any=is_array_any,
301
305
  )
302
306
 
@@ -366,7 +370,7 @@ class Field():
366
370
  self.__comp = '='
367
371
  self.__unaccent = False
368
372
  return
369
- self.__unaccent = unaccent
373
+ self.unaccent = unaccent
370
374
  comp = None
371
375
  if isinstance(value, tuple):
372
376
  if len(value) != 2:
@@ -428,10 +432,22 @@ class Field():
428
432
  """
429
433
  return self.__unaccent
430
434
 
435
+ @property
436
+ def _is_text_like(self):
437
+ base_type = self.__sql_type.lstrip('_')
438
+ return base_type in _TEXT_LIKE_TYPES
439
+
431
440
  @unaccent.setter
432
441
  def unaccent(self, value):
433
442
  if not isinstance(value, bool):
434
443
  raise RuntimeError('unaccent value must be True or False!')
444
+ if value and not self._is_text_like:
445
+ warnings.warn(
446
+ f"unaccent ignored: field '{self.__name}' has non-text type '{self.__sql_type}'",
447
+ UserWarning,
448
+ stacklevel=2,
449
+ )
450
+ value = False
435
451
  self.__unaccent = value
436
452
 
437
453
  def _comp(self):
@@ -41,6 +41,18 @@ CONF_DIR = os.path.abspath(environ.get('HALFORM_CONF_DIR', '/etc/half_orm'))
41
41
 
42
42
  # UUID is natively supported in psycopg 3
43
43
 
44
+ _SQL_TO_JSON = {
45
+ 'uuid': 'string', 'text': 'string', 'varchar': 'string', 'bpchar': 'string',
46
+ 'int4': 'integer', 'int8': 'integer', 'int2': 'integer',
47
+ 'float4': 'number', 'float8': 'number', 'numeric': 'number',
48
+ 'bool': 'boolean',
49
+ 'date': 'date', 'timestamp': 'datetime', 'timestamptz': 'datetime',
50
+ 'jsonb': 'json', 'json': 'json',
51
+ }
52
+ def _sql_to_json_type(sql_type: str) -> str:
53
+ base = sql_type.lstrip('_')
54
+ return _SQL_TO_JSON.get(base, 'string')
55
+
44
56
  register = register_class
45
57
 
46
58
  class Model:
@@ -167,6 +179,106 @@ class Model:
167
179
 
168
180
  reconnect = __connect
169
181
 
182
+ def ho_meta(self) -> dict:
183
+ """Return a structured description of all relations visible in the database scope.
184
+
185
+ For each relation (table, view, materialized view…) the returned dict maps
186
+ ``'<schema>/<table>'`` keys to a metadata dict with the following structure::
187
+
188
+ {
189
+ 'schema': str, # schema name
190
+ 'table': str, # relation name
191
+ 'kind': str, # 'r' table, 'v' view, 'm' matview, 'p' partition
192
+ 'pk_fields': list[str], # primary-key column names
193
+ 'fields': [
194
+ {
195
+ 'name': str, # column name
196
+ 'sql_type': str, # PostgreSQL type (e.g. 'text', 'int4')
197
+ 'json_type': str, # JSON schema type (e.g. 'string', 'integer')
198
+ 'is_pk': bool,
199
+ 'not_null': bool,
200
+ 'has_default': bool,
201
+ },
202
+ ...
203
+ ],
204
+ 'fk_deps': [ # outgoing foreign keys
205
+ {
206
+ 'local_fields': list[str],
207
+ 'remote_schema': str,
208
+ 'remote_table': str,
209
+ 'remote_fields': list[str],
210
+ },
211
+ ...
212
+ ],
213
+ 'reverse_fks': [ # incoming foreign keys
214
+ {
215
+ 'local_fields': list[str],
216
+ 'remote_schema': str,
217
+ 'remote_table': str,
218
+ 'remote_fields': list[str],
219
+ 'is_singleton': bool,
220
+ },
221
+ ...
222
+ ],
223
+ }
224
+
225
+ Returns:
226
+ dict: mapping ``'<schema>/<table>'`` → metadata dict (see above).
227
+
228
+ Example::
229
+
230
+ model = Model('halftest')
231
+ meta = model.ho_meta()
232
+ person = meta['actor/person']
233
+ print(person['kind']) # 'r'
234
+ print(person['pk_fields']) # ['id']
235
+ for f in person['fields']:
236
+ print(f['name'], f['sql_type'])
237
+ """
238
+ result = {}
239
+ for kind, sfqrn, _ in self.desc():
240
+ dbname, schema, table = sfqrn
241
+ key = f'{schema}/{table}'
242
+ fields_meta = self._fields_metadata(sfqrn)
243
+ fkeys_meta = self._fkeys_metadata(sfqrn)
244
+ pk_fields = self._pkey_constraint(sfqrn)
245
+
246
+ fields = []
247
+ for fname, fdata in fields_meta.items():
248
+ fields.append({
249
+ 'name': fname,
250
+ 'sql_type': fdata['fieldtype'],
251
+ 'json_type': _sql_to_json_type(fdata['fieldtype']), # helper à ajouter
252
+ 'is_pk': bool(fdata.get('pkey')),
253
+ 'not_null': bool(fdata.get('notnull')),
254
+ 'has_default': fdata.get('default_expr') is not None,
255
+ })
256
+
257
+ fk_deps, reverse_fks = [], []
258
+ for fk_name, fk_data in fkeys_meta.items():
259
+ ftable_key, ffields, local_fields, upd, del_, is_reverse, is_singleton = fk_data
260
+ _, r_schema, r_table = ftable_key
261
+ entry = {
262
+ 'local_fields': local_fields,
263
+ 'remote_schema': r_schema,
264
+ 'remote_table': r_table,
265
+ 'remote_fields': ffields,
266
+ }
267
+ if is_reverse:
268
+ entry['is_singleton'] = is_singleton
269
+ reverse_fks.append(entry)
270
+ else:
271
+ fk_deps.append(entry)
272
+
273
+ result[key] = {
274
+ 'schema': schema, 'table': table, 'kind': kind,
275
+ 'pk_fields': pk_fields,
276
+ 'fields': fields,
277
+ 'fk_deps': fk_deps,
278
+ 'reverse_fks': reverse_fks,
279
+ }
280
+ return result
281
+
170
282
  def get_relation_class(self, relation_name: str, fields_aliases: typing.Dict=None): # -> Relation
171
283
  """Generate a :class:`~half_orm.relation.Relation` subclass for a table or view.
172
284
 
@@ -0,0 +1 @@
1
+ 1.0.0rc15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: half_orm
3
- Version: 1.0.0rc14
3
+ Version: 1.0.0rc15
4
4
  Summary: A database-first ORM for PostgreSQL
5
5
  Author-email: Joël Maïzi <joel.maizi@collorg.org>
6
6
  License: GPL-3.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"
4
4
 
5
5
  [project]
6
6
  name = "half_orm"
7
- version = "1.0.0rc14"
7
+ version = "1.0.0rc15"
8
8
  description = "A database-first ORM for PostgreSQL"
9
9
  readme = "README.md"
10
10
  license = { text = "GPL-3.0" }
@@ -1 +0,0 @@
1
- 1.0.0rc14
File without changes
File without changes
File without changes
File without changes