welearn-database 1.4.0.dev0__tar.gz → 1.4.3__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 (42) hide show
  1. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/PKG-INFO +1 -1
  2. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/pyproject.toml +5 -5
  3. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/env.py +10 -2
  4. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/16ff997426d3_remove_error_retrieval_unique_constraint.py +3 -3
  5. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/4c7161819e5a_grafana_views.py +17 -0
  6. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/821173cf9c5d_initial_migration.py +118 -27
  7. welearn_database-1.4.3/welearn_database/alembic/versions/b049924f7067_modify_corpus_name_embedding_model_lang_.py +93 -0
  8. welearn_database-1.4.3/welearn_database/alembic/versions/f1ce0ad2845b_add_doi_column_to_document.py +30 -0
  9. welearn_database-1.4.3/welearn_database/alembic/versions/f8602200fa99_update_session_view.py +42 -0
  10. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/corpus_related.py +6 -2
  11. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/document_related.py +43 -24
  12. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/database_utils.py +0 -2
  13. welearn_database-1.4.3/welearn_database/exceptions.py +29 -0
  14. welearn_database-1.4.3/welearn_database/regular_expression.py +11 -0
  15. welearn_database-1.4.0.dev0/welearn_database/exceptions.py +0 -7
  16. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/LICENSE +0 -0
  17. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/README.md +0 -0
  18. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/__init__.py +0 -0
  19. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/README +0 -0
  20. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/script.py.mako +0 -0
  21. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/068312e7800c_add_referrer_origin_column_to_user_and_.py +0 -0
  22. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/0e0bc0fca384_doc_qty_per_source.py +0 -0
  23. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/2ad4895b2674_data_collection.py +0 -0
  24. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/4f5a188dd614_add_main_url_column.py +0 -0
  25. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/4fcbfb7f3145_added_api_key_management_table.py +0 -0
  26. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/5d82613c9aca_context_document.py +0 -0
  27. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/84c42739c17b_create_table_errordataquality.py +0 -0
  28. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/89920abb7ff8_add_category.py +0 -0
  29. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/96bba9e4842a_merge_external_id_error_quality.py +0 -0
  30. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/9b4f1da0c1f2_data_collection_for_focus_group.py +0 -0
  31. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/a50a1db3ca2a_add_used_since_column_for_embeddings.py +0 -0
  32. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/b031206324b7_agent_related.py +0 -0
  33. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/ccdbd708c997_create_column_external_id.py +0 -0
  34. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/alembic/versions/e354666f951d_inferred_user.py +0 -0
  35. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/__init__.py +0 -0
  36. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/enumeration.py +0 -0
  37. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/__init__.py +0 -0
  38. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/agent_related.py +0 -0
  39. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/grafana.py +0 -0
  40. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/data/models/user_related.py +0 -0
  41. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/modules/__init__.py +0 -0
  42. {welearn_database-1.4.0.dev0 → welearn_database-1.4.3}/welearn_database/modules/text_cleaning.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: welearn-database
3
- Version: 1.4.0.dev0
3
+ Version: 1.4.3
4
4
  Summary: All stuff related to relationnal database from the WeLearn project
5
5
  License: cc-by-sa-nc
6
6
  Author: Théo
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "welearn-database"
3
- version = "1.4.0.dev0"
3
+ version = "1.4.3"
4
4
  description = "All stuff related to relationnal database from the WeLearn project"
5
5
  authors = [
6
6
  {name = "Théo",email = "theo.nardin@cri-paris.org"}
@@ -18,10 +18,10 @@ dependencies = [
18
18
  [tool.poetry]
19
19
 
20
20
  [tool.poetry.group.dev.dependencies]
21
- mypy = "^1.16.0"
22
- bandit = "^1.8.6"
23
- isort = "^6.1.0"
24
- black = "^25.9.0"
21
+ mypy = "^2.1.0"
22
+ bandit = "^1.9.4"
23
+ isort = "^8.0.1"
24
+ black = "26.3.1"
25
25
 
26
26
  [build-system]
27
27
  requires = ["poetry-core>=2.0.0,<3.0.0"]
@@ -79,8 +79,16 @@ def run_migrations_online() -> None:
79
79
  if "prod" in os.getenv("PG_HOST").lower():
80
80
  print("Connecting to production database for migrations!")
81
81
  input("Press Enter to continue...")
82
- elif "dev" in os.getenv("PG_HOST" "").lower():
83
- print("Connecting to development database for migrations!")
82
+ elif (
83
+ "dev" in os.getenv("PG_HOST" "").lower()
84
+ and "staging" in os.getenv("PG_DB" "").lower()
85
+ ):
86
+ print("Connecting to staging database for migrations!")
87
+ elif (
88
+ "dev" in os.getenv("PG_HOST" "").lower()
89
+ and not "staging" in os.getenv("PG_DB" "").lower()
90
+ ):
91
+ print("Connecting to dev database for migrations!")
84
92
  else:
85
93
  print("Connecting to unknown database for migrations!")
86
94
 
@@ -9,9 +9,8 @@ Create Date: 2025-06-02 14:23:49.689745
9
9
  from typing import Sequence, Union
10
10
 
11
11
  import sqlalchemy as sa
12
- from sqlalchemy.dialects import postgresql
13
-
14
12
  from alembic import op
13
+ from sqlalchemy.dialects import postgresql
15
14
 
16
15
  # revision identifiers, used by Alembic.
17
16
  revision: str = "16ff997426d3"
@@ -21,9 +20,10 @@ depends_on: Union[str, Sequence[str], None] = None
21
20
 
22
21
 
23
22
  def upgrade() -> None:
24
- op.drop_index(
23
+ op.drop_constraint(
25
24
  "error_retrieval_document_id_http_error_code_idx",
26
25
  "error_retrieval",
26
+ type_="unique",
27
27
  schema="document_related",
28
28
  )
29
29
 
@@ -18,6 +18,8 @@ depends_on: Union[str, Sequence[str], None] = None
18
18
 
19
19
 
20
20
  def upgrade():
21
+ op.execute("CREATE SCHEMA IF NOT EXISTS grafana;")
22
+
21
23
  op.execute(
22
24
  """
23
25
  CREATE OR REPLACE VIEW grafana.corpus
@@ -162,6 +164,21 @@ AS SELECT session.id,
162
164
  op.execute(
163
165
  """
164
166
  CREATE OR REPLACE VIEW grafana.document_latest_state
167
+ AS SELECT DISTINCT ON (ps.document_id) ps.id,
168
+ ps.document_id,
169
+ wd.corpus_id,
170
+ wd.lang,
171
+ ps.title,
172
+ ps.created_at,
173
+ ps.operation_order
174
+ FROM document_related.process_state ps
175
+ JOIN document_related.welearn_document wd ON ps.document_id = wd.id
176
+ ORDER BY ps.document_id, ps.operation_order DESC;
177
+ """
178
+ )
179
+ op.execute(
180
+ """
181
+ CREATE OR REPLACE VIEW grafana.test_document_latest_state
165
182
  AS SELECT DISTINCT ON (ps.document_id) ps.id,
166
183
  ps.document_id,
167
184
  wd.corpus_id,
@@ -8,10 +8,10 @@ Create Date: 2025-01-27 18:10:55.045457
8
8
 
9
9
  from typing import Sequence, Union
10
10
 
11
+ import sqlalchemy
11
12
  import sqlalchemy as sa
12
- from sqlalchemy.dialects import postgresql
13
-
14
13
  from alembic import op
14
+ from sqlalchemy.dialects import postgresql
15
15
 
16
16
  # revision identifiers, used by Alembic.
17
17
  revision: str = "821173cf9c5d"
@@ -22,9 +22,22 @@ depends_on: Union[str, Sequence[str], None] = None
22
22
 
23
23
  def upgrade() -> None:
24
24
  # ### commands auto generated by Alembic - please adjust! ###
25
+ op.execute("CREATE SCHEMA IF NOT EXISTS document_related;")
26
+ op.execute("CREATE SCHEMA IF NOT EXISTS corpus_related;")
27
+ op.execute("CREATE SCHEMA IF NOT EXISTS user_related;")
28
+
29
+ op.execute(
30
+ "CREATE SEQUENCE IF NOT EXISTS document_related.process_state_operation_order_seq"
31
+ )
32
+
25
33
  op.create_table(
26
34
  "bi_classifier_model",
27
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
35
+ sa.Column(
36
+ "id",
37
+ sa.Uuid(),
38
+ server_default=sqlalchemy.func.gen_random_uuid(),
39
+ nullable=False,
40
+ ),
28
41
  sa.Column("title", sa.String(), nullable=False),
29
42
  sa.Column("binary_treshold", sa.NUMERIC(), nullable=False),
30
43
  sa.Column("lang", sa.String(), nullable=False),
@@ -36,7 +49,12 @@ def upgrade() -> None:
36
49
  )
37
50
  op.create_table(
38
51
  "corpus",
39
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
52
+ sa.Column(
53
+ "id",
54
+ sa.Uuid(),
55
+ server_default=sqlalchemy.func.gen_random_uuid(),
56
+ nullable=False,
57
+ ),
40
58
  sa.Column("source_name", sa.String(), nullable=False),
41
59
  sa.Column("is_fix", sa.Boolean(), nullable=False),
42
60
  sa.Column("binary_treshold", sa.NUMERIC(), nullable=False),
@@ -46,7 +64,12 @@ def upgrade() -> None:
46
64
  )
47
65
  op.create_table(
48
66
  "embedding_model",
49
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
67
+ sa.Column(
68
+ "id",
69
+ sa.Uuid(),
70
+ server_default=sqlalchemy.func.gen_random_uuid(),
71
+ nullable=False,
72
+ ),
50
73
  sa.Column("title", sa.String(), nullable=False),
51
74
  sa.Column("lang", sa.String(), nullable=False),
52
75
  sa.PrimaryKeyConstraint("id"),
@@ -54,7 +77,12 @@ def upgrade() -> None:
54
77
  )
55
78
  op.create_table(
56
79
  "n_classifier_model",
57
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
80
+ sa.Column(
81
+ "id",
82
+ sa.Uuid(),
83
+ server_default=sqlalchemy.func.gen_random_uuid(),
84
+ nullable=False,
85
+ ),
58
86
  sa.Column("title", sa.String(), nullable=False),
59
87
  sa.Column("lang", sa.String(), nullable=False),
60
88
  sa.Column("treshold_sdg_1", sa.NUMERIC(), nullable=False),
@@ -81,7 +109,12 @@ def upgrade() -> None:
81
109
  )
82
110
  op.create_table(
83
111
  "keyword",
84
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
112
+ sa.Column(
113
+ "id",
114
+ sa.Uuid(),
115
+ server_default=sqlalchemy.func.gen_random_uuid(),
116
+ nullable=False,
117
+ ),
85
118
  sa.Column("keyword", sa.String(), nullable=False),
86
119
  sa.Column(
87
120
  "created_at", postgresql.TIMESTAMP(), server_default="NOW()", nullable=False
@@ -92,7 +125,12 @@ def upgrade() -> None:
92
125
  )
93
126
  op.create_table(
94
127
  "user_profile",
95
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
128
+ sa.Column(
129
+ "id",
130
+ sa.Uuid(),
131
+ server_default=sqlalchemy.func.gen_random_uuid(),
132
+ nullable=False,
133
+ ),
96
134
  sa.Column("username", sa.String(), nullable=False),
97
135
  sa.Column("email", sa.String(), nullable=False),
98
136
  sa.Column("password_digest", sa.LargeBinary(), nullable=False),
@@ -173,7 +211,12 @@ def upgrade() -> None:
173
211
  )
174
212
  op.create_table(
175
213
  "welearn_document",
176
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
214
+ sa.Column(
215
+ "id",
216
+ sa.Uuid(),
217
+ server_default=sqlalchemy.func.gen_random_uuid(),
218
+ nullable=False,
219
+ ),
177
220
  sa.Column("url", sa.String(), nullable=False),
178
221
  sa.Column("title", sa.String(), nullable=True),
179
222
  sa.Column("lang", sa.String(), nullable=True),
@@ -198,7 +241,12 @@ def upgrade() -> None:
198
241
  )
199
242
  op.create_table(
200
243
  "chat_message",
201
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
244
+ sa.Column(
245
+ "id",
246
+ sa.Uuid(),
247
+ server_default=sqlalchemy.func.gen_random_uuid(),
248
+ nullable=False,
249
+ ),
202
250
  sa.Column("textual_content", sa.String(), nullable=False),
203
251
  sa.Column("user_id", sa.Uuid(), nullable=False),
204
252
  sa.Column(
@@ -210,13 +258,19 @@ def upgrade() -> None:
210
258
  sa.ForeignKeyConstraint(
211
259
  ["user_id"],
212
260
  ["user_related.user_profile.id"],
261
+ name="message_user_id_fkey",
213
262
  ),
214
263
  sa.PrimaryKeyConstraint("id"),
215
264
  schema="user_related",
216
265
  )
217
266
  op.create_table(
218
267
  "analytic_counter",
219
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
268
+ sa.Column(
269
+ "id",
270
+ sa.Uuid(),
271
+ server_default=sqlalchemy.func.gen_random_uuid(),
272
+ nullable=False,
273
+ ),
220
274
  sa.Column("document_id", sa.Uuid(), nullable=False),
221
275
  sa.Column("counter_name", sa.Enum("HIT", name="counter"), nullable=False),
222
276
  sa.Column("counter_value", sa.Integer(), nullable=False),
@@ -236,7 +290,12 @@ def upgrade() -> None:
236
290
  )
237
291
  op.create_table(
238
292
  "document_slice",
239
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
293
+ sa.Column(
294
+ "id",
295
+ sa.Uuid(),
296
+ server_default=sqlalchemy.func.gen_random_uuid(),
297
+ nullable=False,
298
+ ),
240
299
  sa.Column("document_id", sa.Uuid(), nullable=False),
241
300
  sa.Column("embedding", sa.LargeBinary(), nullable=True),
242
301
  sa.Column("body", sa.String(), nullable=True),
@@ -257,7 +316,12 @@ def upgrade() -> None:
257
316
  )
258
317
  op.create_table(
259
318
  "error_retrieval",
260
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
319
+ sa.Column(
320
+ "id",
321
+ sa.Uuid(),
322
+ server_default=sqlalchemy.func.gen_random_uuid(),
323
+ nullable=False,
324
+ ),
261
325
  sa.Column("document_id", sa.Uuid(), nullable=False),
262
326
  sa.Column("http_error_code", sa.Integer(), nullable=True),
263
327
  sa.Column(
@@ -282,7 +346,12 @@ def upgrade() -> None:
282
346
  )
283
347
  op.create_table(
284
348
  "process_state",
285
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
349
+ sa.Column(
350
+ "id",
351
+ sa.Uuid(),
352
+ server_default=sqlalchemy.func.gen_random_uuid(),
353
+ nullable=False,
354
+ ),
286
355
  sa.Column("document_id", sa.Uuid(), nullable=False),
287
356
  sa.Column(
288
357
  "title",
@@ -308,7 +377,9 @@ def upgrade() -> None:
308
377
  sa.Column(
309
378
  "operation_order",
310
379
  sa.BIGINT(),
311
- server_default="nextval('document_related.process_state_operation_order_seq",
380
+ server_default=sqlalchemy.text(
381
+ "nextval('document_related.process_state_operation_order_seq')"
382
+ ),
312
383
  nullable=False,
313
384
  ),
314
385
  sa.ForeignKeyConstraint(
@@ -321,7 +392,12 @@ def upgrade() -> None:
321
392
  )
322
393
  op.create_table(
323
394
  "welearn_document_keyword",
324
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
395
+ sa.Column(
396
+ "id",
397
+ sa.Uuid(),
398
+ server_default=sqlalchemy.func.gen_random_uuid(),
399
+ nullable=False,
400
+ ),
325
401
  sa.Column("welearn_document_id", sa.Uuid(), nullable=False),
326
402
  sa.Column("keyword_id", sa.Uuid(), nullable=False),
327
403
  sa.ForeignKeyConstraint(
@@ -343,7 +419,12 @@ def upgrade() -> None:
343
419
  )
344
420
  op.create_table(
345
421
  "bookmark",
346
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
422
+ sa.Column(
423
+ "id",
424
+ sa.Uuid(),
425
+ server_default=sqlalchemy.func.gen_random_uuid(),
426
+ nullable=False,
427
+ ),
347
428
  sa.Column("document_id", sa.Uuid(), nullable=False),
348
429
  sa.Column("user_id", sa.Uuid(), nullable=False),
349
430
  sa.Column(
@@ -365,7 +446,12 @@ def upgrade() -> None:
365
446
  )
366
447
  op.create_table(
367
448
  "returned_document",
368
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
449
+ sa.Column(
450
+ "id",
451
+ sa.Uuid(),
452
+ server_default=sqlalchemy.func.gen_random_uuid(),
453
+ nullable=False,
454
+ ),
369
455
  sa.Column("message_id", sa.Uuid(), nullable=False),
370
456
  sa.Column("document_id", sa.Uuid(), nullable=False),
371
457
  sa.ForeignKeyConstraint(
@@ -382,7 +468,12 @@ def upgrade() -> None:
382
468
  )
383
469
  op.create_table(
384
470
  "sdg",
385
- sa.Column("id", sa.Uuid(), server_default="gen_random_uuid()", nullable=False),
471
+ sa.Column(
472
+ "id",
473
+ sa.Uuid(),
474
+ server_default=sqlalchemy.func.gen_random_uuid(),
475
+ nullable=False,
476
+ ),
386
477
  sa.Column("sdg_number", sa.Integer(), nullable=False),
387
478
  sa.Column("slice_id", sa.Uuid(), nullable=False),
388
479
  sa.Column(
@@ -406,19 +497,19 @@ def upgrade() -> None:
406
497
  sa.PrimaryKeyConstraint("id"),
407
498
  schema="document_related",
408
499
  )
409
- op.drop_table("alembic_version")
500
+ # op.drop_table("alembic_version")
410
501
  # ### end Alembic commands ###
411
502
 
412
503
 
413
504
  def downgrade() -> None:
414
505
  # ### commands auto generated by Alembic - please adjust! ###
415
- op.create_table(
416
- "alembic_version",
417
- sa.Column(
418
- "version_num", sa.VARCHAR(length=32), autoincrement=False, nullable=False
419
- ),
420
- sa.PrimaryKeyConstraint("version_num", name="alembic_version_pkc"),
421
- )
506
+ # op.create_table(
507
+ # "alembic_version",
508
+ # sa.Column(
509
+ # "version_num", sa.VARCHAR(length=32), autoincrement=False, nullable=False
510
+ # ),
511
+ # sa.PrimaryKeyConstraint("version_num", name="alembic_version_pkc"),
512
+ # )
422
513
  op.drop_table("sdg", schema="document_related")
423
514
  op.drop_table("returned_document", schema="user_related")
424
515
  op.drop_table("bookmark", schema="user_related")
@@ -0,0 +1,93 @@
1
+ """modify corpus_name_embedding_model_lang view
2
+
3
+ Revision ID: b049924f7067
4
+ Revises: f8602200fa99
5
+ Create Date: 2026-03-31 16:09:12.085443
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "b049924f7067"
16
+ down_revision: Union[str, None] = "f8602200fa99"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.execute(
23
+ """
24
+ DROP MATERIALIZED VIEW corpus_related.corpus_name_embedding_model_lang;
25
+ """
26
+ )
27
+ op.execute(
28
+ """
29
+
30
+ CREATE MATERIALIZED VIEW corpus_related.corpus_name_embedding_model_lang
31
+ TABLESPACE pg_default
32
+ AS WITH ranked AS (
33
+ SELECT
34
+ c.source_name,
35
+ cem.corpus_id,
36
+ cem.embedding_model_id,
37
+ em.title,
38
+ em.lang,
39
+ cem.used_since,
40
+ c.category_id,
41
+ ROW_NUMBER() OVER (
42
+ PARTITION BY cem.corpus_id,
43
+ em.lang
44
+ ORDER BY
45
+ cem.used_since DESC
46
+ ) AS rn
47
+ FROM
48
+ corpus_related.corpus_embedding_model cem
49
+ JOIN corpus_related.corpus c ON
50
+ c.id = cem.corpus_id
51
+ JOIN corpus_related.embedding_model em ON
52
+ em.id = cem.embedding_model_id
53
+ WHERE
54
+ c.is_active
55
+ )
56
+ SELECT
57
+ source_name,
58
+ corpus_id,
59
+ embedding_model_id,
60
+ title,
61
+ lang,
62
+ used_since,
63
+ category_id
64
+ FROM
65
+ ranked
66
+ WHERE
67
+ rn = 1
68
+
69
+ WITH DATA;
70
+ """
71
+ )
72
+
73
+
74
+ def downgrade() -> None:
75
+ op.execute(
76
+ """
77
+ DROP MATERIALIZED VIEW corpus_related.corpus_name_embedding_model_lang;
78
+ """
79
+ )
80
+ op.execute(
81
+ """
82
+ CREATE MATERIALIZED VIEW corpus_related.corpus_name_embedding_model_lang
83
+ TABLESPACE pg_default
84
+ AS SELECT corpus.source_name,
85
+ embedding_model.title,
86
+ embedding_model.lang
87
+ FROM corpus_related.corpus
88
+ JOIN corpus_related.corpus_embedding_model ON corpus_embedding_model.corpus_id = corpus.id
89
+ JOIN corpus_related.embedding_model ON embedding_model.id = corpus_embedding_model.embedding_model_id
90
+ WHERE corpus.is_active
91
+ WITH DATA;
92
+ """
93
+ )
@@ -0,0 +1,30 @@
1
+ """add doi column to document
2
+
3
+ Revision ID: f1ce0ad2845b
4
+ Revises: f8602200fa99
5
+ Create Date: 2026-04-29 15:39:51.079086
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "f1ce0ad2845b"
16
+ down_revision: Union[str, None] = "b049924f7067"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.add_column(
23
+ "welearn_document",
24
+ sa.Column("doi", sa.String(), nullable=True, unique=True),
25
+ schema="document_related",
26
+ )
27
+
28
+
29
+ def downgrade() -> None:
30
+ op.drop_column("welearn_document", "doi", schema="document_related")
@@ -0,0 +1,42 @@
1
+ """update session view
2
+
3
+ Revision ID: f8602200fa99
4
+ Revises: 9b4f1da0c1f2
5
+ Create Date: 2026-03-12 12:34:29.240684
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "f8602200fa99"
16
+ down_revision: Union[str, None] = "9b4f1da0c1f2"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.execute(
23
+ """
24
+ CREATE OR REPLACE VIEW grafana."session"
25
+ AS SELECT *
26
+ FROM user_related.session;
27
+ """
28
+ )
29
+
30
+
31
+ def downgrade() -> None:
32
+ op.execute(
33
+ """
34
+ CREATE OR REPLACE VIEW grafana."session"
35
+ AS SELECT session.id,
36
+ session.inferred_user_id,
37
+ session.created_at,
38
+ session.end_at,
39
+ session.host
40
+ FROM user_related.session;
41
+ """
42
+ )
@@ -106,9 +106,13 @@ class CorpusNameEmbeddingModelLang(Base):
106
106
  __tablename__ = "corpus_name_embedding_model_lang"
107
107
  __table_args__ = {"schema": schema_name}
108
108
  __read_only__ = True
109
- source_name: Mapped[str] = mapped_column(primary_key=True)
109
+ source_name: Mapped[str] = mapped_column()
110
+ corpus_id: Mapped[UUID] = mapped_column(primary_key=True)
111
+ embedding_model_id: Mapped[UUID]
110
112
  title: Mapped[str]
111
- lang: Mapped[str]
113
+ lang: Mapped[str] = mapped_column(primary_key=True)
114
+ used_since: Mapped[datetime]
115
+ category_id: Mapped[UUID]
112
116
 
113
117
 
114
118
  class CorpusEmbeddingModel(Base):
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from datetime import datetime
2
3
  from typing import Any
3
4
  from urllib.parse import urlparse
@@ -30,16 +31,23 @@ from welearn_database.data.models.corpus_related import (
30
31
  EmbeddingModel,
31
32
  NClassifierModel,
32
33
  )
33
- from welearn_database.exceptions import InvalidURLScheme
34
+ from welearn_database.exceptions import ContentIsTooShort, InvalidDOI, InvalidURLScheme
34
35
  from welearn_database.modules.text_cleaning import clean_text
36
+ from welearn_database.regular_expression import DOI_VALIDATION_REGEX
35
37
 
36
38
  schema_name = DbSchemaEnum.DOCUMENT_RELATED.value
37
39
 
40
+ NOW = "NOW()"
41
+ GEN_RANDOM_UUID = "gen_random_uuid()"
42
+
38
43
 
39
44
  class WeLearnDocument(Base):
40
45
  """
41
46
  This class represents a document in the WeLearn system.
42
47
  :cvar id: The unique identifier of the document.
48
+ :cvar doi: The unique DOI identifier if it exist for this document
49
+ :cvar external_id: ID use by the document provider for identify it (ex: PubMed ID, ArXiv ID, OAI PMH identifier etc.)
50
+ :cvar external_id_type: The type of the external ID, represented as an enumeration
43
51
  :cvar url: The URL of the document.
44
52
  :cvar title: The title of the document.
45
53
  :cvar lang: The language of the document.
@@ -60,8 +68,9 @@ class WeLearnDocument(Base):
60
68
  )
61
69
 
62
70
  id: Mapped[UUID] = mapped_column(
63
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
71
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
64
72
  )
73
+ doi: Mapped[str | None]
65
74
  external_id: Mapped[str | None]
66
75
  external_id_type: Mapped[str | None] = mapped_column(
67
76
  ENUM(
@@ -86,13 +95,13 @@ class WeLearnDocument(Base):
86
95
  TIMESTAMP(timezone=False),
87
96
  nullable=False,
88
97
  default=func.localtimestamp(),
89
- server_default="NOW()",
98
+ server_default=NOW,
90
99
  )
91
100
  updated_at: Mapped[datetime] = mapped_column(
92
101
  TIMESTAMP(timezone=False),
93
102
  nullable=False,
94
103
  default=func.localtimestamp(),
95
- server_default="NOW()",
104
+ server_default=NOW,
96
105
  onupdate=func.localtimestamp(),
97
106
  )
98
107
 
@@ -127,7 +136,7 @@ class WeLearnDocument(Base):
127
136
  return value
128
137
  cleaned = clean_text(value)
129
138
  if len(value) < 25:
130
- raise ValueError(f"Content is too short : {len(value)}")
139
+ raise ContentIsTooShort(f"Content is too short : {len(value)}")
131
140
 
132
141
  # Hash compute and db storage
133
142
  self.trace = adler32(cleaned.encode("utf-8"))
@@ -145,6 +154,16 @@ class WeLearnDocument(Base):
145
154
  return value
146
155
  return clean_text(value)
147
156
 
157
+ @validates("doi")
158
+ def validate_doi(self, key, value):
159
+ """"""
160
+ if not value:
161
+ return value
162
+
163
+ if not re.match(DOI_VALIDATION_REGEX, value):
164
+ raise InvalidDOI(f"DOI is not valid : {value}")
165
+ return value
166
+
148
167
 
149
168
  class ProcessState(Base):
150
169
  """
@@ -161,7 +180,7 @@ class ProcessState(Base):
161
180
  __table_args__ = {"schema": schema_name}
162
181
 
163
182
  id: Mapped[UUID] = mapped_column(
164
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
183
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
165
184
  )
166
185
  document_id: Mapped[UUID] = mapped_column(
167
186
  types.Uuid,
@@ -180,7 +199,7 @@ class ProcessState(Base):
180
199
  TIMESTAMP(timezone=False),
181
200
  nullable=False,
182
201
  default=func.localtimestamp(),
183
- server_default="NOW()",
202
+ server_default=NOW,
184
203
  )
185
204
  operation_order = mapped_column(
186
205
  types.BIGINT,
@@ -198,14 +217,14 @@ class Keyword(Base):
198
217
  )
199
218
 
200
219
  id: Mapped[UUID] = mapped_column(
201
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
220
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
202
221
  )
203
222
  keyword: Mapped[str]
204
223
  created_at: Mapped[datetime] = mapped_column(
205
224
  TIMESTAMP(timezone=False),
206
225
  nullable=False,
207
226
  default=func.localtimestamp(),
208
- server_default="NOW()",
227
+ server_default=NOW,
209
228
  )
210
229
 
211
230
 
@@ -220,7 +239,7 @@ class WeLearnDocumentKeyword(Base):
220
239
  {"schema": schema_name},
221
240
  )
222
241
  id: Mapped[UUID] = mapped_column(
223
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
242
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
224
243
  )
225
244
  welearn_document_id: Mapped[UUID] = mapped_column(
226
245
  types.Uuid,
@@ -242,7 +261,7 @@ class ErrorRetrieval(Base):
242
261
  __table_args__ = ({"schema": schema_name},)
243
262
 
244
263
  id: Mapped[UUID] = mapped_column(
245
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
264
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
246
265
  )
247
266
 
248
267
  document_id: Mapped[UUID] = mapped_column(
@@ -258,13 +277,13 @@ class ErrorRetrieval(Base):
258
277
  TIMESTAMP(timezone=False),
259
278
  nullable=False,
260
279
  default=func.localtimestamp(),
261
- server_default="NOW()",
280
+ server_default=NOW,
262
281
  )
263
282
  updated_at: Mapped[datetime] = mapped_column(
264
283
  TIMESTAMP(timezone=False),
265
284
  nullable=False,
266
285
  default=func.localtimestamp(),
267
- server_default="NOW()",
286
+ server_default=NOW,
268
287
  onupdate=func.localtimestamp(),
269
288
  )
270
289
  error_info: Mapped[str]
@@ -280,7 +299,7 @@ class ErrorDataQuality(Base):
280
299
  types.Uuid,
281
300
  primary_key=True,
282
301
  nullable=False,
283
- server_default="gen_random_uuid()",
302
+ server_default=GEN_RANDOM_UUID,
284
303
  )
285
304
  document_id: Mapped[UUID] = mapped_column(
286
305
  types.Uuid,
@@ -304,7 +323,7 @@ class ErrorDataQuality(Base):
304
323
  TIMESTAMP(timezone=False),
305
324
  nullable=False,
306
325
  default=func.localtimestamp(),
307
- server_default="NOW()",
326
+ server_default=NOW,
308
327
  )
309
328
  document: Mapped["WeLearnDocument"] = relationship(cascade="all, delete")
310
329
  slice: Mapped["DocumentSlice"] = relationship(cascade="all, delete")
@@ -315,7 +334,7 @@ class DocumentSlice(Base):
315
334
  __table_args__ = {"schema": schema_name}
316
335
 
317
336
  id: Mapped[UUID] = mapped_column(
318
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
337
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
319
338
  )
320
339
  document_id: Mapped[UUID] = mapped_column(
321
340
  types.Uuid,
@@ -345,7 +364,7 @@ class AnalyticCounter(Base):
345
364
  __table_args__ = {"schema": schema_name}
346
365
 
347
366
  id: Mapped[UUID] = mapped_column(
348
- types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
367
+ types.Uuid, primary_key=True, nullable=False, server_default=GEN_RANDOM_UUID
349
368
  )
350
369
  document_id: Mapped[UUID] = mapped_column(
351
370
  types.Uuid,
@@ -361,13 +380,13 @@ class AnalyticCounter(Base):
361
380
  TIMESTAMP(timezone=False),
362
381
  nullable=False,
363
382
  default=func.localtimestamp(),
364
- server_default="NOW()",
383
+ server_default=NOW,
365
384
  )
366
385
  updated_at: Mapped[datetime] = mapped_column(
367
386
  TIMESTAMP(timezone=False),
368
387
  nullable=False,
369
388
  default=func.localtimestamp(),
370
- server_default="NOW()",
389
+ server_default=NOW,
371
390
  onupdate=func.localtimestamp(),
372
391
  )
373
392
  document: Mapped["WeLearnDocument"] = relationship()
@@ -381,7 +400,7 @@ class Sdg(Base):
381
400
  types.Uuid,
382
401
  primary_key=True,
383
402
  nullable=False,
384
- server_default="gen_random_uuid()",
403
+ server_default=GEN_RANDOM_UUID,
385
404
  )
386
405
  slice_id = mapped_column(
387
406
  types.Uuid,
@@ -396,7 +415,7 @@ class Sdg(Base):
396
415
  TIMESTAMP(timezone=False),
397
416
  nullable=False,
398
417
  default=func.localtimestamp(),
399
- server_default="NOW()",
418
+ server_default=NOW,
400
419
  )
401
420
 
402
421
  bi_classifier_model_id = mapped_column(
@@ -418,7 +437,7 @@ class ContextDocument(Base):
418
437
  id = mapped_column(
419
438
  types.Uuid,
420
439
  primary_key=True,
421
- server_default="gen_random_uuid()",
440
+ server_default=GEN_RANDOM_UUID,
422
441
  nullable=False,
423
442
  )
424
443
  url: Mapped[str]
@@ -433,7 +452,7 @@ class ContextDocument(Base):
433
452
  TIMESTAMP(timezone=False),
434
453
  nullable=False,
435
454
  default=func.localtimestamp(),
436
- server_default="NOW()",
455
+ server_default=NOW,
437
456
  )
438
457
  embedding: Mapped[bytes] = mapped_column(LargeBinary)
439
458
 
@@ -484,4 +503,4 @@ class QtyDocumentPerCorpus(Base):
484
503
  __read_only__ = True
485
504
 
486
505
  source_name: Mapped[str] = mapped_column(primary_key=True)
487
- count: Mapped[int]
506
+ count: Mapped[int]
@@ -1,7 +1,5 @@
1
1
  import logging
2
- import math
3
2
  import os
4
- from typing import Any, List
5
3
 
6
4
  from sqlalchemy import URL, create_engine
7
5
  from sqlalchemy.orm import sessionmaker
@@ -0,0 +1,29 @@
1
+ class InvalidURLScheme(Exception):
2
+ """
3
+ Scheme detected in URL is not accepted
4
+ """
5
+
6
+ def __init__(self, msg="URL schema is not accepted", *args):
7
+ super().__init__(msg, *args)
8
+
9
+
10
+ class InvalidDOI(Exception):
11
+ """
12
+ Scheme detected in DOI is not accepted
13
+ """
14
+
15
+ def __init__(self, msg="DOI schema is not accepted", *args):
16
+ super().__init__(msg, *args)
17
+
18
+
19
+ class ContentIsTooShort(Exception):
20
+ """
21
+ The string used as content is too short, it should be at least 25 characters long
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ msg="Content is too short, it should be at least 25 characters long",
27
+ *args,
28
+ ):
29
+ super().__init__(msg, *args)
@@ -0,0 +1,11 @@
1
+ # Regular expressions for data cleaning and preprocessing in the WeLearn Database project.
2
+
3
+ # description: Matches backline characters (newline, tab, carriage return) for removal or replacement.
4
+ # example: "Hello\n\tWorld" -> matches "\n" and "\t"
5
+ # limit: Does not match other whitespace characters like spaces or form feeds.
6
+ BACKLINES_REGEX = r"([\n\t\r])"
7
+
8
+ # description: Match DOI identifier without doi.org as prefix
9
+ # example : 10.1590/s0103-90162002000200027 -> matches "10.1590/s0103-90162002000200027"
10
+ # limit : can't be really used for extraction, can be roughly used for other things than validation
11
+ DOI_VALIDATION_REGEX = r"^(10\.\d{4,5}\/[\S]+[^;,.\s])$"
@@ -1,7 +0,0 @@
1
- class InvalidURLScheme(Exception):
2
- """
3
- Scheme detected in URL is not accepted
4
- """
5
-
6
- def __init__(self, msg="URL schema is not accepted", *args):
7
- super().__init__(msg, *args)