lecrapaud 0.1.0__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 lecrapaud might be problematic. Click here for more details.

Files changed (63) hide show
  1. lecrapaud/__init__.py +1 -0
  2. lecrapaud/api.py +271 -0
  3. lecrapaud/config.py +25 -0
  4. lecrapaud/db/__init__.py +1 -0
  5. lecrapaud/db/alembic/README +1 -0
  6. lecrapaud/db/alembic/env.py +78 -0
  7. lecrapaud/db/alembic/script.py.mako +26 -0
  8. lecrapaud/db/alembic/versions/2025_04_06_1738-7390745388e4_initial_setup.py +295 -0
  9. lecrapaud/db/alembic/versions/2025_04_06_1755-40cd8d3e798e_unique_constraint_for_data.py +30 -0
  10. lecrapaud/db/alembic/versions/2025_05_23_1724-2360941fa0bd_longer_string.py +52 -0
  11. lecrapaud/db/alembic/versions/2025_05_27_1159-b96396dcfaff_add_env_to_trading_tables.py +34 -0
  12. lecrapaud/db/alembic/versions/2025_05_27_1337-40cbfc215f7c_fix_nb_character_on_portfolio.py +39 -0
  13. lecrapaud/db/alembic/versions/2025_05_27_1526-3de994115317_to_datetime.py +36 -0
  14. lecrapaud/db/alembic/versions/2025_05_27_2003-25c227c684f8_add_fees_to_transactions.py +30 -0
  15. lecrapaud/db/alembic/versions/2025_05_27_2047-6b6f2d38e9bc_double_instead_of_float.py +132 -0
  16. lecrapaud/db/alembic/versions/2025_05_31_1111-c175e4a36d68_generalise_stock_to_group.py +36 -0
  17. lecrapaud/db/alembic/versions/2025_05_31_1256-5681095bfc27_create_investment_run_and_portfolio_.py +62 -0
  18. lecrapaud/db/alembic/versions/2025_05_31_1806-339927587383_add_investment_run_id.py +107 -0
  19. lecrapaud/db/alembic/versions/2025_05_31_1834-52b809a34371_make_nullablee.py +38 -0
  20. lecrapaud/db/alembic/versions/2025_05_31_1849-3b8550297e8e_change_date_to_datetime.py +44 -0
  21. lecrapaud/db/alembic/versions/2025_05_31_1852-e6b8c95d8243_add_date_to_portfolio_history.py +30 -0
  22. lecrapaud/db/alembic/versions/2025_06_10_1136-db8cdd83563a_addnewsandoptiontodata.py +32 -0
  23. lecrapaud/db/alembic/versions/2025_06_17_1652-c45f5e49fa2c_make_fields_nullable.py +89 -0
  24. lecrapaud/db/models/__init__.py +11 -0
  25. lecrapaud/db/models/base.py +181 -0
  26. lecrapaud/db/models/dataset.py +129 -0
  27. lecrapaud/db/models/feature.py +45 -0
  28. lecrapaud/db/models/feature_selection.py +125 -0
  29. lecrapaud/db/models/feature_selection_rank.py +79 -0
  30. lecrapaud/db/models/model.py +40 -0
  31. lecrapaud/db/models/model_selection.py +63 -0
  32. lecrapaud/db/models/model_training.py +62 -0
  33. lecrapaud/db/models/score.py +65 -0
  34. lecrapaud/db/models/target.py +67 -0
  35. lecrapaud/db/session.py +45 -0
  36. lecrapaud/directory_management.py +28 -0
  37. lecrapaud/experiment.py +64 -0
  38. lecrapaud/feature_engineering.py +846 -0
  39. lecrapaud/feature_selection.py +1167 -0
  40. lecrapaud/integrations/openai_integration.py +225 -0
  41. lecrapaud/jobs/__init__.py +13 -0
  42. lecrapaud/jobs/config.py +17 -0
  43. lecrapaud/jobs/scheduler.py +36 -0
  44. lecrapaud/jobs/tasks.py +57 -0
  45. lecrapaud/model_selection.py +1671 -0
  46. lecrapaud/predictions.py +292 -0
  47. lecrapaud/preprocessing.py +984 -0
  48. lecrapaud/search_space.py +848 -0
  49. lecrapaud/services/__init__.py +0 -0
  50. lecrapaud/services/embedding_categorical.py +71 -0
  51. lecrapaud/services/indicators.py +309 -0
  52. lecrapaud/speed_tests/experiments.py +139 -0
  53. lecrapaud/speed_tests/test-gpu-bilstm.ipynb +261 -0
  54. lecrapaud/speed_tests/test-gpu-resnet.ipynb +166 -0
  55. lecrapaud/speed_tests/test-gpu-transformers.ipynb +254 -0
  56. lecrapaud/speed_tests/tests.ipynb +145 -0
  57. lecrapaud/speed_tests/trash.py +37 -0
  58. lecrapaud/training.py +239 -0
  59. lecrapaud/utils.py +246 -0
  60. lecrapaud-0.1.0.dist-info/LICENSE +201 -0
  61. lecrapaud-0.1.0.dist-info/METADATA +105 -0
  62. lecrapaud-0.1.0.dist-info/RECORD +63 -0
  63. lecrapaud-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,36 @@
1
+ """generalise_stock_to_group
2
+
3
+ Revision ID: c175e4a36d68
4
+ Revises: 6b6f2d38e9bc
5
+ Create Date: 2025-05-31 11:11:42.103206
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import mysql
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'c175e4a36d68'
16
+ down_revision: Union[str, None] = '6b6f2d38e9bc'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('datasets', sa.Column('number_of_groups', sa.Integer(), nullable=False))
24
+ op.add_column('datasets', sa.Column('list_of_groups', sa.JSON(), nullable=False))
25
+ op.drop_column('datasets', 'number_of_stocks')
26
+ op.drop_column('datasets', 'list_of_companies')
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ op.add_column('datasets', sa.Column('list_of_companies', mysql.JSON(), nullable=False))
33
+ op.add_column('datasets', sa.Column('number_of_stocks', mysql.INTEGER(), autoincrement=False, nullable=False))
34
+ op.drop_column('datasets', 'list_of_groups')
35
+ op.drop_column('datasets', 'number_of_groups')
36
+ # ### end Alembic commands ###
@@ -0,0 +1,62 @@
1
+ """create_investment_run_and_portfolio_history
2
+
3
+ Revision ID: 5681095bfc27
4
+ Revises: c175e4a36d68
5
+ Create Date: 2025-05-31 12:56:05.041307
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '5681095bfc27'
16
+ down_revision: Union[str, None] = 'c175e4a36d68'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.create_table('portfolio_history',
24
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
25
+ sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
26
+ sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
27
+ sa.Column('env', sa.String(length=50), nullable=False),
28
+ sa.Column('portfolio', sa.JSON(), nullable=False),
29
+ sa.Column('total_value', sa.Double(), nullable=False),
30
+ sa.Column('total_cash', sa.Double(), nullable=False),
31
+ sa.Column('total_cash_eur', sa.Double(), nullable=False),
32
+ sa.Column('total_cash_usd', sa.Double(), nullable=False),
33
+ sa.Column('total_portfolio_value', sa.Double(), nullable=False),
34
+ sa.PrimaryKeyConstraint('id')
35
+ )
36
+ op.create_index(op.f('ix_portfolio_history_id'), 'portfolio_history', ['id'], unique=False)
37
+ op.create_table('investment_runs',
38
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
39
+ sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
40
+ sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
41
+ sa.Column('env', sa.String(length=50), nullable=False),
42
+ sa.Column('start_date', sa.Date(), nullable=False),
43
+ sa.Column('end_date', sa.Date(), nullable=True),
44
+ sa.Column('dataset_id', sa.BigInteger(), nullable=False),
45
+ sa.Column('initial_investment', sa.Double(), nullable=False),
46
+ sa.Column('portfolio_max_size', sa.Integer(), nullable=False),
47
+ sa.Column('buffer_days_rebuy', sa.Integer(), nullable=False),
48
+ sa.Column('initial_portfolio', sa.JSON(), nullable=False),
49
+ sa.ForeignKeyConstraint(['dataset_id'], ['datasets.id'], ),
50
+ sa.PrimaryKeyConstraint('id')
51
+ )
52
+ op.create_index(op.f('ix_investment_runs_id'), 'investment_runs', ['id'], unique=False)
53
+ # ### end Alembic commands ###
54
+
55
+
56
+ def downgrade() -> None:
57
+ # ### commands auto generated by Alembic - please adjust! ###
58
+ op.drop_index(op.f('ix_investment_runs_id'), table_name='investment_runs')
59
+ op.drop_table('investment_runs')
60
+ op.drop_index(op.f('ix_portfolio_history_id'), table_name='portfolio_history')
61
+ op.drop_table('portfolio_history')
62
+ # ### end Alembic commands ###
@@ -0,0 +1,107 @@
1
+ """add_investment_run_id
2
+
3
+
4
+ Revision ID: 339927587383
5
+ Revises: 5681095bfc27
6
+ Create Date: 2025-05-31 18:06:13.741056
7
+
8
+ """
9
+
10
+ from typing import Sequence, Union
11
+
12
+ from alembic import op
13
+ import sqlalchemy as sa
14
+ from sqlalchemy.dialects import mysql
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "339927587383"
18
+ down_revision: Union[str, None] = "5681095bfc27"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ op.add_column(
26
+ "operations", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
27
+ )
28
+ op.create_foreign_key(
29
+ None,
30
+ "operations",
31
+ "investment_runs",
32
+ ["investment_run_id"],
33
+ ["id"],
34
+ ondelete="CASCADE",
35
+ )
36
+ op.drop_column("operations", "env")
37
+ op.add_column(
38
+ "portfolio_history",
39
+ sa.Column("investment_run_id", sa.BigInteger(), nullable=False),
40
+ )
41
+ op.create_foreign_key(
42
+ None,
43
+ "portfolio_history",
44
+ "investment_runs",
45
+ ["investment_run_id"],
46
+ ["id"],
47
+ ondelete="CASCADE",
48
+ )
49
+ op.drop_column("portfolio_history", "env")
50
+ op.add_column(
51
+ "portfolios", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
52
+ )
53
+ op.drop_constraint("uq_portfolio_composite", "portfolios", type_="unique")
54
+ op.create_unique_constraint(
55
+ "uq_portfolio_composite", "portfolios", ["stock", "investment_run_id"]
56
+ )
57
+ op.create_foreign_key(
58
+ None,
59
+ "portfolios",
60
+ "investment_runs",
61
+ ["investment_run_id"],
62
+ ["id"],
63
+ ondelete="CASCADE",
64
+ )
65
+ op.drop_column("portfolios", "env")
66
+ op.add_column(
67
+ "transactions", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
68
+ )
69
+ op.create_foreign_key(
70
+ None,
71
+ "transactions",
72
+ "investment_runs",
73
+ ["investment_run_id"],
74
+ ["id"],
75
+ ondelete="CASCADE",
76
+ )
77
+ op.drop_column("transactions", "env")
78
+ # ### end Alembic commands ###
79
+
80
+
81
+ def downgrade() -> None:
82
+ # ### commands auto generated by Alembic - please adjust! ###
83
+ op.add_column(
84
+ "transactions", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
85
+ )
86
+ op.drop_constraint(None, "transactions", type_="foreignkey")
87
+ op.drop_column("transactions", "investment_run_id")
88
+ op.add_column(
89
+ "portfolios", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
90
+ )
91
+ op.drop_constraint(None, "portfolios", type_="foreignkey")
92
+ op.drop_constraint("uq_portfolio_composite", "portfolios", type_="unique")
93
+ op.create_unique_constraint(
94
+ "uq_portfolio_composite", "portfolios", ["stock", "env"]
95
+ )
96
+ op.drop_column("portfolios", "investment_run_id")
97
+ op.add_column(
98
+ "portfolio_history", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
99
+ )
100
+ op.drop_constraint(None, "portfolio_history", type_="foreignkey")
101
+ op.drop_column("portfolio_history", "investment_run_id")
102
+ op.add_column(
103
+ "operations", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
104
+ )
105
+ op.drop_constraint(None, "operations", type_="foreignkey")
106
+ op.drop_column("operations", "investment_run_id")
107
+ # ### end Alembic commands ###
@@ -0,0 +1,38 @@
1
+ """make_nullablee
2
+
3
+ Revision ID: 52b809a34371
4
+ Revises: 339927587383
5
+ Create Date: 2025-05-31 18:34:58.962966
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import mysql
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '52b809a34371'
16
+ down_revision: Union[str, None] = '339927587383'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.alter_column('investment_runs', 'initial_portfolio',
24
+ existing_type=mysql.JSON(),
25
+ nullable=True)
26
+ op.create_index(op.f('ix_investment_runs_id'), 'investment_runs', ['id'], unique=False)
27
+ op.create_foreign_key(None, 'portfolios', 'investment_runs', ['investment_run_id'], ['id'], ondelete='CASCADE')
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade() -> None:
32
+ # ### commands auto generated by Alembic - please adjust! ###
33
+ op.drop_constraint(None, 'portfolios', type_='foreignkey')
34
+ op.drop_index(op.f('ix_investment_runs_id'), table_name='investment_runs')
35
+ op.alter_column('investment_runs', 'initial_portfolio',
36
+ existing_type=mysql.JSON(),
37
+ nullable=False)
38
+ # ### end Alembic commands ###
@@ -0,0 +1,44 @@
1
+ """change_date_To_datetime
2
+
3
+ Revision ID: 3b8550297e8e
4
+ Revises: 52b809a34371
5
+ Create Date: 2025-05-31 18:49:59.238283
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '3b8550297e8e'
16
+ down_revision: Union[str, None] = '52b809a34371'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.alter_column('investment_runs', 'start_date',
24
+ existing_type=sa.DATE(),
25
+ type_=sa.DateTime(),
26
+ existing_nullable=False)
27
+ op.alter_column('investment_runs', 'end_date',
28
+ existing_type=sa.DATE(),
29
+ type_=sa.DateTime(),
30
+ existing_nullable=True)
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade() -> None:
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ op.alter_column('investment_runs', 'end_date',
37
+ existing_type=sa.DateTime(),
38
+ type_=sa.DATE(),
39
+ existing_nullable=True)
40
+ op.alter_column('investment_runs', 'start_date',
41
+ existing_type=sa.DateTime(),
42
+ type_=sa.DATE(),
43
+ existing_nullable=False)
44
+ # ### end Alembic commands ###
@@ -0,0 +1,30 @@
1
+ """add_date_to_portfolio_history
2
+
3
+ Revision ID: e6b8c95d8243
4
+ Revises: 3b8550297e8e
5
+ Create Date: 2025-05-31 18:52:23.021149
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'e6b8c95d8243'
16
+ down_revision: Union[str, None] = '3b8550297e8e'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('portfolio_history', sa.Column('date', sa.DateTime(), nullable=False))
24
+ # ### end Alembic commands ###
25
+
26
+
27
+ def downgrade() -> None:
28
+ # ### commands auto generated by Alembic - please adjust! ###
29
+ op.drop_column('portfolio_history', 'date')
30
+ # ### end Alembic commands ###
@@ -0,0 +1,32 @@
1
+ """AddNewsAndOptionToData
2
+
3
+ Revision ID: db8cdd83563a
4
+ Revises: e6b8c95d8243
5
+ Create Date: 2025-06-10 11:36:57.478070
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'db8cdd83563a'
16
+ down_revision: Union[str, None] = 'e6b8c95d8243'
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
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('datas', sa.Column('news', sa.JSON(), nullable=True))
24
+ op.add_column('datas', sa.Column('options', sa.JSON(), nullable=True))
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade() -> None:
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ op.drop_column('datas', 'options')
31
+ op.drop_column('datas', 'news')
32
+ # ### end Alembic commands ###
@@ -0,0 +1,89 @@
1
+ """
2
+
3
+ Revision ID: c45f5e49fa2c
4
+ Revises: db8cdd83563a
5
+ Create Date: 2025-06-17 16:52:45.042045
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+ import sqlalchemy as sa
13
+ from sqlalchemy.dialects import mysql
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "c45f5e49fa2c"
17
+ down_revision: Union[str, None] = "db8cdd83563a"
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.alter_column(
25
+ "datasets", "train_size", existing_type=mysql.INTEGER(), nullable=True
26
+ )
27
+ op.alter_column(
28
+ "datasets", "test_size", existing_type=mysql.INTEGER(), nullable=True
29
+ )
30
+ op.alter_column(
31
+ "datasets", "number_of_groups", existing_type=mysql.INTEGER(), nullable=True
32
+ )
33
+ op.alter_column(
34
+ "datasets", "list_of_groups", existing_type=mysql.JSON(), nullable=True
35
+ )
36
+ op.alter_column(
37
+ "datasets", "start_date", existing_type=mysql.DATETIME(), nullable=True
38
+ )
39
+ op.alter_column(
40
+ "datasets", "end_date", existing_type=mysql.DATETIME(), nullable=True
41
+ )
42
+ op.alter_column(
43
+ "datasets", "train_start_date", existing_type=mysql.DATETIME(), nullable=True
44
+ )
45
+ op.alter_column(
46
+ "datasets", "train_end_date", existing_type=mysql.DATETIME(), nullable=True
47
+ )
48
+ op.alter_column(
49
+ "datasets", "test_start_date", existing_type=mysql.DATETIME(), nullable=True
50
+ )
51
+ op.alter_column(
52
+ "datasets", "test_end_date", existing_type=mysql.DATETIME(), nullable=True
53
+ )
54
+ # ### end Alembic commands ###
55
+
56
+
57
+ def downgrade() -> None:
58
+ # ### commands auto generated by Alembic - please adjust! ###
59
+ op.alter_column(
60
+ "datasets", "test_end_date", existing_type=mysql.DATETIME(), nullable=False
61
+ )
62
+ op.alter_column(
63
+ "datasets", "test_start_date", existing_type=mysql.DATETIME(), nullable=False
64
+ )
65
+ op.alter_column(
66
+ "datasets", "train_end_date", existing_type=mysql.DATETIME(), nullable=False
67
+ )
68
+ op.alter_column(
69
+ "datasets", "train_start_date", existing_type=mysql.DATETIME(), nullable=False
70
+ )
71
+ op.alter_column(
72
+ "datasets", "end_date", existing_type=mysql.DATETIME(), nullable=False
73
+ )
74
+ op.alter_column(
75
+ "datasets", "start_date", existing_type=mysql.DATETIME(), nullable=False
76
+ )
77
+ op.alter_column(
78
+ "datasets", "list_of_groups", existing_type=mysql.JSON(), nullable=False
79
+ )
80
+ op.alter_column(
81
+ "datasets", "number_of_groups", existing_type=mysql.INTEGER(), nullable=False
82
+ )
83
+ op.alter_column(
84
+ "datasets", "test_size", existing_type=mysql.INTEGER(), nullable=False
85
+ )
86
+ op.alter_column(
87
+ "datasets", "train_size", existing_type=mysql.INTEGER(), nullable=False
88
+ )
89
+ # ### end Alembic commands ###
@@ -0,0 +1,11 @@
1
+ from lecrapaud.db.models.base import Base
2
+
3
+ from lecrapaud.db.models.dataset import Dataset
4
+ from lecrapaud.db.models.feature_selection_rank import FeatureSelectionRank
5
+ from lecrapaud.db.models.feature_selection import FeatureSelection
6
+ from lecrapaud.db.models.feature import Feature
7
+ from lecrapaud.db.models.model_selection import ModelSelection
8
+ from lecrapaud.db.models.model_training import ModelTraining
9
+ from lecrapaud.db.models.model import Model
10
+ from lecrapaud.db.models.score import Score
11
+ from lecrapaud.db.models.target import Target
@@ -0,0 +1,181 @@
1
+ """Base SQLAlchemy model with CRUD operations."""
2
+
3
+ from functools import wraps
4
+
5
+ from sqlalchemy.orm import DeclarativeBase
6
+ from sqlalchemy import desc, asc, and_, delete
7
+ from sqlalchemy.inspection import inspect
8
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
9
+ from lecrapaud.db.session import get_db
10
+
11
+
12
+ def with_db(func):
13
+ """Decorator to allow passing an optional db session"""
14
+
15
+ @wraps(func)
16
+ def wrapper(*args, **kwargs):
17
+ db = kwargs.pop("db", None)
18
+ if db:
19
+ return func(*args, db=db, **kwargs)
20
+ with get_db() as db:
21
+ return func(*args, db=db, **kwargs)
22
+
23
+ return wrapper
24
+
25
+
26
+ # declarative base class
27
+ class Base(DeclarativeBase):
28
+ @classmethod
29
+ @with_db
30
+ def create(cls, db, **kwargs):
31
+ instance = cls(**kwargs)
32
+ db.add(instance)
33
+ db.commit()
34
+ db.refresh(instance)
35
+ return instance
36
+
37
+ @classmethod
38
+ @with_db
39
+ def get(cls, id: int, db=None):
40
+ return db.get(cls, id)
41
+
42
+ @classmethod
43
+ @with_db
44
+ def find_by(cls, db=None, **kwargs):
45
+ return db.query(cls).filter_by(**kwargs).first()
46
+
47
+ @classmethod
48
+ @with_db
49
+ def get_all(
50
+ cls, raw=False, db=None, limit: int = 100, order: str = "desc", **kwargs
51
+ ):
52
+ order_by_field = (
53
+ desc(cls.created_at) if order == "desc" else asc(cls.created_at)
54
+ )
55
+
56
+ query = db.query(cls)
57
+
58
+ # Apply filters from kwargs
59
+ for key, value in kwargs.items():
60
+ if hasattr(cls, key):
61
+ query = query.filter(getattr(cls, key) == value)
62
+
63
+ results = query.order_by(order_by_field).limit(limit).all()
64
+
65
+ if raw:
66
+ return [
67
+ {
68
+ column.name: getattr(row, column.name)
69
+ for column in cls.__table__.columns
70
+ }
71
+ for row in results
72
+ ]
73
+
74
+ return results
75
+
76
+ @classmethod
77
+ @with_db
78
+ def filter(cls, db=None, **kwargs):
79
+ filters = []
80
+
81
+ for key, value in kwargs.items():
82
+ if "__" in key:
83
+ field, op = key.split("__", 1)
84
+ else:
85
+ field, op = key, "eq"
86
+
87
+ if not hasattr(cls, field):
88
+ raise ValueError(f"{field} is not a valid field on {cls.__name__}")
89
+
90
+ column: InstrumentedAttribute = getattr(cls, field)
91
+
92
+ if op == "eq":
93
+ filters.append(column == value)
94
+ elif op == "in":
95
+ filters.append(column.in_(value))
96
+ elif op == "gt":
97
+ filters.append(column > value)
98
+ elif op == "lt":
99
+ filters.append(column < value)
100
+ elif op == "gte":
101
+ filters.append(column >= value)
102
+ elif op == "lte":
103
+ filters.append(column <= value)
104
+ else:
105
+ raise ValueError(f"Unsupported operator: {op}")
106
+
107
+ return db.query(cls).filter(and_(*filters)).all()
108
+
109
+ @classmethod
110
+ @with_db
111
+ def update(cls, id: int, db=None, **kwargs):
112
+ instance = db.get(cls, id)
113
+ if not instance:
114
+ return None
115
+ for key, value in kwargs.items():
116
+ setattr(instance, key, value)
117
+ db.commit()
118
+ db.refresh(instance)
119
+ return instance
120
+
121
+ @classmethod
122
+ @with_db
123
+ def upsert(cls, match_fields: list[str], db=None, **kwargs):
124
+ """
125
+ Upsert an instance of the model: update if found, else create.
126
+
127
+ :param match_fields: list of field names to use for matching
128
+ :param kwargs: all fields for creation or update
129
+ """
130
+ filters = [
131
+ getattr(cls, field) == kwargs[field]
132
+ for field in match_fields
133
+ if field in kwargs
134
+ ]
135
+
136
+ instance = db.query(cls).filter(*filters).first()
137
+
138
+ if instance:
139
+ for key, value in kwargs.items():
140
+ setattr(instance, key, value)
141
+ else:
142
+ instance = cls(**kwargs)
143
+ db.add(instance)
144
+
145
+ db.commit()
146
+ db.refresh(instance)
147
+ return instance
148
+
149
+ @classmethod
150
+ @with_db
151
+ def delete(cls, id: int, db=None):
152
+ instance = db.get(cls, id)
153
+ if instance:
154
+ db.delete(instance)
155
+ db.commit()
156
+ return True
157
+ return False
158
+
159
+ @classmethod
160
+ @with_db
161
+ def delete_all(cls, db=None, **kwargs):
162
+ stmt = delete(cls)
163
+
164
+ for key, value in kwargs.items():
165
+ if hasattr(cls, key):
166
+ stmt = stmt.where(getattr(cls, key) == value)
167
+
168
+ db.execute(stmt)
169
+ db.commit()
170
+ return True
171
+
172
+ @with_db
173
+ def save(self, db=None):
174
+ self = db.merge(self)
175
+ db.add(self)
176
+ db.commit()
177
+ db.refresh(self)
178
+ return self
179
+
180
+ def to_json(self):
181
+ return {c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs}