diff --git a/.env b/.env new file mode 100644 index 00000000..02ccbb3a --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASS=StrongPassword!@DB +DB_NAME=luxury_hotel_db diff --git a/Backend/alembic/__pycache__/env.cpython-312.pyc b/Backend/alembic/__pycache__/env.cpython-312.pyc index 242ab736..e21ea042 100644 Binary files a/Backend/alembic/__pycache__/env.cpython-312.pyc and b/Backend/alembic/__pycache__/env.cpython-312.pyc differ diff --git a/Backend/alembic/env.py b/Backend/alembic/env.py index de9d4ee7..9aa66c2a 100644 --- a/Backend/alembic/env.py +++ b/Backend/alembic/env.py @@ -1,5 +1,5 @@ from logging.config import fileConfig -from sqlalchemy import engine_from_config +from sqlalchemy import create_engine from sqlalchemy import pool from alembic import context import os @@ -15,17 +15,19 @@ config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) database_url = settings.database_url -config.set_main_option('sqlalchemy.url', database_url) target_metadata = Base.metadata def run_migrations_offline() -> None: - url = config.get_main_option('sqlalchemy.url') + """Run migrations in 'offline' mode.""" + url = database_url context.configure(url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={'paramstyle': 'named'}) with context.begin_transaction(): context.run_migrations() def run_migrations_online() -> None: - connectable = engine_from_config(config.get_section(config.config_ini_section, {}), prefix='sqlalchemy.', poolclass=pool.NullPool) + """Run migrations in 'online' mode.""" + # Create engine directly from URL to avoid ConfigParser interpolation issues + connectable = create_engine(database_url, poolclass=pool.NullPool) with connectable.connect() as connection: context.configure(connection=connection, target_metadata=target_metadata) with context.begin_transaction(): diff --git a/Backend/alembic/versions/54e4d0db31a3_create_user_sessions_table.py b/Backend/alembic/versions/54e4d0db31a3_create_user_sessions_table.py new file mode 100644 index 00000000..b43fffea --- /dev/null +++ b/Backend/alembic/versions/54e4d0db31a3_create_user_sessions_table.py @@ -0,0 +1,92 @@ +"""create_user_sessions_table + +Revision ID: 54e4d0db31a3 +Revises: d709b14aa24a +Create Date: 2025-12-06 01:12:15.123456 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + + +# revision identifiers, used by Alembic. +revision = '54e4d0db31a3' +down_revision = 'd709b14aa24a' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Check if table exists before creating + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + if 'user_sessions' not in tables: + # Create user sessions table + op.create_table( + 'user_sessions', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('session_token', sa.String(length=255), nullable=False), + sa.Column('refresh_token', sa.String(length=255), nullable=True), + sa.Column('ip_address', sa.String(length=45), nullable=True), + sa.Column('user_agent', sa.String(length=500), nullable=True), + sa.Column('device_info', sa.Text(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1'), + sa.Column('last_activity', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.Column('expires_at', sa.DateTime(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + # Create indexes + op.create_index(op.f('ix_user_sessions_id'), 'user_sessions', ['id'], unique=False) + op.create_index(op.f('ix_user_sessions_user_id'), 'user_sessions', ['user_id'], unique=False) + op.create_index(op.f('ix_user_sessions_session_token'), 'user_sessions', ['session_token'], unique=True) + op.create_index(op.f('ix_user_sessions_refresh_token'), 'user_sessions', ['refresh_token'], unique=True) + op.create_index(op.f('ix_user_sessions_is_active'), 'user_sessions', ['is_active'], unique=False) + op.create_index(op.f('ix_user_sessions_last_activity'), 'user_sessions', ['last_activity'], unique=False) + op.create_index(op.f('ix_user_sessions_expires_at'), 'user_sessions', ['expires_at'], unique=False) + + +def downgrade() -> None: + # Drop indexes + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + if 'user_sessions' in tables: + try: + op.drop_index(op.f('ix_user_sessions_expires_at'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_last_activity'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_is_active'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_refresh_token'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_session_token'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_user_id'), table_name='user_sessions') + except Exception: + pass + try: + op.drop_index(op.f('ix_user_sessions_id'), table_name='user_sessions') + except Exception: + pass + + # Drop table + op.drop_table('user_sessions') diff --git a/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc index 7f2fe532..c4646114 100644 Binary files a/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/08e2f866e131_add_mfa_fields_to_users.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc index 5a6478e1..1d83b436 100644 Binary files a/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/0e2dc5df18c3_add_privacy_terms_refunds_to_page_type_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc index 4bcfed31..4d5bfb74 100644 Binary files a/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/1444eb61188e_add_section_title_fields_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc index fbc1ce4e..67f97a05 100644 Binary files a/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/163657e72e93_add_page_content_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc index 947574ae..24cbf7db 100644 Binary files a/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/17efc6439cc3_add_luxury_section_fields_to_page_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/316d876b1b71_add_newsletter_subscribers_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/316d876b1b71_add_newsletter_subscribers_table.cpython-312.pyc index 8719cf01..e7c9c195 100644 Binary files a/Backend/alembic/versions/__pycache__/316d876b1b71_add_newsletter_subscribers_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/316d876b1b71_add_newsletter_subscribers_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/54e4d0db31a3_create_user_sessions_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/54e4d0db31a3_create_user_sessions_table.cpython-312.pyc new file mode 100644 index 00000000..9fdb3a9a Binary files /dev/null and b/Backend/alembic/versions/__pycache__/54e4d0db31a3_create_user_sessions_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc index 3cdfb0fa..e75fedf3 100644 Binary files a/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/59baf2338f8a_initial_migration_create_all_tables_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc b/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc index a1dd6121..184d9732 100644 Binary files a/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/6a126cc5b23c_add_capacity_room_size_view_to_rooms.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/6f7f8689fc98_add_anonymous_gdpr_support.cpython-312.pyc b/Backend/alembic/versions/__pycache__/6f7f8689fc98_add_anonymous_gdpr_support.cpython-312.pyc index 312d9344..f10c963b 100644 Binary files a/Backend/alembic/versions/__pycache__/6f7f8689fc98_add_anonymous_gdpr_support.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/6f7f8689fc98_add_anonymous_gdpr_support.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/7a899ef55e3b_add_comprehensive_gdpr_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/7a899ef55e3b_add_comprehensive_gdpr_tables.cpython-312.pyc index ee44ba5f..62ea9346 100644 Binary files a/Backend/alembic/versions/__pycache__/7a899ef55e3b_add_comprehensive_gdpr_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/7a899ef55e3b_add_comprehensive_gdpr_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/87e29a777cb3_add_recipient_type_to_campaigns.cpython-312.pyc b/Backend/alembic/versions/__pycache__/87e29a777cb3_add_recipient_type_to_campaigns.cpython-312.pyc index 27dd199f..5dd517b3 100644 Binary files a/Backend/alembic/versions/__pycache__/87e29a777cb3_add_recipient_type_to_campaigns.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/87e29a777cb3_add_recipient_type_to_campaigns.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc index 578a5ab1..0f770832 100644 Binary files a/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/96c23dad405d_add_system_settings_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc index dec17c65..1498bb0b 100644 Binary files a/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/9bb08492a382_add_cancellation_accessibility_faq_to_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc index 4ecd91c2..3a07d0d1 100644 Binary files a/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_about_page_fields.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc index d45c92b5..68a9a9ae 100644 Binary files a/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_badges_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_blog_posts_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_blog_posts_table.cpython-312.pyc index 3122a342..157acfc1 100644 Binary files a/Backend/alembic/versions/__pycache__/add_blog_posts_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_blog_posts_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_borica_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_borica_payment_method.cpython-312.pyc index 43415a7a..ff1eeab1 100644 Binary files a/Backend/alembic/versions/__pycache__/add_borica_payment_method.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_borica_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc index a792ba22..131f4c32 100644 Binary files a/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_copyright_text_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_enterprise_features.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_enterprise_features.cpython-312.pyc index fee35c8b..3de9cc6c 100644 Binary files a/Backend/alembic/versions/__pycache__/add_enterprise_features.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_enterprise_features.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_enterprise_homepage_fields_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_enterprise_homepage_fields_to_page_content.cpython-312.pyc index 53c8dd67..2bde4a57 100644 Binary files a/Backend/alembic/versions/__pycache__/add_enterprise_homepage_fields_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_enterprise_homepage_fields_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_group_booking_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_group_booking_tables.cpython-312.pyc index 2403c8ae..c3910148 100644 Binary files a/Backend/alembic/versions/__pycache__/add_group_booking_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_group_booking_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_guest_profile_crm_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_guest_profile_crm_tables.cpython-312.pyc index 4e3be4d6..096661ad 100644 Binary files a/Backend/alembic/versions/__pycache__/add_guest_profile_crm_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_guest_profile_crm_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_guest_requests_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_guest_requests_table.cpython-312.pyc index 09d42114..2f2be9b0 100644 Binary files a/Backend/alembic/versions/__pycache__/add_guest_requests_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_guest_requests_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_inventory_management_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_inventory_management_tables.cpython-312.pyc index 6d4a4910..bcec65ea 100644 Binary files a/Backend/alembic/versions/__pycache__/add_inventory_management_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_inventory_management_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_loyalty_system_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_loyalty_system_tables.cpython-312.pyc index 2ed5f96f..c15f72bb 100644 Binary files a/Backend/alembic/versions/__pycache__/add_loyalty_system_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_loyalty_system_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_photos_to_housekeeping_tasks.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_photos_to_housekeeping_tasks.cpython-312.pyc index 029b575e..566f74f7 100644 Binary files a/Backend/alembic/versions/__pycache__/add_photos_to_housekeeping_tasks.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_photos_to_housekeeping_tasks.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_rate_plan_id_to_bookings.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_rate_plan_id_to_bookings.cpython-312.pyc index a773cbb9..76153a93 100644 Binary files a/Backend/alembic/versions/__pycache__/add_rate_plan_id_to_bookings.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_rate_plan_id_to_bookings.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_sections_to_blog_posts.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_sections_to_blog_posts.cpython-312.pyc index ce5e6499..3856be38 100644 Binary files a/Backend/alembic/versions/__pycache__/add_sections_to_blog_posts.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_sections_to_blog_posts.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_service_detail_fields.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_service_detail_fields.cpython-312.pyc index 9b01d96b..ea545394 100644 Binary files a/Backend/alembic/versions/__pycache__/add_service_detail_fields.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_service_detail_fields.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_staff_shifts_tables.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_staff_shifts_tables.cpython-312.pyc index 01856d2c..2d8740ce 100644 Binary files a/Backend/alembic/versions/__pycache__/add_staff_shifts_tables.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_staff_shifts_tables.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc index 8682b2de..f6e289a6 100644 Binary files a/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/add_stripe_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/b1c4d7c154ec_add_enterprise_promotion_conditions.cpython-312.pyc b/Backend/alembic/versions/__pycache__/b1c4d7c154ec_add_enterprise_promotion_conditions.cpython-312.pyc index 37941486..eae1f631 100644 Binary files a/Backend/alembic/versions/__pycache__/b1c4d7c154ec_add_enterprise_promotion_conditions.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/b1c4d7c154ec_add_enterprise_promotion_conditions.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc b/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc index 58ca6030..4d63112d 100644 Binary files a/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/bd309b0742c1_add_promotion_fields_to_bookings.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc index 97ac4934..db8824f1 100644 Binary files a/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/bfa74be4b256_add_luxury_content_fields_to_page_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc index e5e51b8b..1d2160bb 100644 Binary files a/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/cce764ef7a50_add_map_url_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/d032f2351965_add_financial_audit_trail_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/d032f2351965_add_financial_audit_trail_table.cpython-312.pyc index f36e5455..f737793c 100644 Binary files a/Backend/alembic/versions/__pycache__/d032f2351965_add_financial_audit_trail_table.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/d032f2351965_add_financial_audit_trail_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/d709b14aa24a_create_gdpr_requests_table.cpython-312.pyc b/Backend/alembic/versions/__pycache__/d709b14aa24a_create_gdpr_requests_table.cpython-312.pyc new file mode 100644 index 00000000..2458caec Binary files /dev/null and b/Backend/alembic/versions/__pycache__/d709b14aa24a_create_gdpr_requests_table.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc index 7b437edc..7cc3fb77 100644 Binary files a/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/d9aff6c5f0d4_add_paypal_payment_method.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/dbafe747c931_merge_enterprise_and_borica.cpython-312.pyc b/Backend/alembic/versions/__pycache__/dbafe747c931_merge_enterprise_and_borica.cpython-312.pyc index 5e116ff9..8648a149 100644 Binary files a/Backend/alembic/versions/__pycache__/dbafe747c931_merge_enterprise_and_borica.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/dbafe747c931_merge_enterprise_and_borica.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc b/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc index bf4b9775..f5bf18a9 100644 Binary files a/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/f1a2b3c4d5e6_add_is_proforma_to_invoices.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/fe519abcefe7_add_email_verification_and_enhance_user_.cpython-312.pyc b/Backend/alembic/versions/__pycache__/fe519abcefe7_add_email_verification_and_enhance_user_.cpython-312.pyc new file mode 100644 index 00000000..9e18cef2 Binary files /dev/null and b/Backend/alembic/versions/__pycache__/fe519abcefe7_add_email_verification_and_enhance_user_.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc b/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc index e4d73342..383b44bd 100644 Binary files a/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/ff515d77abbe_add_more_luxury_sections_to_page_content.cpython-312.pyc differ diff --git a/Backend/alembic/versions/__pycache__/fff4b67466b3_add_account_lockout_fields_to_users.cpython-312.pyc b/Backend/alembic/versions/__pycache__/fff4b67466b3_add_account_lockout_fields_to_users.cpython-312.pyc index bcefd045..aa0b76a0 100644 Binary files a/Backend/alembic/versions/__pycache__/fff4b67466b3_add_account_lockout_fields_to_users.cpython-312.pyc and b/Backend/alembic/versions/__pycache__/fff4b67466b3_add_account_lockout_fields_to_users.cpython-312.pyc differ diff --git a/Backend/alembic/versions/d709b14aa24a_create_gdpr_requests_table.py b/Backend/alembic/versions/d709b14aa24a_create_gdpr_requests_table.py new file mode 100644 index 00000000..85157c29 --- /dev/null +++ b/Backend/alembic/versions/d709b14aa24a_create_gdpr_requests_table.py @@ -0,0 +1,114 @@ +"""create_gdpr_requests_table + +Revision ID: d709b14aa24a +Revises: b1c4d7c154ec +Create Date: 2025-12-06 01:10:15.233886 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + + +# revision identifiers, used by Alembic. +revision = 'd709b14aa24a' +down_revision = 'b1c4d7c154ec' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Check if table exists before creating + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + if 'gdpr_requests' not in tables: + # Create GDPR requests table + op.create_table( + 'gdpr_requests', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('request_type', sa.Enum('data_export', 'data_deletion', 'data_rectification', 'consent_withdrawal', name='gdprrequesttype'), nullable=False), + sa.Column('status', sa.Enum('pending', 'processing', 'completed', 'rejected', 'cancelled', name='gdprrequeststatus'), nullable=False, server_default='pending'), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('user_email', sa.String(length=255), nullable=False), + sa.Column('is_anonymous', sa.Boolean(), nullable=False, server_default='0'), + sa.Column('request_data', sa.JSON(), nullable=True), + sa.Column('verification_token', sa.String(length=255), nullable=True), + sa.Column('verified_at', sa.DateTime(), nullable=True), + sa.Column('processed_by', sa.Integer(), nullable=True), + sa.Column('processed_at', sa.DateTime(), nullable=True), + sa.Column('processing_notes', sa.Text(), nullable=True), + sa.Column('export_file_path', sa.String(length=500), nullable=True), + sa.Column('deletion_log', sa.JSON(), nullable=True), + sa.Column('ip_address', sa.String(length=45), nullable=True), + sa.Column('user_agent', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')), + sa.Column('expires_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['processed_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + # Create indexes + op.create_index(op.f('ix_gdpr_requests_id'), 'gdpr_requests', ['id'], unique=False) + op.create_index(op.f('ix_gdpr_requests_request_type'), 'gdpr_requests', ['request_type'], unique=False) + op.create_index(op.f('ix_gdpr_requests_status'), 'gdpr_requests', ['status'], unique=False) + op.create_index(op.f('ix_gdpr_requests_user_id'), 'gdpr_requests', ['user_id'], unique=False) + op.create_index(op.f('ix_gdpr_requests_is_anonymous'), 'gdpr_requests', ['is_anonymous'], unique=False) + op.create_index(op.f('ix_gdpr_requests_created_at'), 'gdpr_requests', ['created_at'], unique=False) + op.create_index(op.f('ix_gdpr_requests_verification_token'), 'gdpr_requests', ['verification_token'], unique=True) + else: + # Table already exists, ensure it has the is_anonymous column if missing + columns = [col['name'] for col in inspector.get_columns('gdpr_requests')] + if 'is_anonymous' not in columns: + op.add_column('gdpr_requests', sa.Column('is_anonymous', sa.Boolean(), nullable=False, server_default='0')) + op.create_index(op.f('ix_gdpr_requests_is_anonymous'), 'gdpr_requests', ['is_anonymous'], unique=False) + + # Ensure user_id is nullable + # Note: This might fail if there are existing non-null values, but for a new table it should be fine + try: + op.alter_column('gdpr_requests', 'user_id', existing_type=sa.Integer(), nullable=True) + except Exception: + pass # Column might already be nullable + + +def downgrade() -> None: + # Drop indexes + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + if 'gdpr_requests' in tables: + try: + op.drop_index(op.f('ix_gdpr_requests_verification_token'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_created_at'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_is_anonymous'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_user_id'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_status'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_request_type'), table_name='gdpr_requests') + except Exception: + pass + try: + op.drop_index(op.f('ix_gdpr_requests_id'), table_name='gdpr_requests') + except Exception: + pass + + # Drop table + op.drop_table('gdpr_requests') diff --git a/Backend/alembic/versions/fe519abcefe7_add_email_verification_and_enhance_user_.py b/Backend/alembic/versions/fe519abcefe7_add_email_verification_and_enhance_user_.py new file mode 100644 index 00000000..ad1cbbd3 --- /dev/null +++ b/Backend/alembic/versions/fe519abcefe7_add_email_verification_and_enhance_user_.py @@ -0,0 +1,110 @@ +"""add_email_verification_and_enhance_user_security + +Revision ID: fe519abcefe7 +Revises: 54e4d0db31a3 +Create Date: 2025-12-06 01:53:10.797944 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + + +# revision identifiers, used by Alembic. +revision = 'fe519abcefe7' +down_revision = '54e4d0db31a3' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + # Add email_verified column to users table + if 'users' in tables: + columns = [col['name'] for col in inspector.get_columns('users')] + if 'email_verified' not in columns: + # Use MySQL-compatible boolean default + op.add_column('users', sa.Column('email_verified', sa.Boolean(), nullable=False, server_default=sa.text('0'))) + # Check if index exists before creating + try: + op.create_index('ix_users_email_verified', 'users', ['email_verified'], unique=False) + except Exception: + pass # Index might already exist + + # Add password expiry fields + if 'password_changed_at' not in columns: + op.add_column('users', sa.Column('password_changed_at', sa.DateTime(), nullable=True)) + # Check if index exists before creating + try: + op.create_index('ix_users_password_changed_at', 'users', ['password_changed_at'], unique=False) + except Exception: + pass # Index might already exist + + # Create password_history table + if 'password_history' not in tables: + op.create_table( + 'password_history', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('password_hash', sa.String(length=255), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_password_history_id'), 'password_history', ['id'], unique=False) + op.create_index(op.f('ix_password_history_user_id'), 'password_history', ['user_id'], unique=False) + op.create_index(op.f('ix_password_history_created_at'), 'password_history', ['created_at'], unique=False) + + # Create email_verification_tokens table + if 'email_verification_tokens' not in tables: + op.create_table( + 'email_verification_tokens', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('token', sa.String(length=255), nullable=False), + sa.Column('email', sa.String(length=100), nullable=False), + sa.Column('expires_at', sa.DateTime(), nullable=False), + sa.Column('used', sa.Boolean(), nullable=False, server_default=sa.text('0')), + sa.Column('created_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP')), + sa.Column('updated_at', sa.DateTime(), nullable=False, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP')), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_email_verification_tokens_id'), 'email_verification_tokens', ['id'], unique=False) + op.create_index(op.f('ix_email_verification_tokens_user_id'), 'email_verification_tokens', ['user_id'], unique=False) + op.create_index(op.f('ix_email_verification_tokens_token'), 'email_verification_tokens', ['token'], unique=True) + op.create_index(op.f('ix_email_verification_tokens_expires_at'), 'email_verification_tokens', ['expires_at'], unique=False) + + +def downgrade() -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + tables = inspector.get_table_names() + + # Drop email_verification_tokens table + if 'email_verification_tokens' in tables: + op.drop_index(op.f('ix_email_verification_tokens_expires_at'), table_name='email_verification_tokens') + op.drop_index(op.f('ix_email_verification_tokens_token'), table_name='email_verification_tokens') + op.drop_index(op.f('ix_email_verification_tokens_user_id'), table_name='email_verification_tokens') + op.drop_index(op.f('ix_email_verification_tokens_id'), table_name='email_verification_tokens') + op.drop_table('email_verification_tokens') + + # Remove password_history table + if 'password_history' in tables: + op.drop_index(op.f('ix_password_history_created_at'), table_name='password_history') + op.drop_index(op.f('ix_password_history_user_id'), table_name='password_history') + op.drop_index(op.f('ix_password_history_id'), table_name='password_history') + op.drop_table('password_history') + + # Remove email_verified and password_changed_at columns from users table + if 'users' in tables: + columns = [col['name'] for col in inspector.get_columns('users')] + if 'password_changed_at' in columns: + op.drop_index('ix_users_password_changed_at', table_name='users') + op.drop_column('users', 'password_changed_at') + if 'email_verified' in columns: + op.drop_index('ix_users_email_verified', table_name='users') + op.drop_column('users', 'email_verified') diff --git a/Backend/seeders/__init__.py b/Backend/seeders/__init__.py new file mode 100644 index 00000000..edad87bd --- /dev/null +++ b/Backend/seeders/__init__.py @@ -0,0 +1,2 @@ +# Seeders package + diff --git a/Backend/seeders/__pycache__/__init__.cpython-312.pyc b/Backend/seeders/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4b27ce4f Binary files /dev/null and b/Backend/seeders/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/seeders/__pycache__/banner_seeder.cpython-312.pyc b/Backend/seeders/__pycache__/banner_seeder.cpython-312.pyc new file mode 100644 index 00000000..6dfb035f Binary files /dev/null and b/Backend/seeders/__pycache__/banner_seeder.cpython-312.pyc differ diff --git a/Backend/seeders/__pycache__/homepage_seeder.cpython-312.pyc b/Backend/seeders/__pycache__/homepage_seeder.cpython-312.pyc new file mode 100644 index 00000000..02fa3f6e Binary files /dev/null and b/Backend/seeders/__pycache__/homepage_seeder.cpython-312.pyc differ diff --git a/Backend/seeders/__pycache__/user_seeder.cpython-312.pyc b/Backend/seeders/__pycache__/user_seeder.cpython-312.pyc new file mode 100644 index 00000000..5863be85 Binary files /dev/null and b/Backend/seeders/__pycache__/user_seeder.cpython-312.pyc differ diff --git a/Backend/seeders/about_seeder.py b/Backend/seeders/about_seeder.py new file mode 100644 index 00000000..f3c5765f --- /dev/null +++ b/Backend/seeders/about_seeder.py @@ -0,0 +1,318 @@ +""" +About Page Seeder +Seeds the database with comprehensive about page content +All images are from Unsplash (free stock photos) +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_about_page_data(): + """Generate comprehensive about page data with Unsplash images""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.ABOUT, + 'title': 'About Our Hotel', + 'subtitle': 'A Legacy of Luxury and Exceptional Service', + 'description': 'Discover our rich heritage spanning three decades. We have been crafting exceptional experiences for discerning travelers worldwide, blending timeless elegance with modern amenities.', + 'content': '

For over three decades, we have been at the forefront of luxury hospitality, creating unforgettable experiences for our guests. Our commitment to excellence, attention to detail, and passion for service has made us a destination of choice for travelers seeking the finest in accommodation, dining, and personalized service.

', + 'meta_title': 'About Us | Luxury Hotel & Resort - Our Story, Mission & Vision', + 'meta_description': 'Learn about our luxury hotel\'s rich heritage, mission, vision, and commitment to exceptional hospitality. Discover our story spanning three decades of excellence.', + 'meta_keywords': 'about us, hotel history, luxury hospitality, hotel mission, hotel vision, hotel story, luxury hotel', + 'og_title': 'About Our Luxury Hotel - A Legacy of Excellence', + 'og_description': 'Discover our rich heritage spanning three decades. We have been crafting exceptional experiences for discerning travelers worldwide.', + 'og_image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/about', + 'hero_title': 'Our Story', + 'hero_subtitle': 'Three Decades of Excellence in Luxury Hospitality', + 'hero_image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1920&h=1080&fit=crop', + 'hero_video_url': None, + 'hero_video_poster': None, + 'about_hero_image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1400&h=900&fit=crop', + 'story_content': '

Founded in 1993, our hotel began as a vision to create a sanctuary of luxury and sophistication in the heart of the city. What started as a small boutique property has grown into an internationally recognized destination, consistently ranked among the world\'s finest hotels.

Over the years, we have welcomed countless guests, from world leaders and celebrities to families celebrating special moments. Each guest has contributed to our story, and we are honored to have been part of their journeys.

Our commitment to excellence has never wavered. We continuously invest in our facilities, train our staff to the highest standards, and innovate to exceed expectations. Today, we stand as a testament to what is possible when passion meets dedication.

', + 'mission': '

Our mission is to provide unparalleled luxury experiences that exceed expectations. We are committed to creating memorable moments for every guest through exceptional service, world-class amenities, and attention to every detail. We strive to be the benchmark of excellence in hospitality, where every interaction reflects our dedication to perfection.

', + 'vision': '

Our vision is to be the world\'s most respected luxury hotel brand, recognized for our unwavering commitment to excellence, innovation, and sustainable luxury. We envision a future where our guests feel truly at home, where our team members are proud ambassadors of our values, and where our community benefits from our presence and commitment to responsible hospitality.

', + 'values': json.dumps([ + { + 'icon': 'heart', + 'title': 'Excellence', + 'description': 'We strive for perfection in every detail, ensuring your experience exceeds expectations. Excellence is not a goal but a standard we maintain in everything we do.' + }, + { + 'icon': 'users', + 'title': 'Hospitality', + 'description': 'Our dedicated team provides warm, personalized service to make you feel at home. We believe true hospitality comes from the heart and is reflected in every interaction.' + }, + { + 'icon': 'leaf', + 'title': 'Sustainability', + 'description': 'Committed to eco-friendly practices for a better future and a delightful stay. We balance luxury with responsibility, ensuring our operations protect the environment for future generations.' + }, + { + 'icon': 'award', + 'title': 'Quality', + 'description': 'We maintain the highest standards in service, amenities, and guest satisfaction. Quality is embedded in our culture and evident in every aspect of your stay.' + }, + { + 'icon': 'handshake', + 'title': 'Integrity', + 'description': 'We conduct our business with honesty, transparency, and ethical practices. Trust is the foundation of our relationships with guests, partners, and our community.' + }, + { + 'icon': 'lightbulb', + 'title': 'Innovation', + 'description': 'We embrace new technologies and creative solutions to enhance your experience. Innovation drives us to continuously improve and stay ahead of industry trends.' + } + ]), + 'team': json.dumps([ + { + 'name': 'Sarah Johnson', + 'position': 'General Manager', + 'bio': 'With over 20 years of experience in luxury hospitality, Sarah leads our team with passion and dedication. Her commitment to excellence has been instrumental in our success.', + 'image': 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=400&fit=crop&crop=face', + 'social': { + 'linkedin': 'https://linkedin.com/in/sarahjohnson', + 'twitter': 'https://twitter.com/sarahjohnson' + } + }, + { + 'name': 'Michael Chen', + 'position': 'Executive Chef', + 'bio': 'Award-winning chef with a passion for creating culinary masterpieces. Michael brings innovative flavors and techniques to our restaurants, delighting guests with every dish.', + 'image': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face', + 'social': { + 'linkedin': 'https://linkedin.com/in/michaelchen', + 'instagram': 'https://instagram.com/chefmichael' + } + }, + { + 'name': 'Emily Rodriguez', + 'position': 'Director of Guest Services', + 'bio': 'Emily ensures every guest receives personalized attention and exceptional service. Her warm personality and attention to detail make her a favorite among our guests.', + 'image': 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop&crop=face', + 'social': { + 'linkedin': 'https://linkedin.com/in/emilyrodriguez' + } + }, + { + 'name': 'David Thompson', + 'position': 'Spa Director', + 'bio': 'With expertise in wellness and holistic healing, David has created a world-class spa experience. His innovative treatments and serene environment provide guests with ultimate relaxation.', + 'image': 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=400&fit=crop&crop=face', + 'social': { + 'linkedin': 'https://linkedin.com/in/davidthompson' + } + } + ]), + 'timeline': json.dumps([ + { + 'year': '1993', + 'title': 'Foundation', + 'description': 'Hotel founded with a vision to create a luxury destination. Opened with 50 rooms and a commitment to exceptional service.', + 'image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=800&h=600&fit=crop' + }, + { + 'year': '2000', + 'title': 'First Expansion', + 'description': 'Expanded to 150 rooms and added our first fine dining restaurant. Received our first five-star rating from international travel guides.', + 'image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&h=600&fit=crop' + }, + { + 'year': '2008', + 'title': 'Spa & Wellness Center', + 'description': 'Opened our world-class spa and wellness center, introducing holistic wellness programs and luxury treatments.', + 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=800&h=600&fit=crop' + }, + { + 'year': '2015', + 'title': 'Major Renovation', + 'description': 'Completed a comprehensive renovation, modernizing all rooms and public spaces while preserving our classic elegance.', + 'image': 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=800&h=600&fit=crop' + }, + { + 'year': '2020', + 'title': 'Sustainability Initiative', + 'description': 'Launched our comprehensive sustainability program, achieving carbon neutrality and implementing eco-friendly practices throughout the property.', + 'image': 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&h=600&fit=crop' + }, + { + 'year': '2023', + 'title': 'Award Recognition', + 'description': 'Received "Best Luxury Hotel" award and maintained our five-star rating. Celebrated 30 years of excellence in hospitality.', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=800&h=600&fit=crop' + } + ]), + 'achievements': json.dumps([ + { + 'icon': 'trophy', + 'title': 'Best Luxury Hotel 2023', + 'description': 'Awarded by International Hospitality Association', + 'year': '2023', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=400&h=400&fit=crop' + }, + { + 'icon': 'star', + 'title': 'Five-Star Rating', + 'description': 'Consistently rated five-star by global travel guides', + 'year': '2020-2023', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=400&h=400&fit=crop' + }, + { + 'icon': 'medal', + 'title': 'Excellence in Service', + 'description': 'Recognized for outstanding customer service and guest satisfaction', + 'year': '2022', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=400&h=400&fit=crop' + }, + { + 'icon': 'leaf', + 'title': 'Green Hotel Certification', + 'description': 'Certified for sustainable practices and environmental responsibility', + 'year': '2021', + 'image': 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=400&h=400&fit=crop' + }, + { + 'icon': 'users', + 'title': 'Guest Choice Award', + 'description': 'Voted favorite luxury hotel by guests worldwide', + 'year': '2023', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=400&h=400&fit=crop' + }, + { + 'icon': 'award', + 'title': 'Culinary Excellence', + 'description': 'Restaurant awarded Michelin star for exceptional cuisine', + 'year': '2022', + 'image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=400&h=400&fit=crop' + } + ]), + 'stats': json.dumps([ + {'number': '50000+', 'label': 'Happy Guests', 'icon': 'users'}, + {'number': '30+', 'label': 'Years of Excellence', 'icon': 'calendar'}, + {'number': '150+', 'label': 'Awards Won', 'icon': 'award'}, + {'number': '98%', 'label': 'Guest Satisfaction', 'icon': 'star'}, + {'number': '200+', 'label': 'Team Members', 'icon': 'users'}, + {'number': '115', 'label': 'Luxury Rooms', 'icon': 'home'} + ]), + 'stats_section_title': 'Our Achievements in Numbers', + 'stats_section_subtitle': 'A legacy built on excellence and guest satisfaction', + 'testimonials': json.dumps([ + { + 'name': 'James Wilson', + 'title': 'VIP Guest', + 'quote': 'The level of luxury and attention to detail is simply extraordinary. This hotel understands what true hospitality means.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Sophia Lee', + 'title': 'Travel Blogger', + 'quote': 'Every aspect of my stay was flawless. The service, the amenities, the ambiance - truly a five-star experience.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'David Chen', + 'title': 'CEO, Global Corp', + 'quote': 'An impeccable hotel with outstanding service. This is what luxury hospitality should be - perfect in every way.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Maria Garcia', + 'title': 'Frequent Guest', + 'quote': 'I\'ve stayed at many luxury hotels, but this one stands out. The personal attention and genuine care for guests is remarkable.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=200&h=200&fit=crop&crop=face' + } + ]), + 'testimonials_section_title': 'What Our Guests Say', + 'testimonials_section_subtitle': 'Stories from our valued guests', + 'gallery_images': json.dumps([ + 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1200&h=800&fit=crop' + ]), + 'gallery_section_title': 'Our Photo Gallery', + 'gallery_section_subtitle': 'A glimpse into our world of luxury', + 'contact_info': json.dumps({ + 'phone': '+1 (555) 123-4567', + 'email': 'info@luxuryhotel.com', + 'address': '123 Luxury Avenue, Premium City, PC 12345' + }), + 'social_links': json.dumps({ + 'facebook': 'https://facebook.com/luxuryhotel', + 'twitter': 'https://twitter.com/luxuryhotel', + 'instagram': 'https://instagram.com/luxuryhotel', + 'linkedin': 'https://linkedin.com/company/luxuryhotel', + 'youtube': 'https://youtube.com/luxuryhotel' + }), + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_about_page(db: Session): + """Seed about page content into the database""" + try: + about_data = get_about_page_data() + + # Check if about page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.ABOUT).first() + + if existing_content: + logger.info('Updating existing about page content...') + for key, value in about_data.items(): + if key not in ['id', 'page_type', 'created_at']: # Don't update primary key or creation date + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new about page content...') + about_page = PageContent(**about_data) + db.add(about_page) + + db.commit() + logger.info('About page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding about page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_about_page(db) + except Exception as e: + logger.error(f'Failed to seed about page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/accessibility_seeder.py b/Backend/seeders/accessibility_seeder.py new file mode 100644 index 00000000..fcc27fb1 --- /dev/null +++ b/Backend/seeders/accessibility_seeder.py @@ -0,0 +1,200 @@ +""" +Accessibility Page Seeder +Seeds the database with comprehensive accessibility information +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_accessibility_page_data(): + """Generate comprehensive accessibility page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.ACCESSIBILITY, + 'title': 'Accessibility', + 'subtitle': 'Committed to Accessibility for All', + 'description': 'We are committed to providing accessible facilities and services for all our guests. Learn about our accessibility features and accommodations.', + 'content': """ +

Our Commitment to Accessibility

+

At Luxury Hotel & Resort, we are committed to ensuring that our facilities and services are accessible to all guests, including those with disabilities. We strive to comply with the Americans with Disabilities Act (ADA) and continuously work to improve accessibility throughout our property.

+ +

Accessible Accommodations

+

Accessible Guest Rooms

+

We offer several accessible guest rooms designed to meet ADA requirements, featuring:

+ + +

Room Features

+ + +

Public Areas & Facilities

+

Entrance & Lobby

+ + +

Dining Facilities

+ + +

Recreation & Fitness

+ + +

Meeting & Event Spaces

+ + +

Parking & Transportation

+ + +

Service Animals

+

Service animals are welcome throughout our property. We comply with all applicable laws regarding service animals and do not charge additional fees for service animals.

+ +

Assistive Devices & Services

+

Available Upon Request

+ + +

Website Accessibility

+

We are committed to making our website accessible to all users. Our website is designed to comply with WCAG 2.1 Level AA standards. If you encounter any accessibility barriers on our website, please contact us so we can address the issue.

+ +

Communication

+

Our staff is trained to communicate effectively with guests who have disabilities. We can provide information in alternative formats upon request, including:

+ + +

Accessibility Concerns & Feedback

+

We welcome feedback from our guests regarding accessibility. If you have any concerns or suggestions for improvement, please contact us:

+ + +

Reservations

+

When making a reservation, please inform us of any specific accessibility needs so we can ensure your room and services meet your requirements. Our reservations team is available 24/7 to assist you.

+ + +

Ongoing Improvements

+

We are continuously working to improve accessibility throughout our property. We regularly review and update our facilities and services to better serve all our guests. If you have suggestions for improvements, we would love to hear from you.

+ +

Last Updated: {}

+ """.format(now.strftime('%B %d, %Y')), + 'meta_title': 'Accessibility | Luxury Hotel & Resort - Accessible Facilities', + 'meta_description': 'Learn about our accessible facilities and services. We are committed to providing accessible accommodations for all guests.', + 'meta_keywords': 'accessibility, ADA, accessible hotel, wheelchair accessible, disability accommodations, accessible rooms', + 'og_title': 'Accessibility - Luxury Hotel & Resort', + 'og_description': 'Committed to accessibility for all. Learn about our accessible facilities and services.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/accessibility', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_accessibility_page(db: Session): + """Seed accessibility page content into the database""" + try: + accessibility_data = get_accessibility_page_data() + + # Check if accessibility page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.ACCESSIBILITY).first() + + if existing_content: + logger.info('Updating existing accessibility page content...') + for key, value in accessibility_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new accessibility page content...') + accessibility_page = PageContent(**accessibility_data) + db.add(accessibility_page) + + db.commit() + logger.info('Accessibility page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding accessibility page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_accessibility_page(db) + except Exception as e: + logger.error(f'Failed to seed accessibility page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/banner_seeder.py b/Backend/seeders/banner_seeder.py new file mode 100644 index 00000000..541bcb7d --- /dev/null +++ b/Backend/seeders/banner_seeder.py @@ -0,0 +1,315 @@ +""" +Banner Seeder +Seeds the database with comprehensive banners for different positions +All images are from Unsplash (free stock photos) +""" +import sys +from pathlib import Path +from datetime import datetime, timezone, timedelta + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.content.models.banner import Banner +from src.shared.config.logging_config import get_logger + +logger = get_logger(__name__) + + +def get_banner_data(): + """Generate comprehensive banner data with Unsplash images""" + + now = datetime.now(timezone.utc) + future_date = now + timedelta(days=365) # Banners active for 1 year + + banners = [ + # Home Page Banners + { + 'title': 'Welcome to Luxury', + 'description': 'Experience unparalleled elegance and world-class service at our award-winning hotel. Discover sophisticated rooms, exceptional dining, and modern amenities.', + 'image_url': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1920&h=1080&fit=crop', + 'link_url': '/rooms', + 'position': 'home', + 'display_order': 1, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Special Summer Offer', + 'description': 'Book now and save up to 30% on your summer getaway! Perfect for families, couples, and solo travelers. Limited time offer.', + 'image_url': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1920&h=1080&fit=crop', + 'link_url': '/book', + 'position': 'home', + 'display_order': 2, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Fine Dining Experience', + 'description': 'Savor exquisite cuisine at our Michelin-starred restaurants. Enjoy international flavors, local specialties, and expertly curated wine pairings.', + 'image_url': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1920&h=1080&fit=crop', + 'link_url': '/services', + 'position': 'home', + 'display_order': 3, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Luxury Spa & Wellness', + 'description': 'Rejuvenate at our world-class spa with personalized treatments, therapeutic massages, steam rooms, saunas, and yoga classes.', + 'image_url': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1920&h=1080&fit=crop', + 'link_url': '/services', + 'position': 'home', + 'display_order': 4, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Business Events & Conferences', + 'description': 'Host your corporate event in our state-of-the-art facilities. Versatile spaces for intimate meetings to large conferences with cutting-edge technology.', + 'image_url': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1920&h=1080&fit=crop', + 'link_url': '/contact', + 'position': 'home', + 'display_order': 5, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # Rooms Page Banners + { + 'title': 'Luxurious Suites', + 'description': 'Elegantly designed suites with spacious layouts, separate living areas, marble bathrooms, and private balconies with panoramic views. Smart room controls and premium amenities included.', + 'image_url': 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1920&h=1080&fit=crop', + 'link_url': '/rooms', + 'position': 'rooms', + 'display_order': 1, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Presidential Suite', + 'description': 'Ultimate luxury in our exclusive 2,000+ sq ft suite. Features grand living room, formal dining, fully equipped kitchen, private terrace, and personal butler service. Perfect for VIP guests.', + 'image_url': 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1920&h=1080&fit=crop', + 'link_url': '/rooms', + 'position': 'rooms', + 'display_order': 2, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Ocean View Rooms', + 'description': 'Breathtaking ocean views from private balconies. Spacious rooms with floor-to-ceiling windows, coastal decor, and premium furnishings. Perfect for romantic getaways.', + 'image_url': 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1920&h=1080&fit=crop', + 'link_url': '/rooms', + 'position': 'rooms', + 'display_order': 3, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # About Page Banners + { + 'title': 'Our Story', + 'description': 'Discover our rich heritage spanning three decades. Founded to redefine luxury hospitality, we\'ve grown into an internationally recognized destination with timeless elegance.', + 'image_url': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1920&h=1080&fit=crop', + 'link_url': '/about', + 'position': 'about', + 'display_order': 1, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Award-Winning Service', + 'description': 'Recognized globally for exceptional hospitality with prestigious awards including five-star ratings and "Best Luxury Hotel" honors. Our trained team delivers service beyond expectations.', + 'image_url': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=1920&h=1080&fit=crop', + 'link_url': '/about', + 'position': 'about', + 'display_order': 2, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # Contact Page Banner + { + 'title': 'Get in Touch', + 'description': 'Our friendly team is available 24/7 for reservations, inquiries, and special requests. Reach us by phone, email, or visit our front desk. Concierge assistance available.', + 'image_url': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1920&h=1080&fit=crop', + 'link_url': '/contact', + 'position': 'contact', + 'display_order': 1, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # Services Page Banners + { + 'title': 'Premium Services', + 'description': 'Enjoy personalized butler service, private airport transfers, VIP lounge access, and priority reservations. Business center, city tours, and special occasion planning available.', + 'image_url': 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=1920&h=1080&fit=crop', + 'link_url': '/services', + 'position': 'services', + 'display_order': 1, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': '24/7 Concierge Service', + 'description': 'Our dedicated concierge team is available around the clock. We assist with restaurant reservations, event tickets, transportation, exclusive experiences, and special occasions.', + 'image_url': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1920&h=1080&fit=crop', + 'link_url': '/services', + 'position': 'services', + 'display_order': 2, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # Promotional Banners (can be used across pages) + { + 'title': 'Early Bird Special', + 'description': 'Book 30 days in advance and save 20%! Perfect for travelers who plan ahead. Applies to all room types. Terms and conditions apply.', + 'image_url': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1920&h=1080&fit=crop', + 'link_url': '/book', + 'position': 'home', + 'display_order': 6, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Weekend Getaway Package', + 'description': 'All-inclusive weekend package with luxurious accommodation, daily breakfast, and full spa access. Late checkout included. Available Friday through Sunday.', + 'image_url': 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1920&h=1080&fit=crop', + 'link_url': '/book', + 'position': 'home', + 'display_order': 7, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Honeymoon Package', + 'description': 'Romantic honeymoon package includes luxurious suite, breakfast in bed, candlelit dinner with champagne, couples spa treatments, and special amenities.', + 'image_url': 'https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=1920&h=1080&fit=crop', + 'link_url': '/book', + 'position': 'home', + 'display_order': 8, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + + # Footer/General Banners + { + 'title': 'Join Our Loyalty Program', + 'description': 'Earn points with every stay. Redeem for free nights, upgrades, dining credits, and spa treatments. Multiple tier levels from Silver to Platinum. Join free today!', + 'image_url': 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=1920&h=1080&fit=crop', + 'link_url': '/loyalty', + 'position': 'home', + 'display_order': 9, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + }, + { + 'title': 'Gift Cards Available', + 'description': 'Give the gift of luxury with our hotel gift cards. Perfect for any occasion. Usable for accommodations, dining, spa, and all services. Never expire. Purchase online or at front desk.', + 'image_url': 'https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=1920&h=1080&fit=crop', + 'link_url': '/gift-cards', + 'position': 'home', + 'display_order': 10, + 'is_active': True, + 'start_date': now, + 'end_date': future_date + } + ] + + return banners + + +def seed_banners(db: Session, clear_existing: bool = False): + """Seed banners into the database""" + try: + if clear_existing: + logger.info('Clearing existing banners...') + db.query(Banner).delete() + db.commit() + + banners_data = get_banner_data() + created_count = 0 + updated_count = 0 + + for banner_data in banners_data: + # Check if banner with same title and position already exists + existing = db.query(Banner).filter( + Banner.title == banner_data['title'], + Banner.position == banner_data['position'] + ).first() + + if existing: + logger.info(f'Updating existing banner: {banner_data["title"]} ({banner_data["position"]})') + # Update existing banner + for key, value in banner_data.items(): + setattr(existing, key, value) + existing.updated_at = datetime.now(timezone.utc) + updated_count += 1 + else: + logger.info(f'Creating new banner: {banner_data["title"]} ({banner_data["position"]})') + # Create new banner + banner = Banner(**banner_data) + db.add(banner) + created_count += 1 + + db.commit() + logger.info(f'Banner seeding completed! Created: {created_count}, Updated: {updated_count}') + return created_count, updated_count + + except Exception as e: + logger.error(f'Error seeding banners: {str(e)}', exc_info=True) + db.rollback() + raise + + +def main(): + """Main function to run the seeder""" + import argparse + + parser = argparse.ArgumentParser(description='Seed banners into the database') + parser.add_argument( + '--clear', + action='store_true', + help='Clear existing banners before seeding' + ) + + args = parser.parse_args() + + logger.info('Starting banner seeder...') + + db = SessionLocal() + try: + created, updated = seed_banners(db, clear_existing=args.clear) + logger.info(f'Banner seeder completed successfully! Created: {created}, Updated: {updated}') + except Exception as e: + logger.error(f'Failed to seed banners: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/blog_seeder.py b/Backend/seeders/blog_seeder.py new file mode 100644 index 00000000..8c58fd81 --- /dev/null +++ b/Backend/seeders/blog_seeder.py @@ -0,0 +1,320 @@ +""" +Blog Seeder +Seeds the database with blog posts +All images are from Unsplash (free stock photos) +""" +import json +import sys +import re +from pathlib import Path +from datetime import datetime, timezone, timedelta + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.blog import BlogPost +from src.auth.models.user import User + +# Import all models to ensure relationships are loaded correctly +from src.models import * + +logger = get_logger(__name__) + + +def slugify(text): + """Convert text to URL-friendly slug""" + text = text.lower() + text = re.sub(r'[^\w\s-]', '', text) + text = re.sub(r'[-\s]+', '-', text) + return text.strip('-') + + +def get_blog_posts_data(author_id: int): + """Generate comprehensive blog posts data with Unsplash images""" + now = datetime.now(timezone.utc) + + posts = [ + { + 'title': '10 Tips for Planning the Perfect Luxury Hotel Stay', + 'slug': '10-tips-planning-perfect-luxury-hotel-stay', + 'excerpt': 'Discover expert tips to make your luxury hotel experience unforgettable. From booking strategies to maximizing amenities, we share insider secrets.', + 'content': '''

Planning a luxury hotel stay requires attention to detail and insider knowledge. Whether you're celebrating a special occasion or simply treating yourself, these tips will help you make the most of your experience.

+ +

1. Book in Advance

+

Luxury hotels often offer early bird discounts and better room availability when you book well in advance. Planning ahead also gives you access to special packages and upgrades.

+ +

2. Communicate Your Preferences

+

Don't hesitate to communicate your preferences when booking. Whether you prefer a high floor, specific room amenities, or have dietary restrictions, hotels are happy to accommodate.

+ +

3. Join Loyalty Programs

+

Most luxury hotels offer loyalty programs with exclusive benefits. Join before your stay to earn points, receive member rates, and enjoy perks like late checkout.

+ +

4. Explore Hotel Amenities

+

Take advantage of all the hotel has to offer - from spa treatments to fine dining. Many luxury hotels have world-class facilities that are worth experiencing.

+ +

5. Use Concierge Services

+

The concierge team is your gateway to the best local experiences. They can secure restaurant reservations, arrange transportation, and provide insider recommendations.

+ +

6. Check for Special Packages

+

Look for special packages that bundle accommodations with dining, spa, or local experiences. These often provide better value than booking separately.

+ +

7. Review Cancellation Policies

+

Understand the cancellation and modification policies before booking. Flexible rates may cost more but provide peace of mind.

+ +

8. Pack Appropriately

+

While luxury hotels provide many amenities, bringing appropriate attire for dining and activities ensures you're prepared for all experiences.

+ +

9. Arrive Early or Late

+

If possible, arrive early or late to avoid peak check-in times. This often results in better service and sometimes room upgrades if available.

+ +

10. Leave Reviews

+

Share your experience through reviews. Hotels value feedback and often reward guests who provide detailed, constructive reviews.

+ +

Remember, a luxury hotel stay is about creating memories. Take your time, enjoy every moment, and don't hesitate to ask for what will make your stay perfect.

''', + 'featured_image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=5), + 'is_published': True, + 'tags': json.dumps(['travel tips', 'luxury travel', 'hotel guide', 'vacation planning']), + 'meta_title': '10 Tips for Planning the Perfect Luxury Hotel Stay | Travel Guide', + 'meta_description': 'Expert tips for planning an unforgettable luxury hotel stay. Learn booking strategies, how to maximize amenities, and insider secrets.', + 'meta_keywords': 'luxury hotel tips, hotel booking guide, travel planning, luxury travel advice', + 'sections': None + }, + { + 'title': 'The Art of Fine Dining: A Culinary Journey at Our Hotel', + 'slug': 'art-fine-dining-culinary-journey', + 'excerpt': 'Explore our award-winning restaurants and discover the culinary philosophy behind our Michelin-starred dining experiences.', + 'content': '''

Fine dining is an art form that combines exceptional ingredients, masterful technique, and impeccable service. At our hotel, we've created culinary experiences that celebrate both tradition and innovation.

+ +

Our Culinary Philosophy

+

Our chefs believe in using the finest locally sourced ingredients, supporting sustainable practices, and creating dishes that tell a story. Each plate is a masterpiece, carefully crafted to delight all senses.

+ +

Signature Dishes

+

From our signature truffle risotto to our perfectly aged wagyu beef, every dish on our menu has been thoughtfully created. Our tasting menus offer a journey through flavors, textures, and culinary traditions.

+ +

Wine Pairing Excellence

+

Our sommeliers curate wine pairings that enhance every dish. With an extensive cellar featuring rare vintages and hidden gems, we ensure the perfect complement to your meal.

+ +

Private Dining Experiences

+

For special occasions, our private dining rooms offer intimate settings with personalized menus. Our chefs work closely with guests to create unforgettable culinary experiences.

+ +

Join us for a culinary journey that celebrates the art of fine dining and creates memories that last a lifetime.

''', + 'featured_image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=10), + 'is_published': True, + 'tags': json.dumps(['fine dining', 'culinary', 'restaurant', 'gourmet']), + 'meta_title': 'The Art of Fine Dining | Culinary Excellence', + 'meta_description': 'Discover our award-winning restaurants and Michelin-starred dining experiences. Explore our culinary philosophy and signature dishes.', + 'meta_keywords': 'fine dining, Michelin restaurant, gourmet cuisine, culinary experience', + 'sections': None + }, + { + 'title': 'Wellness and Relaxation: Your Guide to Our Spa & Wellness Center', + 'slug': 'wellness-relaxation-spa-wellness-center-guide', + 'excerpt': 'Discover our world-class spa and wellness center. Learn about our treatments, wellness programs, and how to achieve ultimate relaxation.', + 'content': '''

In today's fast-paced world, taking time for wellness and relaxation is essential. Our spa and wellness center offers a sanctuary where you can rejuvenate your mind, body, and spirit.

+ +

Our Wellness Philosophy

+

We believe in holistic wellness that addresses physical, mental, and emotional well-being. Our treatments combine traditional techniques with modern innovations to provide comprehensive care.

+ +

Signature Treatments

+

From our signature deep tissue massages to rejuvenating facials, each treatment is customized to your needs. Our expert therapists use premium products and time-tested techniques.

+ +

Wellness Programs

+

Beyond individual treatments, we offer comprehensive wellness programs including yoga classes, meditation sessions, and personalized fitness consultations.

+ +

The Spa Environment

+

Our spa facilities include steam rooms, saunas, relaxation lounges, and private treatment rooms. Every detail is designed to create a serene, peaceful atmosphere.

+ +

Couples Experiences

+

Share the relaxation experience with our couples treatments. Perfect for romantic getaways or celebrating special moments together.

+ +

Whether you're seeking stress relief, muscle recovery, or simply a moment of tranquility, our spa and wellness center provides the perfect escape.

''', + 'featured_image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=15), + 'is_published': True, + 'tags': json.dumps(['spa', 'wellness', 'relaxation', 'self-care']), + 'meta_title': 'Wellness and Relaxation Guide | Spa & Wellness Center', + 'meta_description': 'Discover our world-class spa and wellness center. Learn about treatments, wellness programs, and how to achieve ultimate relaxation.', + 'meta_keywords': 'spa, wellness, relaxation, massage, hotel spa', + 'sections': None + }, + { + 'title': 'Sustainable Luxury: Our Commitment to Environmental Responsibility', + 'slug': 'sustainable-luxury-environmental-responsibility', + 'excerpt': 'Learn about our sustainability initiatives and how we balance luxury with environmental responsibility. Discover our green practices and commitment to the planet.', + 'content': '''

Luxury and sustainability are not mutually exclusive. At our hotel, we've proven that you can enjoy world-class amenities while protecting the environment for future generations.

+ +

Our Sustainability Mission

+

We're committed to reducing our environmental footprint through innovative practices, renewable energy, and responsible sourcing. Our goal is carbon neutrality while maintaining the highest standards of luxury.

+ +

Green Building Practices

+

Our facilities incorporate energy-efficient systems, water conservation measures, and sustainable materials. We continuously invest in technologies that reduce our environmental impact.

+ +

Local and Sustainable Sourcing

+

We prioritize local suppliers and sustainable ingredients in our restaurants. This not only reduces our carbon footprint but also supports local communities and ensures the freshest quality.

+ +

Waste Reduction

+

Through comprehensive recycling programs, composting, and waste reduction initiatives, we've significantly decreased our waste output. Single-use plastics have been eliminated throughout the property.

+ +

Guest Participation

+

We invite guests to join us in our sustainability efforts through optional programs like towel reuse, energy conservation, and supporting local conservation projects.

+ +

Together, we can enjoy luxury experiences while protecting our planet. Every small action contributes to a more sustainable future.

''', + 'featured_image': 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=20), + 'is_published': True, + 'tags': json.dumps(['sustainability', 'environment', 'green hotel', 'eco-friendly']), + 'meta_title': 'Sustainable Luxury | Environmental Responsibility', + 'meta_description': 'Learn about our sustainability initiatives and how we balance luxury with environmental responsibility. Discover our green practices.', + 'meta_keywords': 'sustainable luxury, green hotel, environmental responsibility, eco-friendly', + 'sections': None + }, + { + 'title': 'Celebrating 30 Years of Excellence in Hospitality', + 'slug': 'celebrating-30-years-excellence-hospitality', + 'excerpt': 'Join us as we celebrate three decades of providing exceptional hospitality. Learn about our journey, milestones, and vision for the future.', + 'content': '''

This year marks a significant milestone - 30 years of excellence in hospitality. From our humble beginnings to becoming an internationally recognized destination, our journey has been remarkable.

+ +

Our Beginnings

+

Founded in 1993, we started with a simple vision: to create a sanctuary of luxury and sophistication. What began as a small boutique property has grown into a world-class destination.

+ +

Key Milestones

+

Over three decades, we've achieved numerous milestones - from our first five-star rating to receiving international awards. Each achievement represents our commitment to excellence.

+ +

Our Team

+

None of this would be possible without our dedicated team. Their passion, professionalism, and commitment to service excellence have been the foundation of our success.

+ +

Looking Forward

+

As we celebrate this milestone, we're also looking to the future. We continue to innovate, invest in our facilities, and enhance our services to exceed guest expectations.

+ +

Thank You

+

To all our guests, partners, and team members - thank you for being part of our journey. Here's to the next 30 years of creating unforgettable experiences.

+ +

Join us in celebrating this special anniversary with special packages and events throughout the year.

''', + 'featured_image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=25), + 'is_published': True, + 'tags': json.dumps(['anniversary', 'hotel history', 'milestone', 'celebration']), + 'meta_title': 'Celebrating 30 Years of Excellence | Hotel Anniversary', + 'meta_description': 'Join us as we celebrate three decades of providing exceptional hospitality. Learn about our journey, milestones, and vision for the future.', + 'meta_keywords': 'hotel anniversary, 30 years, hospitality excellence, milestone', + 'sections': None + }, + { + 'title': 'The Ultimate Business Traveler\'s Guide to Our Hotel', + 'slug': 'ultimate-business-traveler-guide', + 'excerpt': 'Discover how our hotel caters to business travelers with state-of-the-art facilities, convenient services, and amenities designed for productivity and comfort.', + 'content': '''

Business travel doesn't have to mean sacrificing comfort or productivity. Our hotel is designed specifically to meet the needs of modern business travelers.

+ +

Business Facilities

+

Our fully equipped business center provides everything you need - from high-speed internet to meeting rooms with state-of-the-art AV equipment. Work seamlessly from anywhere in the hotel.

+ +

Convenient Services

+

Express check-in and checkout, 24/7 concierge service, and flexible meeting arrangements ensure your business needs are met efficiently. We understand that time is valuable.

+ +

Comfortable Accommodations

+

Our rooms feature dedicated workspaces, ergonomic furniture, and premium amenities. After a long day of meetings, relax in comfort and recharge for tomorrow.

+ +

Networking Opportunities

+

Our restaurants and lounges provide perfect settings for business networking. Whether hosting clients or connecting with colleagues, we provide the ideal atmosphere.

+ +

Wellness for Business Travelers

+

Maintain your wellness routine with our fitness center, spa services, and healthy dining options. We help you stay balanced even when traveling for business.

+ +

Experience business travel redefined - where productivity meets luxury, and comfort enhances performance.

''', + 'featured_image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop', + 'author_id': author_id, + 'published_at': now - timedelta(days=30), + 'is_published': True, + 'tags': json.dumps(['business travel', 'corporate', 'productivity', 'meetings']), + 'meta_title': 'Business Traveler\'s Guide | Corporate Accommodation', + 'meta_description': 'Discover how our hotel caters to business travelers with state-of-the-art facilities, convenient services, and productivity-focused amenities.', + 'meta_keywords': 'business travel, corporate hotel, business facilities, meeting rooms', + 'sections': None + } + ] + + return posts + + +def seed_blog_posts(db: Session, clear_existing: bool = False): + """Seed blog posts into the database""" + try: + # Get admin user as author + admin_user = db.query(User).filter(User.email == 'admin@hotel.com').first() + if not admin_user: + logger.error('Admin user not found. Please run user seeder first.') + raise ValueError('Admin user not found. Please run user seeder first.') + + if clear_existing: + logger.info('Clearing existing blog posts...') + db.query(BlogPost).delete() + db.commit() + logger.info('Existing blog posts cleared.') + + posts_data = get_blog_posts_data(admin_user.id) + now = datetime.now(timezone.utc) + + created_count = 0 + updated_count = 0 + + for post_data in posts_data: + # Generate slug if not provided + if not post_data.get('slug'): + post_data['slug'] = slugify(post_data['title']) + + existing = db.query(BlogPost).filter(BlogPost.slug == post_data['slug']).first() + + if existing: + logger.debug(f"Blog post '{post_data['title']}' already exists. Updating...") + for key, value in post_data.items(): + setattr(existing, key, value) + existing.updated_at = now + updated_count += 1 + else: + logger.debug(f"Creating blog post: {post_data['title']}") + post = BlogPost(**post_data) + db.add(post) + created_count += 1 + + db.commit() + logger.info(f'Successfully seeded blog posts! Created: {created_count}, Updated: {updated_count}, Total: {len(posts_data)}') + except Exception as e: + db.rollback() + logger.error(f'Error seeding blog posts: {str(e)}', exc_info=True) + raise + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Seed blog posts') + parser.add_argument( + '--clear', + action='store_true', + help='Clear existing blog posts before seeding' + ) + args = parser.parse_args() + + db = SessionLocal() + try: + seed_blog_posts(db, clear_existing=args.clear) + except Exception as e: + logger.error(f'Failed to seed blog posts: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/cancellation_seeder.py b/Backend/seeders/cancellation_seeder.py new file mode 100644 index 00000000..53fefb04 --- /dev/null +++ b/Backend/seeders/cancellation_seeder.py @@ -0,0 +1,221 @@ +""" +Cancellation Policy Page Seeder +Seeds the database with comprehensive cancellation policy content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_cancellation_page_data(): + """Generate comprehensive cancellation policy page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.CANCELLATION, + 'title': 'Cancellation Policy', + 'subtitle': 'Reservation Cancellation Terms and Conditions', + 'description': 'Learn about our cancellation policy, including cancellation deadlines, fees, and procedures for different rate types.', + 'content': """ +

1. Overview

+

This Cancellation Policy outlines the terms and conditions for cancelling reservations at Luxury Hotel & Resort. Cancellation terms vary by rate type and are clearly stated at the time of booking. Please review this policy carefully before making a reservation.

+ +

2. Standard Cancellation Policy

+

2.1 Flexible Rate

+

For reservations made with our Flexible Rate:

+ + +

2.2 Non-Refundable Rate

+

For reservations made with our Non-Refundable Rate:

+ + +

2.3 Advance Purchase Rate

+

For Advance Purchase rates:

+ + +

3. Special Packages and Promotions

+

Cancellation terms for special packages, promotions, and seasonal offers may differ from standard rates. Specific cancellation terms will be clearly stated at the time of booking and in your confirmation email.

+ +

4. Group Bookings

+

For group bookings (typically 10 or more rooms):

+ + +

5. Event and Meeting Bookings

+

Cancellation policies for event and meeting space bookings are detailed in your event contract. Generally:

+ + +

6. How to Cancel

+

6.1 Online Cancellation

+

You can cancel your reservation online through:

+ + +

6.2 Phone Cancellation

+

Call our reservations team:

+ + +

6.3 Email Cancellation

+

Send an email to:

+ + +

7. Cancellation Confirmation

+

Upon successful cancellation, you will receive a cancellation confirmation email. Please retain this confirmation for your records. If you do not receive a confirmation, please contact us to verify that your cancellation was processed.

+ +

8. Refunds

+

Refunds for eligible cancellations will be processed to the original payment method within 5-10 business days. The time it takes for the refund to appear in your account depends on your financial institution. For more information, please refer to our Refund Policy.

+ +

9. Modifications vs. Cancellations

+

9.1 Date Changes

+

Modifying your reservation dates may be treated as a cancellation and rebooking, subject to availability and current rates. Please contact us to discuss modification options.

+ +

9.2 Room Type Changes

+

Changes to room type are subject to availability and rate differences. Additional charges may apply if the new room type has a higher rate.

+ +

10. Force Majeure

+

In cases of force majeure events (natural disasters, pandemics, government restrictions, travel bans, etc.), we will work with you to provide flexible cancellation options, including:

+ +

Each situation is evaluated individually. Please contact us as soon as possible if you are affected by a force majeure event.

+ +

11. Third-Party Bookings

+

If you made your reservation through a third-party booking site (such as Expedia, Booking.com, etc.), you must cancel through that third party according to their cancellation policies. We cannot directly cancel third-party bookings.

+ +

12. No-Show Policy

+

If you do not arrive on your scheduled check-in date and have not cancelled your reservation:

+ + +

13. Early Departure

+

If you check out earlier than your scheduled departure date:

+ + +

14. Special Circumstances

+

We understand that unexpected circumstances may arise. In cases of medical emergencies, family emergencies, or other extenuating circumstances, please contact us immediately. We will review your situation and may offer flexible cancellation options on a case-by-case basis.

+ +

15. Contact Information

+

For cancellation requests or questions about our cancellation policy, please contact us:

+ + +

16. Policy Updates

+

We reserve the right to update this Cancellation Policy at any time. Changes will be posted on this page with an updated "Last Updated" date. The cancellation terms that apply to your reservation are those in effect at the time you made your booking.

+ +

Last Updated: {}

+ """.format(now.strftime('%B %d, %Y')), + 'meta_title': 'Cancellation Policy | Luxury Hotel & Resort', + 'meta_description': 'Learn about our cancellation policy, including cancellation deadlines, fees, and procedures for different rate types.', + 'meta_keywords': 'cancellation policy, hotel cancellation, booking cancellation, cancel reservation, cancellation terms', + 'og_title': 'Cancellation Policy - Luxury Hotel & Resort', + 'og_description': 'Reservation cancellation terms and conditions. Learn how to cancel your booking and applicable fees.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/cancellation', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_cancellation_page(db: Session): + """Seed cancellation policy page content into the database""" + try: + cancellation_data = get_cancellation_page_data() + + # Check if cancellation page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.CANCELLATION).first() + + if existing_content: + logger.info('Updating existing cancellation policy page content...') + for key, value in cancellation_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new cancellation policy page content...') + cancellation_page = PageContent(**cancellation_data) + db.add(cancellation_page) + + db.commit() + logger.info('Cancellation policy page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding cancellation policy page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_cancellation_page(db) + except Exception as e: + logger.error(f'Failed to seed cancellation policy page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/contact_seeder.py b/Backend/seeders/contact_seeder.py new file mode 100644 index 00000000..15bb13bb --- /dev/null +++ b/Backend/seeders/contact_seeder.py @@ -0,0 +1,167 @@ +""" +Contact Page Seeder +Seeds the database with comprehensive contact page content +All images are from Unsplash (free stock photos) +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_contact_page_data(): + """Generate comprehensive contact page data with Unsplash images""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.CONTACT, + 'title': 'Contact Us', + 'subtitle': 'We\'re Here to Help - Get in Touch', + 'description': 'Have questions or need assistance? Our friendly team is available 24/7 to help with reservations, inquiries, and special requests. Reach out to us through any of the methods below.', + 'content': '

We value your feedback and are here to assist you with any questions or requests. Whether you\'re planning a stay, have a question about our services, or need assistance during your visit, our team is ready to help.

', + 'meta_title': 'Contact Us | Luxury Hotel & Resort - Get in Touch', + 'meta_description': 'Contact our luxury hotel for reservations, inquiries, or assistance. Our friendly team is available 24/7. Phone, email, and location information.', + 'meta_keywords': 'contact us, hotel contact, reservations, customer service, hotel phone number, hotel email', + 'og_title': 'Contact Us - Luxury Hotel & Resort', + 'og_description': 'Have questions or need assistance? Our friendly team is available 24/7 to help with reservations, inquiries, and special requests.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/contact', + 'hero_title': 'Get in Touch', + 'hero_subtitle': 'We\'re Here to Help You', + 'hero_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1920&h=1080&fit=crop', + 'hero_video_url': None, + 'hero_video_poster': None, + 'contact_info': json.dumps({ + 'phone': '+1 (555) 123-4567', + 'toll_free': '+1 (800) 123-4567', + 'email': 'info@luxuryhotel.com', + 'reservations_email': 'reservations@luxuryhotel.com', + 'address': '123 Luxury Avenue, Premium City, PC 12345', + 'address_line2': 'United States', + 'business_hours': { + 'front_desk': '24/7', + 'concierge': '24/7', + 'reservations': 'Monday - Sunday: 8:00 AM - 10:00 PM', + 'restaurant': 'Breakfast: 7:00 AM - 11:00 AM, Lunch: 12:00 PM - 3:00 PM, Dinner: 6:00 PM - 11:00 PM', + 'spa': 'Monday - Sunday: 9:00 AM - 9:00 PM' + } + }), + 'map_url': 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3022.184132576!2d-73.98811768459418!3d40.75889597932681!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x89c25855c6480299%3A0x55194ec5a1ae072e!2sTimes%20Square!5e0!3m2!1sen!2sus!4v1234567890123!5m2!1sen!2sus', + 'social_links': json.dumps({ + 'facebook': 'https://facebook.com/luxuryhotel', + 'twitter': 'https://twitter.com/luxuryhotel', + 'instagram': 'https://instagram.com/luxuryhotel', + 'linkedin': 'https://linkedin.com/company/luxuryhotel', + 'youtube': 'https://youtube.com/luxuryhotel', + 'pinterest': 'https://pinterest.com/luxuryhotel' + }), + 'testimonials': json.dumps([ + { + 'name': 'Sarah Johnson', + 'title': 'Guest', + 'quote': 'The customer service team was incredibly helpful and responsive. They answered all my questions and made my booking process seamless.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Michael Chen', + 'title': 'Business Traveler', + 'quote': 'The concierge team went above and beyond to help with my business needs. Their attention to detail and professionalism is outstanding.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=face' + } + ]), + 'testimonials_section_title': 'What Our Guests Say About Our Service', + 'testimonials_section_subtitle': 'Real feedback from our valued guests', + 'features': json.dumps([ + { + 'icon': 'phone', + 'title': '24/7 Support', + 'description': 'Our team is available around the clock to assist you with any questions or requests.' + }, + { + 'icon': 'clock', + 'title': 'Quick Response', + 'description': 'We respond to all inquiries within 24 hours, often much sooner.' + }, + { + 'icon': 'users', + 'title': 'Expert Team', + 'description': 'Our knowledgeable staff is trained to provide exceptional service and assistance.' + }, + { + 'icon': 'globe', + 'title': 'Multiple Languages', + 'description': 'We speak your language. Our team is fluent in multiple languages to serve international guests.' + } + ]), + 'features_section_title': 'Why Contact Us', + 'features_section_subtitle': 'We\'re committed to providing exceptional service', + 'gallery_images': json.dumps([ + 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop' + ]), + 'gallery_section_title': 'Visit Our Hotel', + 'gallery_section_subtitle': 'Experience our luxury facilities', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_contact_page(db: Session): + """Seed contact page content into the database""" + try: + contact_data = get_contact_page_data() + + # Check if contact page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.CONTACT).first() + + if existing_content: + logger.info('Updating existing contact page content...') + for key, value in contact_data.items(): + if key not in ['id', 'page_type', 'created_at']: # Don't update primary key or creation date + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new contact page content...') + contact_page = PageContent(**contact_data) + db.add(contact_page) + + db.commit() + logger.info('Contact page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding contact page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_contact_page(db) + except Exception as e: + logger.error(f'Failed to seed contact page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/faq_seeder.py b/Backend/seeders/faq_seeder.py new file mode 100644 index 00000000..83e0bdde --- /dev/null +++ b/Backend/seeders/faq_seeder.py @@ -0,0 +1,192 @@ +""" +FAQ Page Seeder +Seeds the database with comprehensive FAQ content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_faq_page_data(): + """Generate comprehensive FAQ page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.FAQ, + 'title': 'Frequently Asked Questions', + 'subtitle': 'Find Answers to Common Questions', + 'description': 'Get answers to the most frequently asked questions about our hotel, reservations, services, and policies.', + 'content': """ +

General Questions

+ +

What are your check-in and check-out times?

+

Check-in time is 3:00 PM, and check-out time is 11:00 AM. Early check-in and late check-out may be available upon request, subject to availability and additional charges.

+ +

Do you offer airport transportation?

+

Yes, we offer airport transfer services. Please contact our concierge team at least 24 hours in advance to arrange transportation. Additional charges may apply.

+ +

Is parking available?

+

Yes, we offer complimentary valet parking for all hotel guests. Self-parking is also available at an additional charge.

+ +

Do you have a fitness center?

+

Yes, our state-of-the-art fitness center is open 24/7 and is complimentary for all hotel guests. We also offer personal training sessions upon request.

+ +

Is Wi-Fi available?

+

Yes, complimentary high-speed Wi-Fi is available throughout the hotel, including all guest rooms and public areas.

+ +

Reservations & Booking

+ +

How do I make a reservation?

+

You can make a reservation through our website, by calling our reservations team at +1 (555) 123-4567, or by emailing reservations@luxuryhotel.com.

+ +

Can I modify or cancel my reservation?

+

Yes, you can modify or cancel your reservation through our website or by contacting our reservations team. Please refer to our cancellation policy for details on refunds and fees.

+ +

What payment methods do you accept?

+

We accept all major credit cards (Visa, MasterCard, American Express, Discover), debit cards, and bank transfers. Cash payments are also accepted at check-in.

+ +

Do you require a deposit?

+

A credit card is required to guarantee your reservation. A deposit may be required for certain room types or during peak seasons. Details will be provided at the time of booking.

+ +

Can I book for someone else?

+

Yes, you can make a reservation for another guest. Please provide the guest's name and contact information at the time of booking, and ensure they have a valid ID for check-in.

+ +

Rooms & Accommodations

+ +

What amenities are included in the rooms?

+

All rooms include complimentary Wi-Fi, flat-screen TV, minibar, coffee maker, in-room safe, air conditioning, and luxury toiletries. Suites include additional amenities such as separate living areas and premium services.

+ +

Do you have connecting rooms?

+

Yes, we have connecting rooms available. Please request connecting rooms at the time of booking, and we will do our best to accommodate your request based on availability.

+ +

Are rooms accessible for guests with disabilities?

+

Yes, we have accessible rooms designed to meet ADA requirements. Please inform us of any specific accessibility needs when making your reservation.

+ +

Can I request a specific room or floor?

+

Yes, you can request a specific room or floor preference. While we cannot guarantee specific rooms, we will do our best to honor your request based on availability.

+ +

Do you allow pets?

+

We welcome service animals. For pets, please contact us in advance as pet-friendly rooms are limited and additional charges may apply.

+ +

Services & Amenities

+ +

Do you have a spa?

+

Yes, we have a world-class spa offering a variety of treatments. Advance reservations are recommended. Please contact our spa directly to book treatments.

+ +

Are there restaurants on-site?

+

Yes, we have multiple dining options including fine dining, casual restaurants, and room service available 24/7. Our restaurants feature award-winning cuisine and extensive wine selections.

+ +

Do you offer room service?

+

Yes, room service is available 24 hours a day. Our extensive menu includes breakfast, lunch, dinner, and late-night options.

+ +

Is there a business center?

+

Yes, our business center is equipped with computers, printers, and meeting facilities. It is available 24/7 for all guests.

+ +

Do you have meeting and event facilities?

+

Yes, we have versatile meeting and event spaces suitable for conferences, weddings, and other special occasions. Please contact our events team for more information.

+ +

Policies & Procedures

+ +

What is your cancellation policy?

+

Cancellation policies vary by rate type and booking. Generally, cancellations made 24-48 hours before check-in are free. Please refer to your booking confirmation for specific cancellation terms.

+ +

What is your smoking policy?

+

Our hotel is 100% non-smoking. Smoking is not permitted in any guest rooms or indoor public areas. Designated outdoor smoking areas are available.

+ +

What is the minimum age to check in?

+

Guests must be at least 18 years old to check in. Guests under 18 must be accompanied by an adult.

+ +

Do you have a lost and found?

+

Yes, we maintain a lost and found department. If you have lost an item, please contact our front desk immediately. We will make every effort to locate and return your belongings.

+ +

Special Requests

+ +

Can I request early check-in or late check-out?

+

Early check-in and late check-out are subject to availability. Please contact us in advance, and we will do our best to accommodate your request. Additional charges may apply.

+ +

Do you accommodate special dietary requirements?

+

Yes, we can accommodate various dietary requirements including vegetarian, vegan, gluten-free, and allergies. Please inform us of any dietary restrictions when making your reservation or dining reservation.

+ +

Can you help arrange special occasions?

+

Absolutely! Our concierge team can help arrange special occasions such as anniversaries, birthdays, or proposals. Please contact us in advance to discuss your needs.

+ +

Contact & Support

+ +

How can I contact the hotel?

+

You can reach us by phone at +1 (555) 123-4567, email at info@luxuryhotel.com, or through our website's contact form. Our front desk is available 24/7.

+ +

Do you have a loyalty program?

+

Yes, we offer a loyalty program with exclusive benefits including room upgrades, late check-out, and special rates. Membership is free. Please visit our website or contact us for more information.

+ +

Can I leave feedback about my stay?

+

We value your feedback! You can leave a review on our website, through our post-stay survey, or by contacting our guest relations team directly.

+ """, + 'meta_title': 'FAQ | Frequently Asked Questions - Luxury Hotel & Resort', + 'meta_description': 'Find answers to frequently asked questions about reservations, rooms, services, and policies at Luxury Hotel & Resort.', + 'meta_keywords': 'FAQ, frequently asked questions, hotel questions, booking questions, hotel policies, hotel information', + 'og_title': 'FAQ - Luxury Hotel & Resort', + 'og_description': 'Find answers to common questions about our hotel, reservations, and services.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/faq', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_faq_page(db: Session): + """Seed FAQ page content into the database""" + try: + faq_data = get_faq_page_data() + + # Check if FAQ page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.FAQ).first() + + if existing_content: + logger.info('Updating existing FAQ page content...') + for key, value in faq_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new FAQ page content...') + faq_page = PageContent(**faq_data) + db.add(faq_page) + + db.commit() + logger.info('FAQ page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding FAQ page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_faq_page(db) + except Exception as e: + logger.error(f'Failed to seed FAQ page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/footer_seeder.py b/Backend/seeders/footer_seeder.py new file mode 100644 index 00000000..2cf6a059 --- /dev/null +++ b/Backend/seeders/footer_seeder.py @@ -0,0 +1,173 @@ +""" +Footer Page Seeder +Seeds the database with comprehensive footer content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_footer_page_data(): + """Generate comprehensive footer page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.FOOTER, + 'title': 'Luxury Hotel & Resort', + 'subtitle': 'Experience Unparalleled Elegance and Comfort', + 'description': 'Your premier destination for luxury hospitality. Experience world-class service, exquisite accommodations, and unforgettable moments.', + 'content': '

For over three decades, we have been crafting exceptional experiences for discerning travelers worldwide. Our commitment to excellence and attention to detail sets us apart.

', + 'social_links': json.dumps({ + 'facebook': 'https://facebook.com/luxuryhotel', + 'twitter': 'https://twitter.com/luxuryhotel', + 'instagram': 'https://instagram.com/luxuryhotel', + 'linkedin': 'https://linkedin.com/company/luxuryhotel', + 'youtube': 'https://youtube.com/luxuryhotel', + 'pinterest': 'https://pinterest.com/luxuryhotel' + }), + 'footer_links': json.dumps({ + 'quick_links': [ + {'label': 'Home', 'url': '/'}, + {'label': 'About Us', 'url': '/about'}, + {'label': 'Rooms & Suites', 'url': '/rooms'}, + {'label': 'Services', 'url': '/services'}, + {'label': 'Contact', 'url': '/contact'}, + {'label': 'Blog', 'url': '/blog'} + ], + 'accommodations': [ + {'label': 'Standard Rooms', 'url': '/rooms?type=standard'}, + {'label': 'Deluxe Rooms', 'url': '/rooms?type=deluxe'}, + {'label': 'Executive Suites', 'url': '/rooms?type=executive'}, + {'label': 'Presidential Suites', 'url': '/rooms?type=presidential'}, + {'label': 'Ocean View Rooms', 'url': '/rooms?type=ocean-view'}, + {'label': 'Family Rooms', 'url': '/rooms?type=family'} + ], + 'services': [ + {'label': 'Spa & Wellness', 'url': '/services?category=spa'}, + {'label': 'Fine Dining', 'url': '/services?category=dining'}, + {'label': 'Concierge', 'url': '/services?category=concierge'}, + {'label': 'Business Center', 'url': '/services?category=business'}, + {'label': 'Event Planning', 'url': '/services?category=events'}, + {'label': 'Transportation', 'url': '/services?category=transportation'} + ], + 'information': [ + {'label': 'About Us', 'url': '/about'}, + {'label': 'Our Story', 'url': '/about#story'}, + {'label': 'Awards & Recognition', 'url': '/about#awards'}, + {'label': 'Careers', 'url': '/careers'}, + {'label': 'Press & Media', 'url': '/press'}, + {'label': 'Blog', 'url': '/blog'} + ], + 'support_links': [ + {'label': 'Contact Us', 'url': '/contact'}, + {'label': 'FAQ', 'url': '/faq'}, + {'label': 'Booking Help', 'url': '/help'}, + {'label': 'Cancellation Policy', 'url': '/cancellation'}, + {'label': 'Privacy Policy', 'url': '/privacy'}, + {'label': 'Terms & Conditions', 'url': '/terms'}, + {'label': 'Refund Policy', 'url': '/refunds'}, + {'label': 'Accessibility', 'url': '/accessibility'} + ] + }), + 'badges': json.dumps([ + { + 'icon': 'award', + 'text': '5-Star Rated', + 'description': 'Consistently rated five-star by global travel guides' + }, + { + 'icon': 'trophy', + 'text': 'Award Winning', + 'description': 'Best Luxury Hotel 2023' + }, + { + 'icon': 'shield', + 'text': 'Secure Booking', + 'description': 'Your data and payments are protected' + }, + { + 'icon': 'star', + 'text': '98% Satisfaction', + 'description': 'Guest satisfaction rating' + }, + { + 'icon': 'leaf', + 'text': 'Eco-Friendly', + 'description': 'Certified green hotel' + }, + { + 'icon': 'check-circle', + 'text': 'Best Price Guarantee', + 'description': 'We guarantee the best rates' + } + ]), + 'copyright_text': f'© {datetime.now(timezone.utc).year} Luxury Hotel & Resort. All rights reserved.', + 'contact_info': json.dumps({ + 'phone': '+1 (555) 123-4567', + 'toll_free': '+1 (800) 123-4567', + 'email': 'info@luxuryhotel.com', + 'reservations_email': 'reservations@luxuryhotel.com', + 'address': '123 Luxury Avenue, Premium City, PC 12345, United States' + }), + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_footer_page(db: Session): + """Seed footer page content into the database""" + try: + footer_data = get_footer_page_data() + + # Check if footer page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.FOOTER).first() + + if existing_content: + logger.info('Updating existing footer page content...') + for key, value in footer_data.items(): + if key not in ['id', 'page_type', 'created_at']: # Don't update primary key or creation date + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new footer page content...') + footer_page = PageContent(**footer_data) + db.add(footer_page) + + db.commit() + logger.info('Footer page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding footer page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_footer_page(db) + except Exception as e: + logger.error(f'Failed to seed footer page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/homepage_seeder.py b/Backend/seeders/homepage_seeder.py new file mode 100644 index 00000000..97f7e4ac --- /dev/null +++ b/Backend/seeders/homepage_seeder.py @@ -0,0 +1,596 @@ +""" +Homepage Seeder +Seeds the homepage with comprehensive content including images from Unsplash +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal, engine +from src.content.models.page_content import PageContent, PageType +from src.shared.config.logging_config import get_logger + +logger = get_logger(__name__) + + +def get_homepage_data(): + """Generate comprehensive homepage data with Unsplash images""" + + # Using Unsplash Source API for images (free, no API key needed) + # Format: https://source.unsplash.com/{width}x{height}/?{keywords} + + return { + 'page_type': PageType.HOME, + 'title': 'Luxury Hotel & Resort', + 'subtitle': 'Experience Unparalleled Elegance and Comfort', + 'description': 'Welcome to our world-class luxury hotel where exceptional service meets breathtaking elegance. Discover a sanctuary of sophistication and comfort.', + 'content': '

Experience the pinnacle of luxury hospitality at our award-winning hotel. Nestled in a prime location, we offer world-class amenities, exquisite dining, and unparalleled service that creates unforgettable memories.

', + + # SEO Meta Tags + 'meta_title': 'Luxury Hotel & Resort | Premium Accommodation & World-Class Service', + 'meta_description': 'Discover luxury accommodation with world-class amenities, fine dining, and exceptional service. Book your stay at our award-winning hotel today.', + 'meta_keywords': 'luxury hotel, resort, premium accommodation, fine dining, spa, business hotel, vacation, travel', + 'og_title': 'Luxury Hotel & Resort - Experience Unparalleled Elegance', + 'og_description': 'Welcome to our world-class luxury hotel where exceptional service meets breathtaking elegance.', + 'og_image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com', + + # Hero Section + 'hero_title': 'Welcome to Luxury Redefined', + 'hero_subtitle': 'Where Every Moment Becomes a Memory', + 'hero_image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1920&h=1080&fit=crop', + 'hero_video_url': None, + 'hero_video_poster': None, + + # Story Content + 'story_content': '

For over three decades, we have been crafting exceptional experiences for our guests. Our commitment to excellence, attention to detail, and passion for hospitality has made us a destination of choice for discerning travelers worldwide.

', + + # Values Section + 'values': json.dumps([ + { + 'icon': 'heart', + 'title': 'Excellence', + 'description': 'We strive for perfection in every detail, ensuring your experience exceeds expectations.' + }, + { + 'icon': 'shield', + 'title': 'Trust', + 'description': 'Your comfort and security are our top priorities, backed by years of trusted service.' + }, + { + 'icon': 'users', + 'title': 'Hospitality', + 'description': 'Our dedicated team is committed to making your stay memorable and delightful.' + }, + { + 'icon': 'award', + 'title': 'Quality', + 'description': 'We maintain the highest standards in service, amenities, and guest satisfaction.' + } + ]), + + # Features Section + 'features_section_title': 'Why Choose Us', + 'features_section_subtitle': 'Discover what makes us the perfect choice for your stay', + 'features': json.dumps([ + { + 'icon': 'wifi', + 'title': 'Free High-Speed WiFi', + 'description': 'Stay connected with complimentary high-speed internet throughout the property.', + 'image': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=800&h=600&fit=crop' + }, + { + 'icon': 'utensils', + 'title': 'Fine Dining', + 'description': 'Savor exquisite cuisine at our award-winning restaurants and bars.', + 'image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800&h=600&fit=crop' + }, + { + 'icon': 'spa', + 'title': 'Luxury Spa', + 'description': 'Rejuvenate your mind and body at our world-class spa and wellness center.', + 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=800&h=600&fit=crop' + }, + { + 'icon': 'dumbbell', + 'title': 'Fitness Center', + 'description': 'Maintain your workout routine in our state-of-the-art fitness facility.', + 'image': 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=800&h=600&fit=crop' + }, + { + 'icon': 'swimming-pool', + 'title': 'Swimming Pool', + 'description': 'Relax and unwind in our stunning outdoor and indoor pools.', + 'image': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800&h=600&fit=crop' + }, + { + 'icon': 'car', + 'title': 'Valet Parking', + 'description': 'Complimentary valet parking service for all our guests.', + 'image': 'https://images.unsplash.com/photo-1502877338535-766e1452684a?w=800&h=600&fit=crop' + } + ]), + + # About Preview Section + 'about_preview_title': 'Our Story', + 'about_preview_subtitle': 'A Legacy of Excellence', + 'about_preview_content': '

Since our founding, we have been dedicated to providing exceptional hospitality experiences. Our commitment to excellence, combined with our passion for service, has established us as a leader in the luxury hospitality industry.

', + 'about_preview_image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop', + + # Stats Section + 'stats_section_title': 'Our Achievements', + 'stats_section_subtitle': 'Numbers that speak for themselves', + 'stats': json.dumps([ + { + 'number': '50000+', + 'label': 'Happy Guests', + 'icon': 'users' + }, + { + 'number': '30+', + 'label': 'Years of Excellence', + 'icon': 'calendar' + }, + { + 'number': '150+', + 'label': 'Awards Won', + 'icon': 'award' + }, + { + 'number': '98%', + 'label': 'Guest Satisfaction', + 'icon': 'star' + } + ]), + + # Rooms Section + 'rooms_section_title': 'Luxurious Accommodations', + 'rooms_section_subtitle': 'Elegant rooms and suites designed for your comfort', + 'rooms_section_button_text': 'View All Rooms', + 'rooms_section_button_link': '/rooms', + 'rooms_section_enabled': True, + + # Luxury Services Section + 'luxury_services_section_title': 'Premium Services', + 'luxury_services_section_subtitle': 'Indulge in our exclusive offerings', + 'services_section_button_text': 'Explore Services', + 'services_section_button_link': '/services', + 'services_section_limit': 6, + 'luxury_services': json.dumps([ + { + 'icon': 'concierge', + 'title': '24/7 Concierge', + 'description': 'Our dedicated concierge team is available around the clock to assist with any request.', + 'image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=600&h=400&fit=crop' + }, + { + 'icon': 'plane', + 'title': 'Airport Transfer', + 'description': 'Complimentary airport transfer service for a seamless arrival experience.', + 'image': 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=600&h=400&fit=crop' + }, + { + 'icon': 'briefcase', + 'title': 'Business Center', + 'description': 'Fully equipped business center with meeting rooms and conference facilities.', + 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=600&h=400&fit=crop' + }, + { + 'icon': 'wine', + 'title': 'Wine Cellar', + 'description': 'Explore our extensive collection of fine wines from around the world.', + 'image': 'https://images.unsplash.com/photo-1510812431401-41d2bd2722f3?w=600&h=400&fit=crop' + }, + { + 'icon': 'music', + 'title': 'Live Entertainment', + 'description': 'Enjoy live music and entertainment in our elegant lounge areas.', + 'image': 'https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=600&h=400&fit=crop' + }, + { + 'icon': 'gift', + 'title': 'Gift Shop', + 'description': 'Browse our curated selection of luxury gifts and souvenirs.', + 'image': 'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=600&h=400&fit=crop' + } + ]), + + # Luxury Experiences Section + 'luxury_experiences_section_title': 'Unique Experiences', + 'luxury_experiences_section_subtitle': 'Create unforgettable memories with our curated experiences', + 'luxury_experiences': json.dumps([ + { + 'icon': 'sunset', + 'title': 'Sunset Rooftop Dining', + 'description': 'Dine under the stars with panoramic city views and gourmet cuisine.', + 'image': 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=600&fit=crop' + }, + { + 'icon': 'compass', + 'title': 'City Tours', + 'description': 'Explore the city with our guided tours showcasing local culture and landmarks.', + 'image': 'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=800&h=600&fit=crop' + }, + { + 'icon': 'camera', + 'title': 'Photography Sessions', + 'description': 'Capture your special moments with our professional photography services.', + 'image': 'https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=800&h=600&fit=crop' + } + ]), + + # Amenities Section + 'amenities_section_title': 'World-Class Amenities', + 'amenities_section_subtitle': 'Everything you need for a perfect stay', + 'amenities': json.dumps([ + { + 'icon': 'wifi', + 'title': 'Free WiFi', + 'description': 'High-speed internet access throughout the property' + }, + { + 'icon': 'tv', + 'title': 'Smart TV', + 'description': 'Streaming services and premium channels in every room' + }, + { + 'icon': 'coffee', + 'title': 'Coffee Maker', + 'description': 'Premium coffee and tea making facilities' + }, + { + 'icon': 'snowflake', + 'title': 'Climate Control', + 'description': 'Individual temperature control in all rooms' + }, + { + 'icon': 'lock', + 'title': 'Safe', + 'description': 'In-room safe for your valuables' + }, + { + 'icon': 'shirt', + 'title': 'Laundry Service', + 'description': 'Professional laundry and dry cleaning services' + } + ]), + + # Testimonials Section + 'testimonials_section_title': 'What Our Guests Say', + 'testimonials_section_subtitle': 'Real experiences from real guests', + 'testimonials': json.dumps([ + { + 'name': 'Sarah Johnson', + 'title': 'Business Traveler', + 'quote': 'The service was exceptional and the rooms were immaculate. This hotel exceeded all my expectations.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Michael Chen', + 'title': 'Honeymooner', + 'quote': 'Our honeymoon was made perfect by the attentive staff and beautiful accommodations. Truly unforgettable!', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Emily Rodriguez', + 'title': 'Family Vacation', + 'quote': 'Perfect for families! The kids loved the pool and the staff was so accommodating. We will definitely return.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'David Thompson', + 'title': 'Luxury Traveler', + 'quote': 'The attention to detail and level of service is unmatched. This is what luxury hospitality should be.', + 'rating': 5, + 'image': 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&h=200&fit=crop&crop=face' + } + ]), + + # Gallery Section + 'gallery_section_title': 'Photo Gallery', + 'gallery_section_subtitle': 'A glimpse into our world of luxury', + 'gallery_images': json.dumps([ + 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1542314831-068cd1dbfeeb?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1578683010236-d716f9a3f461?w=1200&h=800&fit=crop' + ]), + + # Luxury Section + 'luxury_section_title': 'Luxury Redefined', + 'luxury_section_subtitle': 'Experience the epitome of elegance and sophistication', + 'luxury_section_image': 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1400&h=900&fit=crop', + 'luxury_features': json.dumps([ + { + 'icon': 'crown', + 'title': 'Royal Treatment', + 'description': 'Experience service fit for royalty with our personalized attention to every detail.' + }, + { + 'icon': 'gem', + 'title': 'Premium Quality', + 'description': 'Only the finest materials and furnishings grace our elegant spaces.' + }, + { + 'icon': 'sparkles', + 'title': 'Exclusive Access', + 'description': 'Enjoy exclusive access to private lounges and premium facilities.' + } + ]), + + # Luxury Gallery Section + 'luxury_gallery_section_title': 'Luxury Gallery', + 'luxury_gallery_section_subtitle': 'Discover our world of refined elegance', + 'luxury_gallery': json.dumps([ + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop' + ]), + + # Luxury Testimonials Section + 'luxury_testimonials_section_title': 'Guest Experiences', + 'luxury_testimonials_section_subtitle': 'Stories from our valued guests', + 'luxury_testimonials': json.dumps([ + { + 'name': 'James Wilson', + 'title': 'VIP Guest', + 'quote': 'The level of luxury and attention to detail is simply extraordinary. This is hospitality at its finest.', + 'image': 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200&h=200&fit=crop&crop=face' + }, + { + 'name': 'Maria Garcia', + 'title': 'Celebrity Guest', + 'quote': 'Privacy, elegance, and impeccable service. This hotel understands true luxury.', + 'image': 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&h=200&fit=crop&crop=face' + } + ]), + + # Awards Section + 'awards_section_title': 'Awards & Recognition', + 'awards_section_subtitle': 'Recognized for excellence in hospitality', + 'awards': json.dumps([ + { + 'icon': 'trophy', + 'title': 'Best Luxury Hotel 2023', + 'description': 'Awarded by International Hospitality Association', + 'year': '2023', + 'image': 'https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&h=400&fit=crop' + }, + { + 'icon': 'star', + 'title': '5-Star Rating', + 'description': 'Consistently rated 5 stars by leading travel organizations', + 'year': '2023', + 'image': 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=400&h=400&fit=crop' + }, + { + 'icon': 'award', + 'title': 'Excellence in Service', + 'description': 'Recognized for outstanding customer service and guest satisfaction', + 'year': '2022', + 'image': 'https://images.unsplash.com/photo-1579621970563-ebec7560ff3e?w=400&h=400&fit=crop' + } + ]), + + # Partners Section + 'partners_section_title': 'Our Partners', + 'partners_section_subtitle': 'Trusted by leading brands and organizations', + 'partners': json.dumps([ + { + 'name': 'Travel Partner', + 'logo': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=200&h=100&fit=crop', + 'url': '#' + }, + { + 'name': 'Luxury Brand', + 'logo': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=200&h=100&fit=crop', + 'url': '#' + }, + { + 'name': 'Hospitality Group', + 'logo': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=200&h=100&fit=crop', + 'url': '#' + } + ]), + + # CTA Section + 'cta_title': 'Ready to Experience Luxury?', + 'cta_subtitle': 'Book your stay today and discover why we are the preferred choice for discerning travelers', + 'cta_button_text': 'Book Now', + 'cta_button_link': '/book', + 'cta_image': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1400&h=700&fit=crop', + + # Newsletter Section + 'newsletter_section_title': 'Stay Connected', + 'newsletter_section_subtitle': 'Subscribe to our newsletter for exclusive offers and updates', + 'newsletter_placeholder': 'Enter your email address', + 'newsletter_button_text': 'Subscribe', + 'newsletter_enabled': True, + + # Trust Badges Section + 'trust_badges_section_title': 'Why Trust Us', + 'trust_badges_section_subtitle': 'Your peace of mind is our priority', + 'trust_badges_enabled': True, + 'trust_badges': json.dumps([ + { + 'icon': 'shield-check', + 'title': 'Secure Booking', + 'description': 'Your data and payments are protected with industry-leading security' + }, + { + 'icon': 'clock', + 'title': '24/7 Support', + 'description': 'Round-the-clock customer support for all your needs' + }, + { + 'icon': 'undo', + 'title': 'Flexible Cancellation', + 'description': 'Free cancellation up to 24 hours before check-in' + }, + { + 'icon': 'check-circle', + 'title': 'Best Price Guarantee', + 'description': 'We guarantee the best rates for your stay' + } + ]), + + # Promotions Section + 'promotions_section_title': 'Special Offers', + 'promotions_section_subtitle': 'Exclusive deals and packages for our guests', + 'promotions_enabled': True, + 'promotions': json.dumps([ + { + 'title': 'Early Bird Special', + 'description': 'Book 30 days in advance and save 20%', + 'discount': '20% OFF', + 'image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&h=600&fit=crop', + 'link': '/promotions/early-bird' + }, + { + 'title': 'Weekend Getaway', + 'description': 'Perfect weekend escape with complimentary breakfast', + 'discount': '15% OFF', + 'image': 'https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=800&h=600&fit=crop', + 'link': '/promotions/weekend' + }, + { + 'title': 'Honeymoon Package', + 'description': 'Romantic getaway with special amenities and services', + 'discount': '25% OFF', + 'image': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800&h=600&fit=crop', + 'link': '/promotions/honeymoon' + } + ]), + + # Blog Section + 'blog_section_title': 'Latest News & Updates', + 'blog_section_subtitle': 'Stay informed with our latest articles and hotel news', + 'blog_posts_limit': 3, + 'blog_enabled': True, + + # Sections Enabled + 'sections_enabled': json.dumps({ + 'hero': True, + 'features': True, + 'about_preview': True, + 'stats': True, + 'rooms': True, + 'services': True, + 'experiences': True, + 'amenities': True, + 'testimonials': True, + 'gallery': True, + 'luxury': True, + 'awards': True, + 'partners': True, + 'cta': True, + 'newsletter': True, + 'trust_badges': True, + 'promotions': True, + 'blog': True + }), + + # Badges + 'badges': json.dumps([ + { + 'text': '5-Star Rated', + 'icon': 'star' + }, + { + 'text': 'Award Winning', + 'icon': 'award' + }, + { + 'text': 'Eco-Friendly', + 'icon': 'leaf' + } + ]), + + # Social Links + 'social_links': json.dumps({ + 'facebook': 'https://facebook.com/luxuryhotel', + 'twitter': 'https://twitter.com/luxuryhotel', + 'instagram': 'https://instagram.com/luxuryhotel', + 'linkedin': 'https://linkedin.com/company/luxuryhotel', + 'youtube': 'https://youtube.com/luxuryhotel' + }), + + # Contact Info + 'contact_info': json.dumps({ + 'phone': '+1 (555) 123-4567', + 'email': 'info@luxuryhotel.com', + 'address': '123 Luxury Avenue, Premium City, PC 12345' + }), + + # Active Status + 'is_active': True, + 'created_at': datetime.now(timezone.utc), + 'updated_at': datetime.now(timezone.utc) + } + + +def seed_homepage(db: Session): + """Seed the homepage content""" + try: + # Check if homepage already exists + existing = db.query(PageContent).filter(PageContent.page_type == PageType.HOME).first() + + if existing: + logger.info('Homepage content already exists. Updating...') + # Update existing content + data = get_homepage_data() + for key, value in data.items(): + if key != 'page_type': # Don't update page_type + setattr(existing, key, value) + existing.updated_at = datetime.now(timezone.utc) + db.commit() + logger.info('Homepage content updated successfully!') + return existing + else: + logger.info('Creating new homepage content...') + # Create new content + data = get_homepage_data() + homepage = PageContent(**data) + db.add(homepage) + db.commit() + db.refresh(homepage) + logger.info('Homepage content created successfully!') + return homepage + + except Exception as e: + logger.error(f'Error seeding homepage: {str(e)}', exc_info=True) + db.rollback() + raise + + +def main(): + """Main function to run the seeder""" + logger.info('Starting homepage seeder...') + + db = SessionLocal() + try: + homepage = seed_homepage(db) + logger.info(f'Homepage seeder completed successfully! ID: {homepage.id}') + except Exception as e: + logger.error(f'Failed to seed homepage: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/privacy_seeder.py b/Backend/seeders/privacy_seeder.py new file mode 100644 index 00000000..7c8790d8 --- /dev/null +++ b/Backend/seeders/privacy_seeder.py @@ -0,0 +1,172 @@ +""" +Privacy Policy Page Seeder +Seeds the database with comprehensive privacy policy content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_privacy_page_data(): + """Generate comprehensive privacy policy page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.PRIVACY, + 'title': 'Privacy Policy', + 'subtitle': 'Your Privacy Matters to Us', + 'description': 'Learn how we collect, use, and protect your personal information. We are committed to maintaining your privacy and ensuring the security of your data.', + 'content': """ +

1. Introduction

+

At Luxury Hotel & Resort, we are committed to protecting your privacy and ensuring the security of your personal information. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website, make a reservation, or use our services.

+ +

2. Information We Collect

+

2.1 Personal Information

+

We may collect personal information that you provide directly to us, including:

+ + +

2.2 Automatically Collected Information

+

When you visit our website, we may automatically collect certain information, including:

+ + +

3. How We Use Your Information

+

We use the information we collect for various purposes, including:

+ + +

4. Information Sharing and Disclosure

+

We do not sell your personal information. We may share your information in the following circumstances:

+ + +

5. Data Security

+

We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the Internet is 100% secure, and we cannot guarantee absolute security.

+ +

6. Your Rights

+

Depending on your location, you may have certain rights regarding your personal information, including:

+ + +

7. Cookies and Tracking Technologies

+

We use cookies and similar tracking technologies to enhance your browsing experience, analyze website traffic, and personalize content. You can control cookie preferences through your browser settings.

+ +

8. Third-Party Links

+

Our website may contain links to third-party websites. We are not responsible for the privacy practices of these external sites. We encourage you to review their privacy policies.

+ +

9. Children's Privacy

+

Our services are not directed to individuals under the age of 18. We do not knowingly collect personal information from children. If you believe we have collected information from a child, please contact us immediately.

+ +

10. International Data Transfers

+

Your information may be transferred to and processed in countries other than your country of residence. We ensure appropriate safeguards are in place to protect your information in accordance with this Privacy Policy.

+ +

11. Changes to This Privacy Policy

+

We may update this Privacy Policy from time to time. We will notify you of any material changes by posting the new policy on this page and updating the "Last Updated" date.

+ +

12. Contact Us

+

If you have any questions or concerns about this Privacy Policy or our data practices, please contact us at:

+

+ Email: privacy@luxuryhotel.com
+ Phone: +1 (555) 123-4567
+ Address: 123 Luxury Avenue, Premium City, PC 12345, United States +

+ +

Last Updated: {}

+ """.format(now.strftime('%B %d, %Y')), + 'meta_title': 'Privacy Policy | Luxury Hotel & Resort', + 'meta_description': 'Learn how Luxury Hotel & Resort collects, uses, and protects your personal information. Read our comprehensive privacy policy.', + 'meta_keywords': 'privacy policy, data protection, personal information, GDPR, privacy rights, hotel privacy', + 'og_title': 'Privacy Policy - Luxury Hotel & Resort', + 'og_description': 'Your privacy matters to us. Learn how we protect and use your personal information.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/privacy', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_privacy_page(db: Session): + """Seed privacy policy page content into the database""" + try: + privacy_data = get_privacy_page_data() + + # Check if privacy page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.PRIVACY).first() + + if existing_content: + logger.info('Updating existing privacy policy page content...') + for key, value in privacy_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new privacy policy page content...') + privacy_page = PageContent(**privacy_data) + db.add(privacy_page) + + db.commit() + logger.info('Privacy policy page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding privacy policy page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_privacy_page(db) + except Exception as e: + logger.error(f'Failed to seed privacy policy page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/refunds_seeder.py b/Backend/seeders/refunds_seeder.py new file mode 100644 index 00000000..594e4a13 --- /dev/null +++ b/Backend/seeders/refunds_seeder.py @@ -0,0 +1,193 @@ +""" +Refund Policy Page Seeder +Seeds the database with comprehensive refund policy content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_refunds_page_data(): + """Generate comprehensive refund policy page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.REFUNDS, + 'title': 'Refund Policy', + 'subtitle': 'Our Refund Policy and Procedures', + 'description': 'Learn about our refund policy, including eligibility, processing times, and how to request a refund for your reservation or services.', + 'content': """ +

1. Overview

+

At Luxury Hotel & Resort, we strive to provide exceptional service and ensure your satisfaction. This Refund Policy outlines the terms and conditions under which refunds may be issued for reservations, services, and other transactions.

+ +

2. Reservation Refunds

+

2.1 Cancellation Refunds

+

Refunds for cancelled reservations are subject to the cancellation policy associated with your booking:

+ + +

2.2 Early Departure

+

If you check out earlier than your scheduled departure date, you may be eligible for a partial refund for unused nights, subject to the terms of your reservation and availability. Early departure refunds are not guaranteed and are evaluated on a case-by-case basis.

+ +

2.3 No-Show

+

If you do not arrive on your scheduled check-in date and have not cancelled your reservation, you will be charged for the first night. No refunds are provided for no-show reservations.

+ +

3. Service Refunds

+

3.1 Spa Services

+

Spa service refunds or rescheduling:

+ + +

3.2 Dining Reservations

+

Dining reservation refunds:

+ + +

3.3 Event & Meeting Bookings

+

Refund policies for events and meetings vary based on the size and type of event. Please refer to your event contract for specific refund terms.

+ +

4. Processing Refunds

+

4.1 Refund Processing Time

+

Once approved, refunds are typically processed within 5-10 business days. The time it takes for the refund to appear in your account depends on your financial institution:

+ + +

4.2 Refund Method

+

Refunds will be issued to the original payment method used for the transaction. If the original payment method is no longer valid, please contact us to arrange an alternative refund method.

+ +

5. Requesting a Refund

+

5.1 How to Request

+

To request a refund, please contact us:

+ + +

5.2 Required Information

+

When requesting a refund, please provide:

+ + +

6. Non-Refundable Items

+

The following items and services are generally non-refundable:

+ + +

7. Special Circumstances

+

7.1 Force Majeure

+

In cases of force majeure events (natural disasters, pandemics, government restrictions, etc.), we will work with you to provide refunds, credits, or rescheduling options. Each situation is evaluated individually.

+ +

7.2 Service Issues

+

If you experience service issues during your stay, please bring them to our attention immediately so we can address them. We may offer partial refunds or credits for significant service failures, evaluated on a case-by-case basis.

+ +

8. Disputes & Appeals

+

If you are not satisfied with a refund decision, you may appeal by contacting our guest relations team. We will review your case and provide a response within 5-7 business days.

+ +

9. Third-Party Bookings

+

If you made your reservation through a third-party booking site (such as Expedia, Booking.com, etc.), refund requests must be processed through that third party according to their policies. We cannot directly process refunds for third-party bookings.

+ +

10. Contact Information

+

For refund inquiries or assistance, please contact us:

+ + +

11. Policy Updates

+

We reserve the right to update this Refund Policy at any time. Changes will be posted on this page with an updated "Last Updated" date. Your continued use of our services after changes are posted constitutes acceptance of the updated policy.

+ +

Last Updated: {}

+ """.format(now.strftime('%B %d, %Y')), + 'meta_title': 'Refund Policy | Luxury Hotel & Resort', + 'meta_description': 'Learn about our refund policy, including eligibility, processing times, and how to request refunds for reservations and services.', + 'meta_keywords': 'refund policy, cancellation refund, hotel refund, booking refund, refund request', + 'og_title': 'Refund Policy - Luxury Hotel & Resort', + 'og_description': 'Our refund policy and procedures. Learn how to request refunds for reservations and services.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/refunds', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_refunds_page(db: Session): + """Seed refunds policy page content into the database""" + try: + refunds_data = get_refunds_page_data() + + # Check if refunds page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.REFUNDS).first() + + if existing_content: + logger.info('Updating existing refunds policy page content...') + for key, value in refunds_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new refunds policy page content...') + refunds_page = PageContent(**refunds_data) + db.add(refunds_page) + + db.commit() + logger.info('Refunds policy page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding refunds policy page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_refunds_page(db) + except Exception as e: + logger.error(f'Failed to seed refunds policy page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/room_seeder.py b/Backend/seeders/room_seeder.py new file mode 100644 index 00000000..662de21c --- /dev/null +++ b/Backend/seeders/room_seeder.py @@ -0,0 +1,473 @@ +""" +Room Seeder +Seeds the database with room types and rooms +All images are from Unsplash (free stock photos) +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone +from decimal import Decimal + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.rooms.models.room import Room, RoomStatus +from src.rooms.models.room_type import RoomType + +# Import all models to ensure relationships are loaded correctly +from src.models import * + +logger = get_logger(__name__) + + +def get_room_types_data(): + """Generate room types data""" + now = datetime.now(timezone.utc) + + return [ + { + 'name': 'Standard Room', + 'description': 'Comfortable and well-appointed standard rooms perfect for business travelers and couples. Features modern amenities and elegant decor.', + 'base_price': Decimal('150.00'), + 'capacity': 2, + 'amenities': json.dumps([ + 'Free WiFi', + 'Flat-screen TV', + 'Air Conditioning', + 'Mini Bar', + 'Work Desk', + 'In-room Safe', + 'Coffee Maker', + 'Hair Dryer' + ]) + }, + { + 'name': 'Deluxe Room', + 'description': 'Spacious deluxe rooms with enhanced amenities and premium furnishings. Ideal for guests seeking extra comfort and space.', + 'base_price': Decimal('220.00'), + 'capacity': 2, + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV', + 'Air Conditioning', + 'Premium Mini Bar', + 'Work Desk', + 'In-room Safe', + 'Nespresso Machine', + 'Hair Dryer', + 'Bathrobe & Slippers', + 'City View' + ]) + }, + { + 'name': 'Executive Suite', + 'description': 'Luxurious suites with separate living areas, perfect for extended stays or guests who prefer extra space and privacy.', + 'base_price': Decimal('350.00'), + 'capacity': 3, + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Air Conditioning', + 'Premium Mini Bar', + 'Separate Living Area', + 'In-room Safe', + 'Nespresso Machine', + 'Hair Dryer', + 'Bathrobe & Slippers', + 'Panoramic View', + 'Premium Toiletries', + 'Welcome Amenities' + ]) + }, + { + 'name': 'Presidential Suite', + 'description': 'The ultimate in luxury accommodation. Spacious multi-room suite with premium amenities, private terrace, and personalized butler service.', + 'base_price': Decimal('800.00'), + 'capacity': 4, + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Air Conditioning', + 'Premium Mini Bar', + 'Separate Living & Dining Areas', + 'In-room Safe', + 'Nespresso Machine', + 'Hair Dryer', + 'Bathrobe & Slippers', + 'Panoramic View', + 'Premium Toiletries', + 'Welcome Amenities', + 'Private Terrace', + 'Butler Service', + 'Private Bar', + 'Jacuzzi' + ]) + }, + { + 'name': 'Ocean View Room', + 'description': 'Stunning ocean view rooms with floor-to-ceiling windows and private balconies. Perfect for romantic getaways and relaxation.', + 'base_price': Decimal('280.00'), + 'capacity': 2, + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV', + 'Air Conditioning', + 'Premium Mini Bar', + 'Work Desk', + 'In-room Safe', + 'Nespresso Machine', + 'Hair Dryer', + 'Bathrobe & Slippers', + 'Ocean View', + 'Private Balcony', + 'Premium Toiletries' + ]) + }, + { + 'name': 'Family Room', + 'description': 'Spacious family-friendly rooms designed to accommodate families with children. Features extra beds and family amenities.', + 'base_price': Decimal('300.00'), + 'capacity': 4, + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Air Conditioning', + 'Mini Bar', + 'Extra Beds', + 'In-room Safe', + 'Coffee Maker', + 'Hair Dryer', + 'Family Amenities', + 'Game Console', + 'Child Safety Features' + ]) + } + ] + + +def get_rooms_data(room_type_map): + """Generate rooms data based on room types""" + now = datetime.now(timezone.utc) + + rooms = [] + + # Standard Rooms (Floors 1-3, Rooms 101-130) + standard_type = room_type_map.get('Standard Room') + if standard_type: + for floor in range(1, 4): + for room_num in range(1, 11): + room_number = f"{floor}{room_num:02d}" + rooms.append({ + 'room_type_id': standard_type.id, + 'room_number': room_number, + 'floor': floor, + 'status': RoomStatus.available, + 'price': Decimal('150.00'), + 'featured': room_num <= 2, # First 2 rooms per floor are featured + 'capacity': 2, + 'room_size': '25 sqm', + 'view': 'City View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Flat-screen TV', + 'Air Conditioning', + 'Mini Bar', + 'Work Desk', + 'In-room Safe' + ]), + 'description': f'Comfortable standard room on floor {floor} with modern amenities and city view. Perfect for business travelers and couples.' + }) + + # Deluxe Rooms (Floors 4-6, Rooms 401-430) + deluxe_type = room_type_map.get('Deluxe Room') + if deluxe_type: + for floor in range(4, 7): + for room_num in range(1, 11): + room_number = f"{floor}{room_num:02d}" + rooms.append({ + 'room_type_id': deluxe_type.id, + 'room_number': room_number, + 'floor': floor, + 'status': RoomStatus.available, + 'price': Decimal('220.00'), + 'featured': room_num <= 2, + 'capacity': 2, + 'room_size': '35 sqm', + 'view': 'City View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV', + 'Air Conditioning', + 'Premium Mini Bar', + 'Work Desk', + 'Nespresso Machine', + 'Bathrobe & Slippers' + ]), + 'description': f'Spacious deluxe room on floor {floor} with premium amenities and enhanced comfort. Ideal for guests seeking extra space.' + }) + + # Executive Suites (Floors 7-8, Rooms 701-720) + executive_type = room_type_map.get('Executive Suite') + if executive_type: + for floor in range(7, 9): + for room_num in range(1, 11): + room_number = f"{floor}{room_num:02d}" + rooms.append({ + 'room_type_id': executive_type.id, + 'room_number': room_number, + 'floor': floor, + 'status': RoomStatus.available, + 'price': Decimal('350.00'), + 'featured': room_num <= 3, + 'capacity': 3, + 'room_size': '55 sqm', + 'view': 'Panoramic View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Separate Living Area', + 'Premium Mini Bar', + 'Nespresso Machine', + 'Bathrobe & Slippers', + 'Premium Toiletries', + 'Welcome Amenities' + ]), + 'description': f'Luxurious executive suite on floor {floor} with separate living area and panoramic views. Perfect for extended stays.' + }) + + # Presidential Suites (Floor 9, Rooms 901-905) + presidential_type = room_type_map.get('Presidential Suite') + if presidential_type: + for room_num in range(1, 6): + room_number = f"9{room_num:02d}" + rooms.append({ + 'room_type_id': presidential_type.id, + 'room_number': room_number, + 'floor': 9, + 'status': RoomStatus.available, + 'price': Decimal('800.00'), + 'featured': True, # All presidential suites are featured + 'capacity': 4, + 'room_size': '120 sqm', + 'view': 'Panoramic View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Separate Living & Dining Areas', + 'Private Terrace', + 'Butler Service', + 'Premium Mini Bar', + 'Private Bar', + 'Jacuzzi', + 'Premium Toiletries', + 'Welcome Amenities' + ]), + 'description': f'Ultimate luxury in our exclusive presidential suite. Features grand living room, formal dining, private terrace, and personal butler service. Suite {room_num}.' + }) + + # Ocean View Rooms (Floor 10, Rooms 1001-1020) + ocean_type = room_type_map.get('Ocean View Room') + if ocean_type: + for room_num in range(1, 21): + room_number = f"10{room_num:02d}" + rooms.append({ + 'room_type_id': ocean_type.id, + 'room_number': room_number, + 'floor': 10, + 'status': RoomStatus.available, + 'price': Decimal('280.00'), + 'featured': room_num <= 5, + 'capacity': 2, + 'room_size': '30 sqm', + 'view': 'Ocean View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1587925358603-dc217c8a64f8?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV', + 'Air Conditioning', + 'Premium Mini Bar', + 'Private Balcony', + 'Nespresso Machine', + 'Bathrobe & Slippers', + 'Ocean View' + ]), + 'description': f'Stunning ocean view room with floor-to-ceiling windows and private balcony. Perfect for romantic getaways and relaxation.' + }) + + # Family Rooms (Floor 1, Rooms 111-120) + family_type = room_type_map.get('Family Room') + if family_type: + for room_num in range(11, 21): + room_number = f"1{room_num:02d}" + rooms.append({ + 'room_type_id': family_type.id, + 'room_number': room_number, + 'floor': 1, + 'status': RoomStatus.available, + 'price': Decimal('300.00'), + 'featured': room_num <= 13, + 'capacity': 4, + 'room_size': '45 sqm', + 'view': 'City View', + 'images': json.dumps([ + 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', + 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop' + ]), + 'amenities': json.dumps([ + 'Free WiFi', + 'Smart TV (Multiple)', + 'Air Conditioning', + 'Mini Bar', + 'Extra Beds', + 'Game Console', + 'Child Safety Features', + 'Family Amenities' + ]), + 'description': f'Spacious family-friendly room on floor 1 designed to accommodate families with children. Features extra beds and family amenities.' + }) + + return rooms + + +def seed_room_types(db: Session): + """Seed room types into the database""" + try: + room_types_data = get_room_types_data() + room_type_map = {} + + now = datetime.now(timezone.utc) + + for room_type_data in room_types_data: + existing = db.query(RoomType).filter(RoomType.name == room_type_data['name']).first() + + if existing: + logger.info(f"Room type '{room_type_data['name']}' already exists. Updating...") + for key, value in room_type_data.items(): + setattr(existing, key, value) + existing.updated_at = now + room_type_map[existing.name] = existing + else: + logger.info(f"Creating room type: {room_type_data['name']}") + room_type = RoomType( + **room_type_data, + created_at=now, + updated_at=now + ) + db.add(room_type) + db.flush() # Flush to get the ID + room_type_map[room_type.name] = room_type + + db.commit() + logger.info(f'Successfully seeded {len(room_type_map)} room types!') + return room_type_map + except Exception as e: + db.rollback() + logger.error(f'Error seeding room types: {str(e)}', exc_info=True) + raise + + +def seed_rooms(db: Session, room_type_map: dict, clear_existing: bool = False): + """Seed rooms into the database""" + try: + if clear_existing: + logger.info('Clearing existing rooms...') + db.query(Room).delete() + db.commit() + logger.info('Existing rooms cleared.') + + rooms_data = get_rooms_data(room_type_map) + now = datetime.now(timezone.utc) + + created_count = 0 + updated_count = 0 + + for room_data in rooms_data: + existing = db.query(Room).filter(Room.room_number == room_data['room_number']).first() + + if existing: + logger.debug(f"Room '{room_data['room_number']}' already exists. Updating...") + for key, value in room_data.items(): + setattr(existing, key, value) + existing.updated_at = now + updated_count += 1 + else: + logger.debug(f"Creating room: {room_data['room_number']}") + room = Room( + **room_data, + created_at=now, + updated_at=now + ) + db.add(room) + created_count += 1 + + db.commit() + logger.info(f'Successfully seeded rooms! Created: {created_count}, Updated: {updated_count}, Total: {len(rooms_data)}') + except Exception as e: + db.rollback() + logger.error(f'Error seeding rooms: {str(e)}', exc_info=True) + raise + + +def seed_rooms_and_types(db: Session, clear_existing: bool = False): + """Seed both room types and rooms""" + room_type_map = seed_room_types(db) + seed_rooms(db, room_type_map, clear_existing=clear_existing) + return room_type_map + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Seed room types and rooms') + parser.add_argument( + '--clear-rooms', + action='store_true', + help='Clear existing rooms before seeding (room types are updated, not cleared)' + ) + args = parser.parse_args() + + db = SessionLocal() + try: + seed_rooms_and_types(db, clear_existing=args.clear_rooms) + except Exception as e: + logger.error(f'Failed to seed rooms: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/run_seeder.py b/Backend/seeders/run_seeder.py new file mode 100755 index 00000000..ccdf470f --- /dev/null +++ b/Backend/seeders/run_seeder.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python3 +""" +Seeder Runner +Run all seeders or specific seeders +""" +import sys +import argparse +from pathlib import Path + +# Add parent directory to path +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from homepage_seeder import seed_homepage +from about_seeder import seed_about_page +from contact_seeder import seed_contact_page +from privacy_seeder import seed_privacy_page +from faq_seeder import seed_faq_page +from accessibility_seeder import seed_accessibility_page +from refunds_seeder import seed_refunds_page +from terms_seeder import seed_terms_page +from cancellation_seeder import seed_cancellation_page +from footer_seeder import seed_footer_page +from banner_seeder import seed_banners +from user_seeder import seed_users_and_roles +from room_seeder import seed_rooms_and_types +from service_seeder import seed_services +from blog_seeder import seed_blog_posts +from settings_seeder import seed_settings +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger + +logger = get_logger(__name__) + + +def main(): + parser = argparse.ArgumentParser(description='Run database seeders') + parser.add_argument( + '--seeder', + choices=['homepage', 'about', 'contact', 'privacy', 'faq', 'accessibility', 'refunds', 'terms', 'cancellation', 'footer', 'banners', 'users', 'rooms', 'services', 'blog', 'settings', 'all'], + default='all', + help='Which seeder to run (default: all)' + ) + parser.add_argument( + '--clear-banners', + action='store_true', + help='Clear existing banners before seeding (only applies to banners seeder)' + ) + parser.add_argument( + '--clear-rooms', + action='store_true', + help='Clear existing rooms before seeding (only applies to rooms seeder)' + ) + parser.add_argument( + '--clear-services', + action='store_true', + help='Clear existing services before seeding (only applies to services seeder)' + ) + parser.add_argument( + '--clear-blog', + action='store_true', + help='Clear existing blog posts before seeding (only applies to blog seeder)' + ) + parser.add_argument( + '--clear-settings', + action='store_true', + help='Clear existing settings before seeding (only applies to settings seeder)' + ) + parser.add_argument( + '--admin-email', + default='admin@hotel.com', + help='Admin user email (default: admin@hotel.com)' + ) + parser.add_argument( + '--admin-password', + default='admin123', + help='Admin user password (default: admin123)' + ) + parser.add_argument( + '--admin-name', + default='Admin User', + help='Admin user full name (default: Admin User)' + ) + + args = parser.parse_args() + + db = SessionLocal() + try: + # Always seed users/roles first if running all or users seeder + if args.seeder == 'users' or args.seeder == 'all': + logger.info('Running user and role seeder...') + seed_users_and_roles( + db, + admin_email=args.admin_email, + admin_password=args.admin_password, + admin_name=args.admin_name + ) + logger.info('User and role seeder completed!') + + if args.seeder == 'homepage' or args.seeder == 'all': + logger.info('Running homepage seeder...') + seed_homepage(db) + logger.info('Homepage seeder completed!') + + if args.seeder == 'about' or args.seeder == 'all': + logger.info('Running about page seeder...') + seed_about_page(db) + logger.info('About page seeder completed!') + + if args.seeder == 'contact' or args.seeder == 'all': + logger.info('Running contact page seeder...') + seed_contact_page(db) + logger.info('Contact page seeder completed!') + + if args.seeder == 'privacy' or args.seeder == 'all': + logger.info('Running privacy policy page seeder...') + seed_privacy_page(db) + logger.info('Privacy policy page seeder completed!') + + if args.seeder == 'faq' or args.seeder == 'all': + logger.info('Running FAQ page seeder...') + seed_faq_page(db) + logger.info('FAQ page seeder completed!') + + if args.seeder == 'accessibility' or args.seeder == 'all': + logger.info('Running accessibility page seeder...') + seed_accessibility_page(db) + logger.info('Accessibility page seeder completed!') + + if args.seeder == 'refunds' or args.seeder == 'all': + logger.info('Running refunds policy page seeder...') + seed_refunds_page(db) + logger.info('Refunds policy page seeder completed!') + + if args.seeder == 'terms' or args.seeder == 'all': + logger.info('Running terms of use page seeder...') + seed_terms_page(db) + logger.info('Terms of use page seeder completed!') + + if args.seeder == 'cancellation' or args.seeder == 'all': + logger.info('Running cancellation policy page seeder...') + seed_cancellation_page(db) + logger.info('Cancellation policy page seeder completed!') + + if args.seeder == 'footer' or args.seeder == 'all': + logger.info('Running footer page seeder...') + seed_footer_page(db) + logger.info('Footer page seeder completed!') + + if args.seeder == 'banners' or args.seeder == 'all': + logger.info('Running banner seeder...') + seed_banners(db, clear_existing=args.clear_banners) + logger.info('Banner seeder completed!') + + if args.seeder == 'rooms' or args.seeder == 'all': + logger.info('Running room seeder...') + seed_rooms_and_types(db, clear_existing=args.clear_rooms) + logger.info('Room seeder completed!') + + if args.seeder == 'services' or args.seeder == 'all': + logger.info('Running service seeder...') + seed_services(db, clear_existing=args.clear_services) + logger.info('Service seeder completed!') + + if args.seeder == 'blog' or args.seeder == 'all': + logger.info('Running blog seeder...') + seed_blog_posts(db, clear_existing=args.clear_blog) + logger.info('Blog seeder completed!') + + if args.seeder == 'settings' or args.seeder == 'all': + logger.info('Running settings seeder...') + seed_settings(db, clear_existing=args.clear_settings) + logger.info('Settings seeder completed!') + + logger.info('All seeders completed successfully!') + except Exception as e: + logger.error(f'Seeder failed: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/service_seeder.py b/Backend/seeders/service_seeder.py new file mode 100644 index 00000000..1ebf3b32 --- /dev/null +++ b/Backend/seeders/service_seeder.py @@ -0,0 +1,413 @@ +""" +Service Seeder +Seeds the database with hotel services +All images are from Unsplash (free stock photos) +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone +from decimal import Decimal +import re + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.hotel_services.models.service import Service + +# Import all models to ensure relationships are loaded correctly +from src.models import * + +logger = get_logger(__name__) + + +def slugify(text): + """Convert text to URL-friendly slug""" + text = text.lower() + text = re.sub(r'[^\w\s-]', '', text) + text = re.sub(r'[-\s]+', '-', text) + return text.strip('-') + + +def get_services_data(): + """Generate comprehensive services data with Unsplash images""" + now = datetime.now(timezone.utc) + + services = [ + # Spa & Wellness Services + { + 'name': 'Luxury Spa Treatment', + 'description': 'Indulge in our signature spa treatments designed to rejuvenate your mind, body, and soul. Experience ultimate relaxation with our expert therapists.', + 'price': Decimal('150.00'), + 'category': 'Spa & Wellness', + 'slug': 'luxury-spa-treatment', + 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop', + 'content': '

Our luxury spa treatments combine traditional techniques with modern wellness practices. Choose from a variety of massages, facials, and body treatments tailored to your needs.

', + 'sections': json.dumps([ + { + 'type': 'features', + 'title': 'Treatment Options', + 'content': 'Swedish Massage, Deep Tissue, Hot Stone, Aromatherapy, Facial Treatments', + 'alignment': 'left', + 'is_visible': True + } + ]), + 'meta_title': 'Luxury Spa Treatment | Hotel Spa Services', + 'meta_description': 'Experience ultimate relaxation with our luxury spa treatments. Expert therapists, premium products, and serene environment.', + 'meta_keywords': 'spa, massage, wellness, relaxation, hotel spa, luxury treatment', + 'is_active': True + }, + { + 'name': 'Couples Massage', + 'description': 'Share a relaxing experience with your partner in our private couples massage room. Perfect for romantic getaways and special occasions.', + 'price': Decimal('280.00'), + 'category': 'Spa & Wellness', + 'slug': 'couples-massage', + 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop', + 'content': '

Enjoy a synchronized massage experience with your loved one in our beautifully appointed couples suite. Includes champagne and chocolate-covered strawberries.

', + 'sections': None, + 'meta_title': 'Couples Massage | Romantic Spa Experience', + 'meta_description': 'Share a romantic spa experience with couples massage. Private suite, synchronized treatments, and special amenities included.', + 'meta_keywords': 'couples massage, romantic spa, couples treatment, hotel spa', + 'is_active': True + }, + { + 'name': 'Facial Treatment', + 'description': 'Rejuvenate your skin with our professional facial treatments using premium skincare products. Customized for your skin type.', + 'price': Decimal('120.00'), + 'category': 'Spa & Wellness', + 'slug': 'facial-treatment', + 'image': 'https://images.unsplash.com/photo-1570172619644-dfd03ed5d881?w=1200&h=800&fit=crop', + 'content': '

Our expert estheticians provide personalized facial treatments to address your specific skin concerns. Includes deep cleansing, exfoliation, and hydration.

', + 'sections': None, + 'meta_title': 'Facial Treatment | Professional Skincare Services', + 'meta_description': 'Professional facial treatments with premium products. Customized for your skin type by expert estheticians.', + 'meta_keywords': 'facial, skincare, spa facial, beauty treatment', + 'is_active': True + }, + + # Dining Services + { + 'name': 'Fine Dining Experience', + 'description': 'Savor exquisite cuisine at our award-winning restaurant. Chef-prepared meals with the finest ingredients and impeccable service.', + 'price': Decimal('200.00'), + 'category': 'Dining', + 'slug': 'fine-dining-experience', + 'image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1200&h=800&fit=crop', + 'content': '

Experience culinary excellence at our Michelin-starred restaurant. Our talented chefs create innovative dishes using locally sourced, seasonal ingredients.

', + 'sections': json.dumps([ + { + 'type': 'menu', + 'title': 'Signature Dishes', + 'content': 'Truffle Risotto, Wagyu Beef, Lobster Thermidor, Seasonal Tasting Menu', + 'alignment': 'center', + 'is_visible': True + } + ]), + 'meta_title': 'Fine Dining Restaurant | Award-Winning Cuisine', + 'meta_description': 'Experience award-winning fine dining with chef-prepared meals, premium ingredients, and exceptional service.', + 'meta_keywords': 'fine dining, restaurant, gourmet, Michelin, hotel restaurant', + 'is_active': True + }, + { + 'name': 'Room Service', + 'description': 'Enjoy delicious meals in the comfort of your room. Available 24/7 with an extensive menu of international and local cuisine.', + 'price': Decimal('50.00'), + 'category': 'Dining', + 'slug': 'room-service', + 'image': 'https://images.unsplash.com/photo-1551632811-5617803d319f?w=1200&h=800&fit=crop', + 'content': '

Our room service menu features breakfast, lunch, dinner, and late-night options. All dishes are prepared fresh and delivered to your room with professional service.

', + 'sections': None, + 'meta_title': '24/7 Room Service | In-Room Dining', + 'meta_description': 'Enjoy delicious meals in your room with our 24/7 room service. Extensive menu, fresh preparation, and professional delivery.', + 'meta_keywords': 'room service, in-room dining, hotel food delivery', + 'is_active': True + }, + { + 'name': 'Private Chef Service', + 'description': 'Experience gourmet dining in the privacy of your suite with a personal chef. Customized menus and intimate dining experience.', + 'price': Decimal('500.00'), + 'category': 'Dining', + 'slug': 'private-chef-service', + 'image': 'https://images.unsplash.com/photo-1577219491135-ce391730fd43?w=1200&h=800&fit=crop', + 'content': '

Our private chef service brings fine dining directly to your suite. Work with our chef to create a customized menu for your special occasion.

', + 'sections': None, + 'meta_title': 'Private Chef Service | In-Suite Dining', + 'meta_description': 'Enjoy a private chef experience in your suite. Customized menus, intimate dining, and exceptional culinary expertise.', + 'meta_keywords': 'private chef, in-suite dining, personal chef, luxury dining', + 'is_active': True + }, + + # Concierge Services + { + 'name': '24/7 Concierge Service', + 'description': 'Our dedicated concierge team is available around the clock to assist with restaurant reservations, event tickets, transportation, and more.', + 'price': Decimal('0.00'), + 'category': 'Concierge', + 'slug': 'concierge-service', + 'image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=800&fit=crop', + 'content': '

From restaurant reservations to exclusive experiences, our concierge team ensures your stay is seamless and memorable. Available 24/7 for all your needs.

', + 'sections': json.dumps([ + { + 'type': 'services', + 'title': 'Concierge Services', + 'content': 'Restaurant Reservations, Event Tickets, Transportation, City Tours, Special Occasions', + 'alignment': 'left', + 'is_visible': True + } + ]), + 'meta_title': '24/7 Concierge Service | Personal Assistance', + 'meta_description': 'Round-the-clock concierge service for restaurant reservations, tickets, transportation, and personalized assistance.', + 'meta_keywords': 'concierge, personal assistant, hotel concierge, guest services', + 'is_active': True + }, + { + 'name': 'Airport Transfer', + 'description': 'Enjoy seamless airport transfers in our luxury vehicles. Professional drivers and comfortable transportation to and from the airport.', + 'price': Decimal('80.00'), + 'category': 'Transportation', + 'slug': 'airport-transfer', + 'image': 'https://images.unsplash.com/photo-1583485088076-494435075764?w=1200&h=800&fit=crop', + 'content': '

Start and end your journey in comfort with our premium airport transfer service. Available for all major airports with luxury vehicles and professional drivers.

', + 'sections': None, + 'meta_title': 'Airport Transfer Service | Luxury Transportation', + 'meta_description': 'Premium airport transfer service with luxury vehicles and professional drivers. Seamless transportation to and from the airport.', + 'meta_keywords': 'airport transfer, transportation, airport shuttle, luxury car service', + 'is_active': True + }, + { + 'name': 'City Tour Service', + 'description': 'Discover the city with our guided tour service. Customized itineraries and expert local guides to show you the best attractions.', + 'price': Decimal('150.00'), + 'category': 'Concierge', + 'slug': 'city-tour-service', + 'image': 'https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=1200&h=800&fit=crop', + 'content': '

Explore the city with our professional tour guides. Choose from standard tours or customize your itinerary to visit your preferred attractions.

', + 'sections': None, + 'meta_title': 'City Tour Service | Guided Tours', + 'meta_description': 'Discover the city with our guided tour service. Expert guides, customized itineraries, and memorable experiences.', + 'meta_keywords': 'city tour, guided tour, sightseeing, local attractions', + 'is_active': True + }, + + # Business Services + { + 'name': 'Business Center Access', + 'description': 'Access our fully equipped business center with computers, printers, meeting rooms, and high-speed internet. Perfect for business travelers.', + 'price': Decimal('25.00'), + 'category': 'Business', + 'slug': 'business-center-access', + 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop', + 'content': '

Our business center provides all the facilities you need for productive work. Includes private workstations, printing services, and meeting spaces.

', + 'sections': None, + 'meta_title': 'Business Center | Professional Workspace', + 'meta_description': 'Fully equipped business center with computers, printers, meeting rooms, and high-speed internet for business travelers.', + 'meta_keywords': 'business center, workspace, meeting room, business facilities', + 'is_active': True + }, + { + 'name': 'Meeting Room Rental', + 'description': 'Host your business meetings in our state-of-the-art meeting rooms. Equipped with modern technology and professional amenities.', + 'price': Decimal('300.00'), + 'category': 'Business', + 'slug': 'meeting-room-rental', + 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop', + 'content': '

Our meeting rooms accommodate various group sizes and are equipped with AV equipment, high-speed internet, and catering options.

', + 'sections': None, + 'meta_title': 'Meeting Room Rental | Business Facilities', + 'meta_description': 'State-of-the-art meeting rooms with modern technology, AV equipment, and professional amenities for your business needs.', + 'meta_keywords': 'meeting room, conference room, business meeting, event space', + 'is_active': True + }, + + # Additional Services + { + 'name': 'Laundry & Dry Cleaning', + 'description': 'Professional laundry and dry cleaning services. Same-day service available for your convenience.', + 'price': Decimal('30.00'), + 'category': 'Housekeeping', + 'slug': 'laundry-dry-cleaning', + 'image': 'https://images.unsplash.com/photo-1582735689369-4fe89db7114c?w=1200&h=800&fit=crop', + 'content': '

Keep your wardrobe fresh with our professional laundry and dry cleaning services. Same-day service available for urgent needs.

', + 'sections': None, + 'meta_title': 'Laundry & Dry Cleaning Service', + 'meta_description': 'Professional laundry and dry cleaning services with same-day service available. Keep your wardrobe fresh during your stay.', + 'meta_keywords': 'laundry, dry cleaning, clothing service, hotel laundry', + 'is_active': True + }, + { + 'name': 'Fitness Center Access', + 'description': 'Access our state-of-the-art fitness center with modern equipment, personal trainers, and group fitness classes.', + 'price': Decimal('0.00'), + 'category': 'Fitness', + 'slug': 'fitness-center-access', + 'image': 'https://images.unsplash.com/photo-1534438747741-0bf23ca35f0d?w=1200&h=800&fit=crop', + 'content': '

Maintain your fitness routine in our fully equipped gym. Features cardio equipment, weight training, and personal training sessions available.

', + 'sections': None, + 'meta_title': 'Fitness Center | Hotel Gym Access', + 'meta_description': 'State-of-the-art fitness center with modern equipment, personal trainers, and group fitness classes. Stay fit during your stay.', + 'meta_keywords': 'fitness center, gym, workout, exercise, hotel gym', + 'is_active': True + }, + { + 'name': 'Personal Shopper', + 'description': 'Discover the city\'s best boutiques and shopping destinations with our expert personal shopper. Tailored shopping experiences.', + 'price': Decimal('200.00'), + 'category': 'Concierge', + 'slug': 'personal-shopper', + 'image': 'https://images.unsplash.com/photo-1528716321680-815a8cdb8bc7?w=1200&h=800&fit=crop', + 'content': '

Let our personal shopper guide you to the best shopping destinations. From luxury boutiques to local markets, we\'ll help you find exactly what you\'re looking for.

', + 'sections': None, + 'meta_title': 'Personal Shopper Service | Shopping Assistance', + 'meta_description': 'Expert personal shopper service to guide you to the best boutiques and shopping destinations. Tailored shopping experiences.', + 'meta_keywords': 'personal shopper, shopping service, boutique shopping, shopping guide', + 'is_active': True + }, + { + 'name': 'Valet Parking', + 'description': 'Complimentary valet parking service for all hotel guests. Secure parking with professional valet attendants.', + 'price': Decimal('0.00'), + 'category': 'Transportation', + 'slug': 'valet-parking', + 'image': 'https://images.unsplash.com/photo-1502877338535-766e1452684a?w=1200&h=800&fit=crop', + 'content': '

Enjoy the convenience of valet parking. Our professional attendants will safely park and retrieve your vehicle whenever needed.

', + 'sections': None, + 'meta_title': 'Valet Parking Service | Complimentary Parking', + 'meta_description': 'Complimentary valet parking service with professional attendants. Secure and convenient parking for all hotel guests.', + 'meta_keywords': 'valet parking, parking service, hotel parking', + 'is_active': True + }, + { + 'name': 'Butler Service', + 'description': 'Experience personalized butler service for suite guests. Available 24/7 to attend to your every need and ensure a flawless stay.', + 'price': Decimal('0.00'), + 'category': 'Concierge', + 'slug': 'butler-service', + 'image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=800&fit=crop', + 'content': '

Our dedicated butlers provide personalized service to suite guests. From unpacking to arranging special requests, we ensure every detail is perfect.

', + 'sections': None, + 'meta_title': 'Butler Service | Personalized Assistance', + 'meta_description': 'Personalized butler service for suite guests. Available 24/7 to attend to your every need and ensure a flawless stay.', + 'meta_keywords': 'butler service, personal butler, suite service, luxury service', + 'is_active': True + }, + { + 'name': 'Event Planning', + 'description': 'Let our expert event planners organize your special occasion. From intimate dinners to grand celebrations, we handle every detail.', + 'price': Decimal('500.00'), + 'category': 'Events', + 'slug': 'event-planning', + 'image': 'https://images.unsplash.com/photo-1519167758481-83f29da1c4fe?w=1200&h=800&fit=crop', + 'content': '

Our event planning team will work with you to create unforgettable celebrations. From venue selection to catering and entertainment, we manage it all.

', + 'sections': None, + 'meta_title': 'Event Planning Service | Special Occasions', + 'meta_description': 'Expert event planning service for special occasions. From intimate dinners to grand celebrations, we handle every detail.', + 'meta_keywords': 'event planning, party planning, special events, celebration planning', + 'is_active': True + }, + { + 'name': 'Photography Service', + 'description': 'Capture your special moments with our professional photography service. Available for events, portraits, and special occasions.', + 'price': Decimal('300.00'), + 'category': 'Events', + 'slug': 'photography-service', + 'image': 'https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=1200&h=800&fit=crop', + 'content': '

Our professional photographers will capture your special moments with artistic flair. Perfect for weddings, anniversaries, and other celebrations.

', + 'sections': None, + 'meta_title': 'Professional Photography Service', + 'meta_description': 'Professional photography service for events, portraits, and special occasions. Capture your special moments with artistic flair.', + 'meta_keywords': 'photography, photographer, event photography, portrait photography', + 'is_active': True + }, + { + 'name': 'Babysitting Service', + 'description': 'Professional babysitting service for families. Certified caregivers available to watch your children while you enjoy your stay.', + 'price': Decimal('50.00'), + 'category': 'Family', + 'slug': 'babysitting-service', + 'image': 'https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=1200&h=800&fit=crop', + 'content': '

Enjoy peace of mind with our professional babysitting service. All caregivers are certified and experienced in childcare.

', + 'sections': None, + 'meta_title': 'Babysitting Service | Childcare', + 'meta_description': 'Professional babysitting service with certified caregivers. Enjoy your stay while your children are safely cared for.', + 'meta_keywords': 'babysitting, childcare, kids service, family service', + 'is_active': True + } + ] + + return services + + +def seed_services(db: Session, clear_existing: bool = False): + """Seed services into the database""" + try: + if clear_existing: + logger.info('Clearing existing services...') + db.query(Service).delete() + db.commit() + logger.info('Existing services cleared.') + + services_data = get_services_data() + now = datetime.now(timezone.utc) + + created_count = 0 + updated_count = 0 + + for service_data in services_data: + # Generate slug if not provided + if not service_data.get('slug'): + service_data['slug'] = slugify(service_data['name']) + + existing = db.query(Service).filter(Service.slug == service_data['slug']).first() + + if existing: + logger.debug(f"Service '{service_data['name']}' already exists. Updating...") + for key, value in service_data.items(): + setattr(existing, key, value) + existing.updated_at = now + updated_count += 1 + else: + logger.debug(f"Creating service: {service_data['name']}") + service = Service( + **service_data, + created_at=now, + updated_at=now + ) + db.add(service) + created_count += 1 + + db.commit() + logger.info(f'Successfully seeded services! Created: {created_count}, Updated: {updated_count}, Total: {len(services_data)}') + except Exception as e: + db.rollback() + logger.error(f'Error seeding services: {str(e)}', exc_info=True) + raise + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Seed hotel services') + parser.add_argument( + '--clear', + action='store_true', + help='Clear existing services before seeding' + ) + args = parser.parse_args() + + db = SessionLocal() + try: + seed_services(db, clear_existing=args.clear) + except Exception as e: + logger.error(f'Failed to seed services: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/settings_seeder.py b/Backend/seeders/settings_seeder.py new file mode 100644 index 00000000..bdea4a0f --- /dev/null +++ b/Backend/seeders/settings_seeder.py @@ -0,0 +1,339 @@ +""" +Settings Seeder +Seeds the database with system settings +""" +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.system.models.system_settings import SystemSettings +from src.auth.models.user import User + +# Import all models to ensure relationships are loaded correctly +from src.models import * + +logger = get_logger(__name__) + + +def get_settings_data(): + """Generate comprehensive system settings data""" + + return [ + # Company Settings + { + 'key': 'company_name', + 'value': 'Luxury Hotel & Resort', + 'description': 'The official name of the hotel/company' + }, + { + 'key': 'company_tagline', + 'value': 'Experience Unparalleled Elegance and Comfort', + 'description': 'Company tagline or slogan' + }, + { + 'key': 'company_logo_url', + 'value': 'https://ui-avatars.com/api/?name=Luxury+Hotel&background=d4af37&color=fff&size=400&bold=true&font-size=0.5', + 'description': 'URL to the company logo image' + }, + { + 'key': 'company_favicon_url', + 'value': 'https://ui-avatars.com/api/?name=LH&background=d4af37&color=fff&size=64&bold=true', + 'description': 'URL to the company favicon image' + }, + { + 'key': 'company_phone', + 'value': '+1 (555) 123-4567', + 'description': 'Primary company phone number' + }, + { + 'key': 'company_email', + 'value': 'info@luxuryhotel.com', + 'description': 'Primary company email address' + }, + { + 'key': 'company_address', + 'value': '123 Luxury Avenue, Premium City, PC 12345, United States', + 'description': 'Company physical address' + }, + { + 'key': 'tax_rate', + 'value': '10.0', + 'description': 'Default tax rate percentage (e.g., 10.0 for 10%)' + }, + { + 'key': 'chat_working_hours_start', + 'value': '9', + 'description': 'Chat support working hours start (24-hour format, e.g., 9 for 9 AM)' + }, + { + 'key': 'chat_working_hours_end', + 'value': '18', + 'description': 'Chat support working hours end (24-hour format, e.g., 18 for 6 PM)' + }, + + # Platform Settings + { + 'key': 'platform_currency', + 'value': 'USD', + 'description': 'Default platform currency code (ISO 4217 format, e.g., USD, EUR, GBP)' + }, + + # Payment Gateway Settings (Placeholder values - should be configured in production) + { + 'key': 'stripe_secret_key', + 'value': '', + 'description': 'Stripe secret API key (configure in production)' + }, + { + 'key': 'stripe_publishable_key', + 'value': '', + 'description': 'Stripe publishable API key (configure in production)' + }, + { + 'key': 'stripe_webhook_secret', + 'value': '', + 'description': 'Stripe webhook secret for verifying webhook events (configure in production)' + }, + { + 'key': 'paypal_client_id', + 'value': '', + 'description': 'PayPal client ID (configure in production)' + }, + { + 'key': 'paypal_client_secret', + 'value': '', + 'description': 'PayPal client secret (configure in production)' + }, + { + 'key': 'paypal_mode', + 'value': 'sandbox', + 'description': 'PayPal mode: "sandbox" for testing, "live" for production' + }, + { + 'key': 'borica_terminal_id', + 'value': '', + 'description': 'Borica terminal ID (configure if using Borica payment gateway)' + }, + { + 'key': 'borica_merchant_id', + 'value': '', + 'description': 'Borica merchant ID (configure if using Borica payment gateway)' + }, + { + 'key': 'borica_private_key_path', + 'value': '', + 'description': 'Path to Borica private key file (configure if using Borica)' + }, + { + 'key': 'borica_certificate_path', + 'value': '', + 'description': 'Path to Borica certificate file (configure if using Borica)' + }, + { + 'key': 'borica_gateway_url', + 'value': '', + 'description': 'Borica gateway URL (configure if using Borica payment gateway)' + }, + { + 'key': 'borica_mode', + 'value': 'test', + 'description': 'Borica mode: "test" for testing, "production" for live transactions' + }, + + # SMTP Settings (Placeholder values - should be configured in production) + { + 'key': 'smtp_host', + 'value': '', + 'description': 'SMTP server hostname (e.g., smtp.gmail.com, smtp.sendgrid.net)' + }, + { + 'key': 'smtp_port', + 'value': '587', + 'description': 'SMTP server port (587 for STARTTLS, 465 for SSL)' + }, + { + 'key': 'smtp_user', + 'value': '', + 'description': 'SMTP authentication username/email' + }, + { + 'key': 'smtp_password', + 'value': '', + 'description': 'SMTP authentication password (stored securely)' + }, + { + 'key': 'smtp_from_email', + 'value': 'noreply@luxuryhotel.com', + 'description': 'Default "From" email address for outgoing emails' + }, + { + 'key': 'smtp_from_name', + 'value': 'Luxury Hotel & Resort', + 'description': 'Default "From" name for outgoing emails' + }, + { + 'key': 'smtp_use_tls', + 'value': 'true', + 'description': 'Use TLS/SSL for SMTP connection (true for port 465, false for port 587 with STARTTLS)' + }, + + # Security Settings + { + 'key': 'recaptcha_site_key', + 'value': '', + 'description': 'Google reCAPTCHA site key (configure if using reCAPTCHA)' + }, + { + 'key': 'recaptcha_secret_key', + 'value': '', + 'description': 'Google reCAPTCHA secret key (configure if using reCAPTCHA)' + }, + { + 'key': 'recaptcha_enabled', + 'value': 'false', + 'description': 'Enable/disable reCAPTCHA verification (true/false)' + }, + + # Additional Settings + { + 'key': 'booking_confirmation_email_enabled', + 'value': 'true', + 'description': 'Enable automatic booking confirmation emails (true/false)' + }, + { + 'key': 'booking_cancellation_email_enabled', + 'value': 'true', + 'description': 'Enable automatic booking cancellation emails (true/false)' + }, + { + 'key': 'newsletter_enabled', + 'value': 'true', + 'description': 'Enable newsletter subscription feature (true/false)' + }, + { + 'key': 'maintenance_mode', + 'value': 'false', + 'description': 'Enable maintenance mode (true/false)' + }, + { + 'key': 'maintenance_message', + 'value': 'We are currently performing scheduled maintenance. Please check back soon.', + 'description': 'Message to display when maintenance mode is enabled' + }, + { + 'key': 'default_checkin_time', + 'value': '15:00', + 'description': 'Default check-in time (24-hour format, e.g., 15:00 for 3 PM)' + }, + { + 'key': 'default_checkout_time', + 'value': '11:00', + 'description': 'Default check-out time (24-hour format, e.g., 11:00 for 11 AM)' + }, + { + 'key': 'cancellation_hours', + 'value': '24', + 'description': 'Number of hours before check-in that cancellation is allowed without penalty' + }, + { + 'key': 'max_guests_per_room', + 'value': '4', + 'description': 'Maximum number of guests allowed per room' + }, + { + 'key': 'min_booking_advance_days', + 'value': '0', + 'description': 'Minimum number of days in advance required for booking (0 = same day allowed)' + }, + { + 'key': 'max_booking_advance_days', + 'value': '365', + 'description': 'Maximum number of days in advance bookings can be made' + } + ] + + +def seed_settings(db: Session, clear_existing: bool = False, admin_user_id: int = None): + """Seed system settings into the database""" + try: + if clear_existing: + logger.info('Clearing existing system settings...') + db.query(SystemSettings).delete() + db.commit() + logger.info('Existing system settings cleared.') + + # Get admin user if not provided + if admin_user_id is None: + admin_user = db.query(User).filter(User.email == 'admin@hotel.com').first() + if admin_user: + admin_user_id = admin_user.id + else: + logger.warning('Admin user not found. Settings will be created without updated_by_id.') + + settings_data = get_settings_data() + now = datetime.now(timezone.utc) + + created_count = 0 + updated_count = 0 + + for setting_data in settings_data: + existing = db.query(SystemSettings).filter(SystemSettings.key == setting_data['key']).first() + + if existing: + logger.debug(f"Setting '{setting_data['key']}' already exists. Updating...") + existing.value = setting_data['value'] + existing.description = setting_data['description'] + if admin_user_id: + existing.updated_by_id = admin_user_id + existing.updated_at = now + updated_count += 1 + else: + logger.debug(f"Creating setting: {setting_data['key']}") + setting = SystemSettings( + key=setting_data['key'], + value=setting_data['value'], + description=setting_data['description'], + updated_by_id=admin_user_id, + updated_at=now + ) + db.add(setting) + created_count += 1 + + db.commit() + logger.info(f'Successfully seeded system settings! Created: {created_count}, Updated: {updated_count}, Total: {len(settings_data)}') + except Exception as e: + db.rollback() + logger.error(f'Error seeding system settings: {str(e)}', exc_info=True) + raise + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Seed system settings') + parser.add_argument( + '--clear', + action='store_true', + help='Clear existing settings before seeding' + ) + args = parser.parse_args() + + db = SessionLocal() + try: + seed_settings(db, clear_existing=args.clear) + except Exception as e: + logger.error(f'Failed to seed system settings: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/terms_seeder.py b/Backend/seeders/terms_seeder.py new file mode 100644 index 00000000..c836960a --- /dev/null +++ b/Backend/seeders/terms_seeder.py @@ -0,0 +1,178 @@ +""" +Terms of Use Page Seeder +Seeds the database with comprehensive terms of use content +""" +import json +import sys +from pathlib import Path +from datetime import datetime, timezone + +# Add parent directory to path to allow importing from src +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +from src.shared.config.logging_config import get_logger +from src.content.models.page_content import PageContent, PageType + +# Import all models to ensure relationships are loaded +from src.models import * + +logger = get_logger(__name__) + + +def get_terms_page_data(): + """Generate comprehensive terms of use page data""" + + now = datetime.now(timezone.utc) + + return { + 'page_type': PageType.TERMS, + 'title': 'Terms of Use', + 'subtitle': 'Terms and Conditions for Using Our Services', + 'description': 'Please read these terms and conditions carefully before using our website or services. By using our services, you agree to be bound by these terms.', + 'content': """ +

1. Acceptance of Terms

+

By accessing and using the Luxury Hotel & Resort website and services, you accept and agree to be bound by the terms and provision of this agreement. If you do not agree to these terms, please do not use our services.

+ +

2. Use of Website

+

2.1 Eligibility

+

You must be at least 18 years old to make a reservation or use our services. By using our website, you represent and warrant that you are at least 18 years of age and have the legal capacity to enter into this agreement.

+ +

2.2 User Account

+

If you create an account with us, you are responsible for maintaining the confidentiality of your account and password. You agree to accept responsibility for all activities that occur under your account.

+ +

2.3 Prohibited Uses

+

You agree not to use our website or services:

+ + +

3. Reservations and Bookings

+

3.1 Reservation Terms

+

When you make a reservation through our website or by phone, you agree to:

+ + +

3.2 Pricing

+

All prices are displayed in the currency specified and are subject to change without notice. We reserve the right to correct any pricing errors. Taxes and fees may apply and will be disclosed at the time of booking.

+ +

3.3 Payment

+

Payment is required at the time of booking or as specified in your reservation confirmation. We accept major credit cards and other payment methods as indicated on our website.

+ +

4. Cancellation and Refund Policy

+

Cancellation and refund terms vary by rate type and are specified at the time of booking. Please refer to your booking confirmation and our Refund Policy for detailed information.

+ +

5. Intellectual Property

+

The content on our website, including text, graphics, logos, images, and software, is the property of Luxury Hotel & Resort or its content suppliers and is protected by copyright and other intellectual property laws. You may not reproduce, distribute, modify, or create derivative works from any content without our express written permission.

+ +

6. User Content

+

If you submit content to our website (such as reviews, comments, or photos), you grant us a non-exclusive, royalty-free, perpetual, and worldwide license to use, reproduce, modify, and distribute such content for any purpose.

+ +

7. Limitation of Liability

+

To the fullest extent permitted by law, Luxury Hotel & Resort shall not be liable for any indirect, incidental, special, consequential, or punitive damages, or any loss of profits or revenues, whether incurred directly or indirectly, or any loss of data, use, goodwill, or other intangible losses resulting from your use of our services.

+ +

8. Indemnification

+

You agree to indemnify, defend, and hold harmless Luxury Hotel & Resort and its officers, directors, employees, and agents from any claims, damages, losses, liabilities, and expenses (including legal fees) arising out of or relating to your use of our services or violation of these terms.

+ +

9. Disclaimer of Warranties

+

Our services are provided "as is" and "as available" without warranties of any kind, either express or implied. We do not warrant that our services will be uninterrupted, secure, or error-free.

+ +

10. Third-Party Links

+

Our website may contain links to third-party websites. We are not responsible for the content, privacy policies, or practices of third-party websites. Your use of third-party websites is at your own risk.

+ +

11. Force Majeure

+

We shall not be liable for any failure or delay in performance under these terms which is due to circumstances beyond our reasonable control, including but not limited to natural disasters, war, terrorism, pandemics, government actions, or other force majeure events.

+ +

12. Governing Law

+

These terms shall be governed by and construed in accordance with the laws of the jurisdiction in which our hotel is located, without regard to its conflict of law provisions.

+ +

13. Dispute Resolution

+

Any disputes arising out of or relating to these terms or our services shall be resolved through binding arbitration in accordance with the rules of the applicable arbitration association, except where prohibited by law.

+ +

14. Changes to Terms

+

We reserve the right to modify these terms at any time. Changes will be effective immediately upon posting on our website. Your continued use of our services after changes are posted constitutes acceptance of the modified terms.

+ +

15. Severability

+

If any provision of these terms is found to be unenforceable or invalid, that provision shall be limited or eliminated to the minimum extent necessary, and the remaining provisions shall remain in full force and effect.

+ +

16. Entire Agreement

+

These terms constitute the entire agreement between you and Luxury Hotel & Resort regarding the use of our services and supersede all prior agreements and understandings.

+ +

17. Contact Information

+

If you have any questions about these Terms of Use, please contact us:

+ + +

Last Updated: {}

+ """.format(now.strftime('%B %d, %Y')), + 'meta_title': 'Terms of Use | Luxury Hotel & Resort - Terms and Conditions', + 'meta_description': 'Read our terms and conditions for using our website and services. By using our services, you agree to these terms.', + 'meta_keywords': 'terms of use, terms and conditions, user agreement, legal terms, hotel terms', + 'og_title': 'Terms of Use - Luxury Hotel & Resort', + 'og_description': 'Terms and conditions for using our website and services. Please read carefully before using our services.', + 'og_image': 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=1200&h=630&fit=crop', + 'canonical_url': 'https://luxuryhotel.com/terms', + 'is_active': True, + 'created_at': now, + 'updated_at': now + } + + +def seed_terms_page(db: Session): + """Seed terms of use page content into the database""" + try: + terms_data = get_terms_page_data() + + # Check if terms page content already exists + existing_content = db.query(PageContent).filter(PageContent.page_type == PageType.TERMS).first() + + if existing_content: + logger.info('Updating existing terms of use page content...') + for key, value in terms_data.items(): + if key not in ['id', 'page_type', 'created_at']: + setattr(existing_content, key, value) + existing_content.updated_at = datetime.now(timezone.utc) + else: + logger.info('Creating new terms of use page content...') + terms_page = PageContent(**terms_data) + db.add(terms_page) + + db.commit() + logger.info('Terms of use page content seeded successfully!') + except Exception as e: + db.rollback() + logger.error(f'Error seeding terms of use page: {str(e)}', exc_info=True) + raise + + +def main(): + db = SessionLocal() + try: + seed_terms_page(db) + except Exception as e: + logger.error(f'Failed to seed terms of use page: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeders/user_seeder.py b/Backend/seeders/user_seeder.py new file mode 100644 index 00000000..e235d10e --- /dev/null +++ b/Backend/seeders/user_seeder.py @@ -0,0 +1,198 @@ +""" +User and Role Seeder +Creates default roles and an admin user +""" +import sys +from pathlib import Path +from datetime import datetime, timezone +import bcrypt + +# Add parent directory to path to import modules +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from sqlalchemy.orm import Session +from src.shared.config.database import SessionLocal +# Import all models to ensure relationships are properly initialized +from src.models import * +from src.auth.models.role import Role +from src.auth.models.user import User +from src.shared.config.logging_config import get_logger + +logger = get_logger(__name__) + + +def get_roles_data(): + """Generate default roles data""" + return [ + { + 'name': 'admin', + 'description': 'Full system access with all administrative privileges' + }, + { + 'name': 'staff', + 'description': 'Hotel staff with operational access' + }, + { + 'name': 'customer', + 'description': 'Regular customer/guest user' + }, + { + 'name': 'accountant', + 'description': 'Financial management and accounting access' + }, + + { + 'name': 'housekeeping', + 'description': 'Housekeeping and room management access' + } + ] + + +def seed_roles(db: Session): + """Seed roles into the database""" + try: + roles_data = get_roles_data() + created_count = 0 + updated_count = 0 + role_map = {} + + for role_data in roles_data: + existing = db.query(Role).filter(Role.name == role_data['name']).first() + + if existing: + logger.info(f'Role already exists: {role_data["name"]}') + role_map[role_data['name']] = existing + else: + logger.info(f'Creating role: {role_data["name"]}') + role = Role(**role_data) + db.add(role) + db.flush() # Flush to get the ID + role_map[role_data['name']] = role + created_count += 1 + + db.commit() + logger.info(f'Role seeding completed! Created: {created_count}, Existing: {len(roles_data) - created_count}') + return role_map + + except Exception as e: + logger.error(f'Error seeding roles: {str(e)}', exc_info=True) + db.rollback() + raise + + +def seed_admin_user(db: Session, admin_role: Role, email: str = 'admin@hotel.com', password: str = 'admin123', full_name: str = 'Admin User'): + """Seed admin user into the database""" + try: + # Check if admin user already exists + existing = db.query(User).filter(User.email == email).first() + + if existing: + logger.info(f'Admin user already exists: {email}') + # Update role if needed + if existing.role_id != admin_role.id: + existing.role_id = admin_role.id + existing.updated_at = datetime.now(timezone.utc) + db.commit() + logger.info(f'Updated admin user role: {email}') + return existing + + # Create new admin user + logger.info(f'Creating admin user: {email}') + password_bytes = password.encode('utf-8') + salt = bcrypt.gensalt() + hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') + + admin_user = User( + email=email, + password=hashed_password, + full_name=full_name, + role_id=admin_role.id, + is_active=True, + phone=None, + address=None + ) + + db.add(admin_user) + db.commit() + db.refresh(admin_user) + logger.info(f'Admin user created successfully: {email}') + return admin_user + + except Exception as e: + logger.error(f'Error seeding admin user: {str(e)}', exc_info=True) + db.rollback() + raise + + +def seed_users_and_roles(db: Session, admin_email: str = 'admin@hotel.com', admin_password: str = 'admin123', admin_name: str = 'Admin User'): + """Seed roles and admin user""" + try: + # First, seed roles + role_map = seed_roles(db) + + # Get admin role + admin_role = role_map.get('admin') + if not admin_role: + raise ValueError('Admin role not found after seeding') + + # Seed admin user + admin_user = seed_admin_user(db, admin_role, admin_email, admin_password, admin_name) + + return role_map, admin_user + + except Exception as e: + logger.error(f'Error seeding users and roles: {str(e)}', exc_info=True) + raise + + +def main(): + """Main function to run the seeder""" + import argparse + + parser = argparse.ArgumentParser(description='Seed roles and admin user') + parser.add_argument( + '--email', + default='admin@hotel.com', + help='Admin user email (default: admin@hotel.com)' + ) + parser.add_argument( + '--password', + default='admin123', + help='Admin user password (default: admin123)' + ) + parser.add_argument( + '--name', + default='Admin User', + help='Admin user full name (default: Admin User)' + ) + + args = parser.parse_args() + + logger.info('Starting user and role seeder...') + + db = SessionLocal() + try: + role_map, admin_user = seed_users_and_roles( + db, + admin_email=args.email, + admin_password=args.password, + admin_name=args.name + ) + logger.info(f'User and role seeder completed successfully!') + logger.info(f'Admin user: {admin_user.email} (ID: {admin_user.id})') + logger.info(f'Roles created: {len(role_map)}') + print(f'\n✓ Admin user created successfully!') + print(f' Email: {admin_user.email}') + print(f' Password: {args.password}') + print(f' Role: admin') + print(f'\n⚠️ IMPORTANT: Change the default password after first login!') + except Exception as e: + logger.error(f'Failed to seed users and roles: {str(e)}', exc_info=True) + sys.exit(1) + finally: + db.close() + + +if __name__ == '__main__': + main() + diff --git a/Backend/seeds_data/__pycache__/add_accountant_role.cpython-312.pyc b/Backend/seeds_data/__pycache__/add_accountant_role.cpython-312.pyc deleted file mode 100644 index 788378bc..00000000 Binary files a/Backend/seeds_data/__pycache__/add_accountant_role.cpython-312.pyc and /dev/null differ diff --git a/Backend/seeds_data/__pycache__/seed_bookings.cpython-312.pyc b/Backend/seeds_data/__pycache__/seed_bookings.cpython-312.pyc deleted file mode 100644 index 61ca3ec7..00000000 Binary files a/Backend/seeds_data/__pycache__/seed_bookings.cpython-312.pyc and /dev/null differ diff --git a/Backend/seeds_data/__pycache__/seed_homepage_footer.cpython-312.pyc b/Backend/seeds_data/__pycache__/seed_homepage_footer.cpython-312.pyc deleted file mode 100644 index 81890ff4..00000000 Binary files a/Backend/seeds_data/__pycache__/seed_homepage_footer.cpython-312.pyc and /dev/null differ diff --git a/Backend/seeds_data/__pycache__/seed_initial_data.cpython-312.pyc b/Backend/seeds_data/__pycache__/seed_initial_data.cpython-312.pyc deleted file mode 100644 index 53a54772..00000000 Binary files a/Backend/seeds_data/__pycache__/seed_initial_data.cpython-312.pyc and /dev/null differ diff --git a/Backend/seeds_data/add_accountant_role.py b/Backend/seeds_data/add_accountant_role.py deleted file mode 100644 index 0fa82f70..00000000 --- a/Backend/seeds_data/add_accountant_role.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to add the 'accountant' role to the database. -Run this script once to create the accountant role if it doesn't exist. -""" - -import sys -from pathlib import Path - -# Add the Backend directory to the path -backend_dir = Path(__file__).parent -sys.path.insert(0, str(backend_dir)) - -from src.shared.config.database import SessionLocal -from src.models.role import Role - -def add_accountant_role(): - """Add the accountant role to the database if it doesn't exist.""" - db = SessionLocal() - try: - # Check if accountant role already exists - existing_role = db.query(Role).filter(Role.name == 'accountant').first() - - if existing_role: - print("✓ Accountant role already exists in the database.") - print(f" Role ID: {existing_role.id}") - print(f" Role Name: {existing_role.name}") - return - - # Create the accountant role - accountant_role = Role( - name='accountant', - description='Accountant role with access to financial data, payments, and invoices' - ) - db.add(accountant_role) - db.commit() - db.refresh(accountant_role) - - print("✓ Accountant role created successfully!") - print(f" Role ID: {accountant_role.id}") - print(f" Role Name: {accountant_role.name}") - print(f" Description: {accountant_role.description}") - - except Exception as e: - db.rollback() - print(f"✗ Error creating accountant role: {e}") - sys.exit(1) - finally: - db.close() - -if __name__ == '__main__': - print("Adding accountant role to the database...") - print("-" * 50) - add_accountant_role() - print("-" * 50) - print("Done!") - diff --git a/Backend/seeds_data/add_housekeeping_role.py b/Backend/seeds_data/add_housekeeping_role.py deleted file mode 100644 index 145aa247..00000000 --- a/Backend/seeds_data/add_housekeeping_role.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to add the 'housekeeping' role to the database. -Run this script once to create the housekeeping role if it doesn't exist. -""" - -import sys -import os -from pathlib import Path - -# Add the Backend directory to the path -backend_dir = Path(__file__).parent.parent -sys.path.insert(0, str(backend_dir)) - -from src.shared.config.database import SessionLocal -from src.models import Role # Use the models __init__ which handles all imports - -def add_housekeeping_role(): - """Add the housekeeping role to the database if it doesn't exist.""" - db = SessionLocal() - try: - # Check if housekeeping role already exists - existing_role = db.query(Role).filter(Role.name == 'housekeeping').first() - - if existing_role: - print("✓ Housekeeping role already exists in the database.") - print(f" Role ID: {existing_role.id}") - print(f" Role Name: {existing_role.name}") - return - - # Create the housekeeping role - housekeeping_role = Role( - name='housekeeping', - description='Housekeeping staff role with access to room cleaning tasks and status updates' - ) - db.add(housekeeping_role) - db.commit() - db.refresh(housekeeping_role) - - print("✓ Housekeeping role created successfully!") - print(f" Role ID: {housekeeping_role.id}") - print(f" Role Name: {housekeeping_role.name}") - print(f" Description: {housekeeping_role.description}") - - except Exception as e: - db.rollback() - print(f"✗ Error creating housekeeping role: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - finally: - db.close() - -if __name__ == '__main__': - print("Adding housekeeping role to the database...") - print("=" * 60) - add_housekeeping_role() - print("=" * 60) - print("Done!") diff --git a/Backend/seeds_data/add_housekeeping_user.py b/Backend/seeds_data/add_housekeeping_user.py deleted file mode 100644 index e43da954..00000000 --- a/Backend/seeds_data/add_housekeeping_user.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to add a housekeeping user to the database. -""" - -import sys -import os -from pathlib import Path - -# Add the Backend directory to the path -backend_dir = Path(__file__).parent.parent -sys.path.insert(0, str(backend_dir)) - -from src.shared.config.database import SessionLocal -from src.models import Role, User -import bcrypt - -def add_housekeeping_user(): - """Add the housekeeping user to the database if it doesn't exist.""" - db = SessionLocal() - try: - # Get housekeeping role - housekeeping_role = db.query(Role).filter(Role.name == 'housekeeping').first() - - if not housekeeping_role: - print("✗ Housekeeping role not found! Please run add_housekeeping_role.py first.") - sys.exit(1) - - # Check if user already exists - existing_user = db.query(User).filter(User.email == 'housekeeping@gnxsoft.com').first() - - if existing_user: - print("✓ Housekeeping user already exists in the database.") - print(f" User ID: {existing_user.id}") - print(f" Email: {existing_user.email}") - print(f" Full Name: {existing_user.full_name}") - print(f" Role: {existing_user.role.name if existing_user.role else 'N/A'}") - return - - # Hash password - password = 'P4eli240453.' - password_bytes = password.encode('utf-8') - salt = bcrypt.gensalt() - hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - - # Create the housekeeping user - housekeeping_user = User( - email='housekeeping@gnxsoft.com', - password=hashed_password, - full_name='Housekeeping Staff', - role_id=housekeeping_role.id, - is_active=True, - currency='EUR' - ) - db.add(housekeeping_user) - db.commit() - db.refresh(housekeeping_user) - - print("✓ Housekeeping user created successfully!") - print(f" User ID: {housekeeping_user.id}") - print(f" Email: {housekeeping_user.email}") - print(f" Full Name: {housekeeping_user.full_name}") - print(f" Role: {housekeeping_role.name}") - print(f" Password: P4eli240453.") - - except Exception as e: - db.rollback() - print(f"✗ Error creating housekeeping user: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - finally: - db.close() - -if __name__ == '__main__': - print("Adding housekeeping user to the database...") - print("=" * 60) - add_housekeeping_user() - print("=" * 60) - print("Done!") - diff --git a/Backend/seeds_data/assign_housekeeping_tasks.py b/Backend/seeds_data/assign_housekeeping_tasks.py deleted file mode 100644 index f0069047..00000000 --- a/Backend/seeds_data/assign_housekeeping_tasks.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to assign test housekeeping tasks to the housekeeping user. -This creates sample tasks for testing the housekeeping dashboard. -""" - -import sys -import os -from pathlib import Path -from datetime import datetime, timedelta - -# Add the Backend directory to the path -backend_dir = Path(__file__).parent.parent -sys.path.insert(0, str(backend_dir)) - -from src.shared.config.database import SessionLocal -from src.models import Role, User, Room -from src.hotel_services.models.housekeeping_task import HousekeepingTask, HousekeepingStatus, HousekeepingType - -def assign_housekeeping_tasks(): - """Assign test housekeeping tasks to the housekeeping user.""" - db = SessionLocal() - try: - # Get housekeeping user - housekeeping_user = db.query(User).join(Role).filter( - Role.name == 'housekeeping', - User.email == 'housekeeping@gnxsoft.com' - ).first() - - if not housekeeping_user: - print("✗ Housekeeping user not found! Please run add_housekeeping_user.py first.") - sys.exit(1) - - print(f"✓ Found housekeeping user: {housekeeping_user.email} (ID: {housekeeping_user.id})") - - # Get admin user for created_by - admin_role = db.query(Role).filter(Role.name == 'admin').first() - admin_user = db.query(User).filter(User.role_id == admin_role.id).first() if admin_role else None - - # Get some rooms - rooms = db.query(Room).limit(5).all() - - if not rooms: - print("✗ No rooms found in the database! Please seed rooms first.") - sys.exit(1) - - print(f"✓ Found {len(rooms)} rooms") - - # Default checklist items for different task types - checklists = { - 'checkout': [ - {'item': 'Bathroom cleaned', 'completed': False, 'notes': ''}, - {'item': 'Beds made with fresh linens', 'completed': False, 'notes': ''}, - {'item': 'Trash emptied', 'completed': False, 'notes': ''}, - {'item': 'Towels replaced', 'completed': False, 'notes': ''}, - {'item': 'Amenities restocked', 'completed': False, 'notes': ''}, - {'item': 'Floor vacuumed and mopped', 'completed': False, 'notes': ''}, - {'item': 'Surfaces dusted', 'completed': False, 'notes': ''}, - {'item': 'Windows and mirrors cleaned', 'completed': False, 'notes': ''}, - ], - 'stayover': [ - {'item': 'Beds made', 'completed': False, 'notes': ''}, - {'item': 'Trash emptied', 'completed': False, 'notes': ''}, - {'item': 'Towels replaced', 'completed': False, 'notes': ''}, - {'item': 'Bathroom cleaned', 'completed': False, 'notes': ''}, - ], - 'vacant': [ - {'item': 'Deep clean bathroom', 'completed': False, 'notes': ''}, - {'item': 'Change linens', 'completed': False, 'notes': ''}, - {'item': 'Vacuum and mop', 'completed': False, 'notes': ''}, - {'item': 'Dust surfaces', 'completed': False, 'notes': ''}, - {'item': 'Check amenities', 'completed': False, 'notes': ''}, - ], - 'inspection': [ - {'item': 'Check all amenities', 'completed': False, 'notes': ''}, - {'item': 'Test electronics', 'completed': False, 'notes': ''}, - {'item': 'Check for damages', 'completed': False, 'notes': ''}, - {'item': 'Verify cleanliness', 'completed': False, 'notes': ''}, - ], - } - - # Create tasks for today - today = datetime.utcnow().replace(hour=9, minute=0, second=0, microsecond=0) - task_types = ['checkout', 'stayover', 'vacant', 'inspection'] - - created_count = 0 - skipped_count = 0 - - for i, room in enumerate(rooms): - # Cycle through task types - task_type = task_types[i % len(task_types)] - - # Check if task already exists for this room today - existing_task = db.query(HousekeepingTask).filter( - HousekeepingTask.room_id == room.id, - HousekeepingTask.assigned_to == housekeeping_user.id, - HousekeepingTask.status == HousekeepingStatus.pending, - HousekeepingTask.scheduled_time >= today.replace(hour=0, minute=0), - HousekeepingTask.scheduled_time < today.replace(hour=23, minute=59, second=59) - ).first() - - if existing_task: - print(f" ⚠️ Task already exists for Room {room.room_number}, skipping...") - skipped_count += 1 - continue - - # Schedule tasks at different times throughout the day - scheduled_time = today + timedelta(hours=i) - - task = HousekeepingTask( - room_id=room.id, - booking_id=None, - task_type=HousekeepingType(task_type), - status=HousekeepingStatus.pending, - scheduled_time=scheduled_time, - assigned_to=housekeeping_user.id, - created_by=admin_user.id if admin_user else housekeeping_user.id, - checklist_items=checklists.get(task_type, []), - notes=f'Test task for Room {room.room_number} - {task_type} cleaning', - estimated_duration_minutes=45 if task_type == 'checkout' else 30 - ) - - db.add(task) - created_count += 1 - print(f" ✓ Created {task_type} task for Room {room.room_number} (scheduled: {scheduled_time.strftime('%Y-%m-%d %H:%M')})") - - db.commit() - - print(f"\n✓ Tasks assigned successfully!") - print(f" - Created: {created_count} task(s)") - print(f" - Skipped: {skipped_count} task(s) (already exist)") - print(f" - Assigned to: {housekeeping_user.email}") - - except Exception as e: - db.rollback() - print(f"✗ Error assigning housekeeping tasks: {e}") - import traceback - traceback.print_exc() - sys.exit(1) - finally: - db.close() - -if __name__ == '__main__': - print("Assigning test housekeeping tasks to housekeeping user...") - print("=" * 60) - assign_housekeeping_tasks() - print("=" * 60) - print("Done!") - diff --git a/Backend/seeds_data/fix_blog_dates.py b/Backend/seeds_data/fix_blog_dates.py deleted file mode 100644 index b2f19ab8..00000000 --- a/Backend/seeds_data/fix_blog_dates.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Fix blog post published_at dates to be in the past -""" -import sys -from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.blog import BlogPost -from datetime import datetime, timedelta - -def fix_blog_dates(): - """Update all blog posts to have published_at dates in the past""" - db: Session = SessionLocal() - - try: - # Get all published posts - posts = db.query(BlogPost).filter(BlogPost.is_published == True).order_by(BlogPost.created_at.asc()).all() - - if not posts: - print("No published posts found.") - return - - # Set base date to 60 days ago - base_date = datetime.utcnow() - timedelta(days=60) - - updated = 0 - for i, post in enumerate(posts): - # Set each post's date going backwards from base_date - # Each post is 2 days earlier than the previous one - new_date = base_date - timedelta(days=i * 2) - post.published_at = new_date - updated += 1 - - db.commit() - print(f"Successfully updated {updated} blog posts with past published_at dates") - - except Exception as e: - db.rollback() - print(f"Error fixing blog dates: {str(e)}") - raise - finally: - db.close() - -if __name__ == "__main__": - print("Fixing blog post published_at dates...") - fix_blog_dates() - print("Done!") - diff --git a/Backend/seeds_data/seed_about_page.py b/Backend/seeds_data/seed_about_page.py deleted file mode 100644 index ee0b88f6..00000000 --- a/Backend/seeds_data/seed_about_page.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os -from pathlib import Path -import json - -sys.path.insert(0, str(Path(__file__).parent)) - -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.page_content import PageContent, PageType -from datetime import datetime - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_about_page(db: Session): - print("=" * 80) - print("SEEDING ABOUT PAGE CONTENT") - print("=" * 80) - - about_data = { - "title": "About Luxury Hotel", - "subtitle": "Where Excellence Meets Unforgettable Experiences", - "description": "Discover the story behind our commitment to luxury hospitality and exceptional service.", - "story_content": "For over three decades, Luxury Hotel has been a beacon of excellence in the hospitality industry. Founded with a vision to redefine luxury travel, we have grown from a single property to a collection of world-renowned destinations, each offering a unique blend of timeless elegance and modern sophistication. Our journey has been marked by countless awards, memorable moments, and the unwavering trust of our guests who return year after year.", - "mission": "To provide unparalleled luxury hospitality experiences that exceed expectations, creating lasting memories for our guests through exceptional service, attention to detail, and genuine care.", - "vision": "To be recognized as the world's premier luxury hotel brand, setting the standard for excellence in hospitality while maintaining our commitment to sustainability and community engagement.", - "about_hero_image": "https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1920&h=1080&fit=crop", - "values": json.dumps([ - { - "icon": "Heart", - "title": "Passion", - "description": "We are passionate about hospitality and dedicated to creating exceptional experiences for every guest." - }, - { - "icon": "Award", - "title": "Excellence", - "description": "We strive for excellence in every aspect of our service, from the smallest detail to the grandest gesture." - }, - { - "icon": "Shield", - "title": "Integrity", - "description": "We conduct our business with honesty, transparency, and respect for our guests and community." - }, - { - "icon": "Users", - "title": "Service", - "description": "Our guests are at the heart of everything we do. Your comfort and satisfaction are our top priorities." - } - ]), - "features": json.dumps([ - { - "icon": "Star", - "title": "Premium Accommodations", - "description": "Luxuriously appointed rooms and suites designed for ultimate comfort and relaxation." - }, - { - "icon": "Clock", - "title": "24/7 Service", - "description": "Round-the-clock concierge and room service to attend to your needs at any time." - }, - { - "icon": "Award", - "title": "Award-Winning", - "description": "Recognized for excellence in hospitality and guest satisfaction." - } - ]), - "team": json.dumps([ - { - "name": "Sarah Johnson", - "role": "General Manager", - "image": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=400&fit=crop", - "bio": "With over 15 years of experience in luxury hospitality, Sarah leads our team with passion and dedication.", - "social_links": { - "linkedin": "https://linkedin.com/in/sarahjohnson", - "twitter": "https://twitter.com/sarahjohnson" - } - }, - { - "name": "Michael Chen", - "role": "Head Chef", - "image": "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop", - "bio": "Award-winning chef with expertise in international cuisine, bringing world-class dining experiences to our guests.", - "social_links": { - "linkedin": "https://linkedin.com/in/michaelchen", - "twitter": "https://twitter.com/michaelchen" - } - }, - { - "name": "Emily Rodriguez", - "role": "Guest Relations Manager", - "image": "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop", - "bio": "Ensuring every guest feels valued and receives personalized attention throughout their stay.", - "social_links": { - "linkedin": "https://linkedin.com/in/emilyrodriguez" - } - } - ]), - "timeline": json.dumps([ - { - "year": "2010", - "title": "Grand Opening", - "description": "Luxury Hotel opened its doors, welcoming guests to a new standard of luxury hospitality.", - "image": "https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800&h=600&fit=crop" - }, - { - "year": "2015", - "title": "First Award", - "description": "Received our first 'Best Luxury Hotel' award, recognizing our commitment to excellence.", - "image": "https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800&h=600&fit=crop" - }, - { - "year": "2018", - "title": "Major Renovation", - "description": "Completed a comprehensive renovation, adding state-of-the-art facilities and expanding our capacity.", - "image": "https://images.unsplash.com/photo-1590490360182-c33d57733427?w=800&h=600&fit=crop" - }, - { - "year": "2020", - "title": "Sustainability Initiative", - "description": "Launched our sustainability program, committing to eco-friendly practices and community engagement.", - "image": "https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=800&h=600&fit=crop" - }, - { - "year": "2023", - "title": "International Recognition", - "description": "Achieved international recognition as one of the world's top luxury hotels.", - "image": "https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=800&h=600&fit=crop" - } - ]), - "achievements": json.dumps([ - { - "icon": "Award", - "title": "Best Luxury Hotel 2023", - "description": "Recognized as the best luxury hotel in the region for exceptional service and amenities.", - "year": "2023", - "image": "https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=400&h=300&fit=crop" - }, - { - "icon": "Star", - "title": "5-Star Rating", - "description": "Maintained our prestigious 5-star rating for over a decade, a testament to our consistent excellence.", - "year": "2022", - "image": "https://images.unsplash.com/photo-1566073771259-6a8506099945?w=400&h=300&fit=crop" - }, - { - "icon": "Award", - "title": "Sustainable Hotel of the Year", - "description": "Awarded for our commitment to environmental sustainability and green practices.", - "year": "2021", - "image": "https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=400&h=300&fit=crop" - }, - { - "icon": "Users", - "title": "Guest Satisfaction Excellence", - "description": "Achieved 98% guest satisfaction rate, the highest in our category.", - "year": "2023", - "image": "https://images.unsplash.com/photo-1590490360182-c33d57733427?w=400&h=300&fit=crop" - } - ]), - "meta_title": "About Us - Luxury Hotel | Our Story, Mission & Vision", - "meta_description": "Learn about Luxury Hotel's commitment to excellence, our story, values, and the dedicated team that makes every stay unforgettable." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.ABOUT).first() - - if existing: - for key, value in about_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing about page content") - else: - new_content = PageContent( - page_type=PageType.ABOUT, - **about_data - ) - db.add(new_content) - print("✓ Created new about page content") - - db.commit() - print("\n✅ About page content seeded successfully!") - print("=" * 80) - -if __name__ == "__main__": - db = get_db() - try: - seed_about_page(db) - except Exception as e: - print(f"\n❌ Error: {e}") - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - diff --git a/Backend/seeds_data/seed_all_test_data.py b/Backend/seeds_data/seed_all_test_data.py deleted file mode 100755 index 116af888..00000000 --- a/Backend/seeds_data/seed_all_test_data.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python3 -""" -Master script to seed all test data for the hotel booking system. -This script runs all necessary seed scripts in the correct order. -""" - -import sys -import os -from pathlib import Path - -# Add the Backend directory to the path -backend_dir = Path(__file__).parent.parent -sys.path.insert(0, str(backend_dir)) -# Also add the seeds_data directory like other seed scripts -sys.path.insert(0, str(Path(__file__).parent)) - -import bcrypt - -def ensure_housekeeping_role(db): - """Ensure housekeeping role exists""" - from src.models.role import Role - housekeeping_role = db.query(Role).filter(Role.name == 'housekeeping').first() - if not housekeeping_role: - print('Creating housekeeping role...') - housekeeping_role = Role(name='housekeeping', description='Housekeeping staff role') - db.add(housekeeping_role) - db.commit() - db.refresh(housekeeping_role) - print('✓ Housekeeping role created') - return housekeeping_role - -def ensure_housekeeping_users(db): - """Ensure housekeeping users exist""" - from src.models.role import Role - from src.models.user import User - housekeeping_role = db.query(Role).filter(Role.name == 'housekeeping').first() - if not housekeeping_role: - print('❌ Housekeeping role not found!') - return - - housekeeping_users = [ - { - 'email': 'housekeeping@gnxsoft.com', - 'password': 'housekeeping123', - 'full_name': 'Housekeeping Staff', - 'phone': '+1 (555) 999-0000' - }, - { - 'email': 'housekeeping2@gnxsoft.com', - 'password': 'housekeeping123', - 'full_name': 'Housekeeping Staff 2', - 'phone': '+1 (555) 999-0001' - } - ] - - for user_data in housekeeping_users: - existing = db.query(User).filter(User.email == user_data['email']).first() - if not existing: - password_bytes = user_data['password'].encode('utf-8') - salt = bcrypt.gensalt() - hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - - user = User( - email=user_data['email'], - password=hashed_password, - full_name=user_data['full_name'], - phone=user_data['phone'], - role_id=housekeeping_role.id, - currency='EUR', - is_active=True - ) - db.add(user) - print(f' ✓ Created housekeeping user: {user_data["email"]} - Password: {user_data["password"]}') - else: - print(f' ⚠️ Housekeeping user "{user_data["email"]}" already exists') - - db.commit() - -def main(): - print('=' * 80) - print('SEEDING ALL TEST DATA FOR HOTEL BOOKING SYSTEM') - print('=' * 80) - print() - - from src.shared.config.database import SessionLocal - db = SessionLocal() - try: - # Step 1: Ensure housekeeping role exists - print('Step 1: Ensuring housekeeping role exists...') - ensure_housekeeping_role(db) - print() - - # Step 2: Ensure housekeeping users exist - print('Step 2: Ensuring housekeeping users exist...') - ensure_housekeeping_users(db) - print() - - # Step 3: Import and run seed scripts - print('Step 3: Running seed scripts...') - print() - - # Import seed modules - from seeds_data.seed_initial_data import seed_roles, seed_room_types, seed_admin_user - from seeds_data.seed_users import seed_users - from seeds_data.seed_rooms import seed_rooms - from seeds_data.seed_bookings import seed_bookings - - # Run seed scripts - print('Running seed_initial_data...') - seed_initial_data.seed_roles(db) - seed_initial_data.seed_room_types(db) - seed_initial_data.seed_admin_user(db) - print() - - print('Running seed_users...') - seed_users_module.seed_users(db) - print() - - print('Running seed_rooms...') - seed_rooms_module.seed_rooms(db) - print() - - print('Running seed_bookings...') - seed_bookings_module.seed_bookings(db) - print() - - print('=' * 80) - print('✅ ALL TEST DATA SEEDED SUCCESSFULLY!') - print('=' * 80) - print() - print('📋 Test Accounts:') - print(' Staff: staff@gnxsoft.com / staff123') - print(' Housekeeping: housekeeping@gnxsoft.com / housekeeping123') - print(' Housekeeping 2: housekeeping2@gnxsoft.com / housekeeping123') - print(' Customer: customer@gnxsoft.com / customer123') - print() - print('🧪 To test notifications:') - print(' 1. Log in as staff (staff@gnxsoft.com)') - print(' 2. Go to Bookings and mark a checked_in booking as checked_out') - print(' 3. Log in as housekeeping user in another browser/tab') - print(' 4. You should receive a real-time notification about the room needing cleaning') - print('=' * 80) - - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_banners_company.py b/Backend/seeds_data/seed_banners_company.py deleted file mode 100644 index a603aff8..00000000 --- a/Backend/seeds_data/seed_banners_company.py +++ /dev/null @@ -1,70 +0,0 @@ -import sys -import os -from datetime import datetime, timedelta -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.banner import Banner -from src.models.system_settings import SystemSettings -from src.models.user import User - -def seed_banners(db: Session): - print('Seeding banners...') - admin_user = db.query(User).filter(User.email == 'admin@hotel.com').first() - admin_id = admin_user.id if admin_user else None - existing_banners = db.query(Banner).all() - if existing_banners: - for banner in existing_banners: - db.delete(banner) - db.commit() - print(f' ✓ Removed {len(existing_banners)} existing banner(s)') - banners_data = [{'title': 'Welcome to Unparalleled Luxury', 'description': 'Where timeless elegance meets modern sophistication. Experience the pinnacle of hospitality in our award-winning luxury hotel.', 'image_url': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1920', 'link_url': '/rooms', 'position': 'home', 'display_order': 1, 'is_active': True, 'start_date': datetime.utcnow() - timedelta(days=30), 'end_date': datetime.utcnow() + timedelta(days=365)}, {'title': 'Exclusive Presidential Suites', 'description': 'Indulge in our most opulent accommodations. Spacious suites with panoramic views, private terraces, and personalized butler service.', 'image_url': 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1920', 'link_url': '/rooms', 'position': 'home', 'display_order': 2, 'is_active': True, 'start_date': datetime.utcnow() - timedelta(days=7), 'end_date': datetime.utcnow() + timedelta(days=365)}, {'title': 'World-Class Spa & Wellness', 'description': 'Rejuvenate your mind, body, and soul. Our award-winning spa offers bespoke treatments using the finest luxury products.', 'image_url': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1920', 'link_url': '/services', 'position': 'home', 'display_order': 3, 'is_active': True, 'start_date': datetime.utcnow() - timedelta(days=1), 'end_date': datetime.utcnow() + timedelta(days=365)}, {'title': 'Michelin-Starred Culinary Excellence', 'description': 'Savor extraordinary flavors crafted by world-renowned chefs. Our fine dining restaurants offer an unforgettable gastronomic journey.', 'image_url': 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=1920', 'link_url': '/services', 'position': 'home', 'display_order': 4, 'is_active': True, 'start_date': datetime.utcnow(), 'end_date': datetime.utcnow() + timedelta(days=365)}, {'title': 'Private Yacht & Exclusive Experiences', 'description': 'Create unforgettable memories with our curated luxury experiences. From private yacht charters to exclusive cultural tours.', 'image_url': 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=1920', 'link_url': '/services', 'position': 'home', 'display_order': 5, 'is_active': True, 'start_date': datetime.utcnow() - timedelta(days=15), 'end_date': datetime.utcnow() + timedelta(days=365)}] - for banner_data in banners_data: - new_banner = Banner(**banner_data) - db.add(new_banner) - print(f' ✓ Created banner: {banner_data['title']}') - db.commit() - print('✓ Banners seeded successfully!\n') - -def seed_company_info(db: Session): - print('Seeding company information...') - admin_user = db.query(User).filter(User.email == 'admin@hotel.com').first() - admin_id = admin_user.id if admin_user else None - company_settings = [{'key': 'company_name', 'value': 'Luxury Hotel', 'description': 'Company name displayed throughout the application'}, {'key': 'company_tagline', 'value': 'Experience Unparalleled Elegance', 'description': 'Company tagline or slogan'}, {'key': 'company_logo_url', 'value': '', 'description': 'URL to company logo image (upload via admin dashboard)'}, {'key': 'company_favicon_url', 'value': '', 'description': 'URL to company favicon image (upload via admin dashboard)'}, {'key': 'company_phone', 'value': '+1 (555) 123-4567', 'description': 'Company contact phone number'}, {'key': 'company_email', 'value': 'info@luxuryhotel.com', 'description': 'Company contact email address'}, {'key': 'company_address', 'value': '123 Luxury Avenue, Premium District, City 12345, Country', 'description': 'Company physical address'}, {'key': 'tax_rate', 'value': '10.0', 'description': 'Default tax rate percentage (e.g., 10.0 for 10%)'}, {'key': 'platform_currency', 'value': 'EUR', 'description': 'Platform-wide currency setting for displaying prices'}] - for setting_data in company_settings: - existing = db.query(SystemSettings).filter(SystemSettings.key == setting_data['key']).first() - if existing: - existing.value = setting_data['value'] - existing.description = setting_data['description'] - if admin_id: - existing.updated_by_id = admin_id - print(f' ✓ Updated setting: {setting_data['key']}') - else: - new_setting = SystemSettings(key=setting_data['key'], value=setting_data['value'], description=setting_data['description'], updated_by_id=admin_id) - db.add(new_setting) - print(f' ✓ Created setting: {setting_data['key']}') - db.commit() - print('✓ Company information seeded successfully!\n') - -def main(): - db: Session = SessionLocal() - try: - print('=' * 80) - print('SEEDING BANNERS AND COMPANY INFORMATION') - print('=' * 80) - print() - seed_banners(db) - seed_company_info(db) - print('=' * 80) - print('✓ All data seeded successfully!') - print('=' * 80) - except Exception as e: - db.rollback() - print(f'\n✗ Error seeding data: {e}') - import traceback - traceback.print_exc() - raise - finally: - db.close() -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/Backend/seeds_data/seed_blog_posts.py b/Backend/seeds_data/seed_blog_posts.py deleted file mode 100644 index d836acc9..00000000 --- a/Backend/seeds_data/seed_blog_posts.py +++ /dev/null @@ -1,1262 +0,0 @@ -""" -Seed script for blog posts with sample data from Unsplash -Run this script to populate the database with sample blog posts -""" -import sys -import os -from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.blog import BlogPost -from src.models.user import User -from src.models.role import Role -from datetime import datetime, timedelta -import json - -# Sample blog posts with Unsplash images -BLOG_POSTS = [ - { - "title": "10 Essential Tips for a Perfect Hotel Stay", - "slug": "10-essential-tips-perfect-hotel-stay", - "excerpt": "Discover insider secrets to make your hotel experience unforgettable. From booking strategies to room selection, we share expert tips.", - "content": """ -

Introduction

-

Planning a hotel stay? Whether you're traveling for business or pleasure, these essential tips will help you make the most of your experience.

- -

1. Book Directly with the Hotel

-

Booking directly with the hotel often provides better rates, room upgrades, and exclusive perks. Plus, you'll have direct communication with the property.

- -

2. Join the Loyalty Program

-

Most hotels offer loyalty programs that provide points, discounts, and special benefits. Sign up before your stay to start earning rewards.

- -

3. Communicate Special Requests Early

-

Whether you need a late checkout, extra pillows, or have dietary restrictions, communicate your needs when booking or at least 24 hours before arrival.

- -

4. Check-In Online

-

Many hotels now offer online check-in, saving you time at the front desk. You can often select your room and receive your key digitally.

- -

5. Explore Hotel Amenities

-

Take advantage of hotel amenities like the spa, fitness center, pool, and restaurants. These are often included in your stay or available at discounted rates.

- -

6. Be Polite to Staff

-

A friendly attitude goes a long way. Hotel staff are more likely to help with special requests or upgrades if you're courteous and respectful.

- -

7. Use the Concierge

-

The concierge can provide local recommendations, make reservations, and help with transportation. They're a valuable resource for making your stay memorable.

- -

8. Review Your Bill

-

Before checking out, review your bill carefully. Check for any unexpected charges and ensure all services match what you received.

- -

9. Leave a Review

-

Share your experience by leaving a review. This helps future guests and provides valuable feedback to the hotel.

- -

10. Stay Connected

-

Follow the hotel on social media for special offers, events, and updates. You might discover exclusive deals or experiences.

- -

Conclusion

-

By following these tips, you'll enhance your hotel stay and create lasting memories. Remember, a great hotel experience is about more than just a comfortable bed—it's about the entire journey.

- """, - "featured_image": "https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop", - "tags": ["Travel Tips", "Hotel Guide", "Hospitality"], - "meta_title": "10 Essential Tips for a Perfect Hotel Stay | Luxury Hotel Blog", - "meta_description": "Discover expert tips for making your hotel stay unforgettable. From booking strategies to maximizing amenities, learn how to get the most from your hotel experience.", - "meta_keywords": "hotel tips, travel guide, hotel stay, hospitality, travel advice" - }, - { - "title": "The Art of Luxury Hospitality: What Sets Premium Hotels Apart", - "slug": "art-of-luxury-hospitality-premium-hotels", - "excerpt": "Explore the defining characteristics of luxury hotels and what makes them stand out in the hospitality industry.", - "content": """ -

Introduction

-

Luxury hospitality is an art form that goes beyond beautiful decor and premium amenities. It's about creating unforgettable experiences that touch every aspect of a guest's journey.

- -

Personalized Service

-

True luxury hotels excel at personalization. From remembering guest preferences to anticipating needs, personalized service creates a sense of being truly valued.

- -

Attention to Detail

-

Every element matters in luxury hospitality. From the thread count of sheets to the temperature of the pool, attention to detail ensures perfection at every turn.

- -

Exceptional Design

-

Luxury hotels are architectural and design masterpieces. They blend local culture with contemporary elegance, creating spaces that inspire and delight.

- -

World-Class Dining

-

Fine dining is a cornerstone of luxury hospitality. Award-winning restaurants, celebrity chefs, and exceptional culinary experiences define premium hotels.

- -

Exclusive Amenities

-

From private butlers to exclusive spa treatments, luxury hotels offer amenities that go beyond the ordinary, creating unique and memorable experiences.

- -

Conclusion

-

Luxury hospitality is about creating moments that guests will remember forever. It's the combination of exceptional service, attention to detail, and genuine care that sets premium hotels apart.

- """, - "featured_image": "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=1200&h=800&fit=crop", - "tags": ["Luxury", "Hospitality", "Premium"], - "meta_title": "The Art of Luxury Hospitality | Premium Hotel Features", - "meta_description": "Discover what makes luxury hotels exceptional. Learn about personalized service, attention to detail, and the elements that define premium hospitality.", - "meta_keywords": "luxury hotels, premium hospitality, luxury travel, hotel design, fine dining" - }, - { - "title": "Sustainable Tourism: How Hotels Are Going Green", - "slug": "sustainable-tourism-hotels-going-green", - "excerpt": "Learn how modern hotels are implementing sustainable practices to protect the environment while providing exceptional guest experiences.", - "content": """ -

Introduction

-

Sustainable tourism is no longer a trend—it's a necessity. Hotels worldwide are implementing eco-friendly practices to reduce their environmental impact while maintaining luxury standards.

- -

Energy Efficiency

-

Modern hotels are investing in renewable energy, LED lighting, and smart systems that reduce energy consumption without compromising guest comfort.

- -

Water Conservation

-

From low-flow fixtures to water recycling systems, hotels are finding innovative ways to conserve water while maintaining the luxury experience guests expect.

- -

Waste Reduction

-

Comprehensive recycling programs, composting, and reducing single-use plastics are becoming standard practices in environmentally conscious hotels.

- -

Local Sourcing

-

Supporting local communities through sourcing local products, hiring local staff, and preserving local culture benefits both the environment and the community.

- -

Green Certifications

-

Many hotels are pursuing green certifications like LEED and Green Key, demonstrating their commitment to sustainability and environmental responsibility.

- -

Guest Education

-

Hotels are educating guests about sustainable practices, encouraging participation in conservation efforts, and making it easy to make eco-friendly choices.

- -

Conclusion

-

Sustainable tourism is the future of hospitality. By choosing eco-friendly hotels, guests can enjoy luxury experiences while supporting environmental conservation.

- """, - "featured_image": "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1200&h=800&fit=crop", - "tags": ["Sustainability", "Eco-Friendly", "Green Travel"], - "meta_title": "Sustainable Tourism: How Hotels Are Going Green", - "meta_description": "Discover how hotels are implementing sustainable practices to protect the environment while providing exceptional guest experiences.", - "meta_keywords": "sustainable tourism, eco-friendly hotels, green travel, environmental conservation, sustainable hospitality" - }, - { - "title": "The Ultimate Guide to Hotel Room Types", - "slug": "ultimate-guide-hotel-room-types", - "excerpt": "Navigate the world of hotel accommodations with our comprehensive guide to different room types and what to expect from each.", - "content": """ -

Introduction

-

Understanding hotel room types can help you choose the perfect accommodation for your needs and budget. Here's your guide to navigating hotel room categories.

- -

Standard Rooms

-

Standard rooms are the most basic accommodation, typically featuring a bed, bathroom, and essential amenities. Perfect for short stays or budget-conscious travelers.

- -

Deluxe Rooms

-

Deluxe rooms offer more space and upgraded amenities. They often feature better views, larger bathrooms, and additional seating areas.

- -

Suites

-

Suites provide separate living and sleeping areas, making them ideal for longer stays or guests who need extra space for work or relaxation.

- -

Presidential Suites

-

The pinnacle of hotel accommodations, presidential suites offer multiple rooms, premium amenities, and often include butler service and exclusive access.

- -

Specialty Rooms

-

Many hotels offer specialty rooms like honeymoon suites, family rooms, or themed accommodations designed for specific guest needs or occasions.

- -

Choosing the Right Room

-

Consider your needs: space requirements, view preferences, length of stay, and budget. Don't hesitate to contact the hotel directly for recommendations.

- -

Conclusion

-

Understanding room types helps you make informed decisions and ensures you get the accommodation that best suits your travel needs and preferences.

- """, - "featured_image": "https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop", - "tags": ["Hotel Guide", "Accommodation", "Travel Tips"], - "meta_title": "The Ultimate Guide to Hotel Room Types | Choose Your Perfect Room", - "meta_description": "Learn about different hotel room types and find the perfect accommodation for your needs. From standard rooms to presidential suites, we cover it all.", - "meta_keywords": "hotel rooms, room types, hotel accommodation, suites, travel guide" - }, - { - "title": "Wellness Tourism: Hotels as Health and Wellness Destinations", - "slug": "wellness-tourism-hotels-health-destinations", - "excerpt": "Discover how hotels are transforming into wellness destinations, offering comprehensive health and wellness programs for modern travelers.", - "content": """ -

Introduction

-

Wellness tourism is one of the fastest-growing segments in travel. Hotels are responding by creating comprehensive wellness programs that address physical, mental, and spiritual well-being.

- -

State-of-the-Art Spas

-

Modern hotel spas offer more than massages. They provide holistic treatments, thermal experiences, and wellness consultations tailored to individual needs.

- -

Fitness and Movement

-

From fully equipped gyms to yoga studios and personal training, hotels are making it easy for guests to maintain their fitness routines while traveling.

- -

Healthy Dining Options

-

Wellness-focused hotels offer nutritious menus, dietary accommodations, and nutritionist consultations, making healthy eating effortless during your stay.

- -

Mindfulness and Meditation

-

Many hotels now offer meditation spaces, mindfulness programs, and stress-reduction activities to help guests find balance and peace.

- -

Outdoor Activities

-

Wellness hotels often provide access to nature, outdoor activities, and adventure experiences that promote physical activity and connection with the environment.

- -

Sleep Optimization

-

Recognizing the importance of rest, wellness hotels focus on sleep quality with premium bedding, blackout curtains, and sleep enhancement programs.

- -

Conclusion

-

Wellness tourism represents a shift toward holistic travel experiences. Hotels that embrace this trend are creating spaces where guests can truly rejuvenate and restore.

- """, - "featured_image": "https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop", - "tags": ["Wellness", "Health", "Spa", "Fitness"], - "meta_title": "Wellness Tourism: Hotels as Health and Wellness Destinations", - "meta_description": "Explore how hotels are becoming wellness destinations, offering comprehensive health and wellness programs for modern travelers seeking balance and rejuvenation.", - "meta_keywords": "wellness tourism, health travel, spa hotels, wellness retreats, fitness travel" - }, - { - "title": "Technology in Modern Hotels: Enhancing the Guest Experience", - "slug": "technology-modern-hotels-enhancing-guest-experience", - "excerpt": "Explore how cutting-edge technology is revolutionizing the hotel industry and creating seamless, personalized guest experiences.", - "content": """ -

Introduction

-

Technology is transforming the hotel industry, making stays more convenient, personalized, and enjoyable. From mobile check-in to smart room controls, innovation is everywhere.

- -

Mobile Check-In and Keyless Entry

-

Guests can now check in via mobile apps and use their smartphones as room keys, eliminating wait times and providing instant access to their rooms.

- -

Smart Room Controls

-

Voice-activated assistants and smart controls allow guests to adjust lighting, temperature, and entertainment systems with simple commands or mobile apps.

- -

Personalized Recommendations

-

AI-powered systems analyze guest preferences to provide personalized recommendations for dining, activities, and services, enhancing the overall experience.

- -

Contactless Services

-

From contactless payments to digital menus and virtual concierge services, hotels are reducing physical contact while maintaining high service standards.

- -

Virtual Reality and Augmented Reality

-

Some hotels are using VR and AR to provide virtual tours, immersive experiences, and enhanced wayfinding, helping guests explore and navigate properties.

- -

Data Analytics

-

Hotels use data analytics to understand guest behavior, optimize operations, and predict needs, ensuring every stay is better than the last.

- -

Conclusion

-

Technology in hotels is about enhancing human connection, not replacing it. The best hotels use technology to free up staff to provide more personalized, meaningful service.

- """, - "featured_image": "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?w=1200&h=800&fit=crop", - "tags": ["Technology", "Innovation", "Smart Hotels"], - "meta_title": "Technology in Modern Hotels: Enhancing Guest Experience", - "meta_description": "Discover how cutting-edge technology is revolutionizing hotels, from mobile check-in to smart room controls and AI-powered personalization.", - "meta_keywords": "hotel technology, smart hotels, mobile check-in, hotel innovation, tech travel" - }, - { - "title": "The Ultimate Guide to Hotel Amenities: What to Expect", - "slug": "ultimate-guide-hotel-amenities-what-to-expect", - "excerpt": "From spas to fitness centers, discover the essential amenities that make a hotel stay memorable and how to make the most of them.", - "content": """ -

Introduction

-

Modern hotels offer an impressive array of amenities designed to enhance your stay. Understanding what's available can help you choose the perfect hotel and maximize your experience.

- -

Spa and Wellness Facilities

-

Luxury hotels often feature world-class spas offering massages, facials, and holistic treatments. These facilities provide relaxation and rejuvenation during your stay.

- -

Fitness Centers

-

State-of-the-art fitness centers with modern equipment allow you to maintain your workout routine while traveling. Many hotels also offer personal training and group classes.

- -

Swimming Pools

-

From rooftop infinity pools to heated indoor pools, swimming facilities provide relaxation and recreation. Some hotels offer poolside service and cabanas for ultimate comfort.

- -

Dining Options

-

Fine dining restaurants, casual cafes, room service, and bars create diverse culinary experiences. Many hotels feature award-winning chefs and unique dining concepts.

- -

Business Facilities

-

Business centers, meeting rooms, and conference facilities cater to corporate travelers. High-speed internet and printing services ensure productivity.

- -

Concierge Services

-

Professional concierge teams can arrange restaurant reservations, transportation, tickets, and local experiences, making your stay seamless and memorable.

- -

Conclusion

-

Hotel amenities significantly enhance your stay. Research available amenities when booking to ensure your hotel meets all your needs and preferences.

- """, - "featured_image": "https://images.unsplash.com/photo-1540541338287-41700207dee6?w=1200&h=800&fit=crop", - "tags": ["Amenities", "Hotel Guide", "Luxury"], - "meta_title": "The Ultimate Guide to Hotel Amenities | What to Expect", - "meta_description": "Discover essential hotel amenities from spas to fitness centers. Learn what to expect and how to make the most of your hotel stay.", - "meta_keywords": "hotel amenities, spa, fitness center, hotel facilities, luxury amenities" - }, - { - "title": "Business Travel Tips: Making the Most of Your Hotel Stay", - "slug": "business-travel-tips-making-most-hotel-stay", - "excerpt": "Essential strategies for business travelers to maximize productivity, comfort, and efficiency during hotel stays.", - "content": """ -

Introduction

-

Business travel requires efficiency and comfort. These tips will help you make the most of your hotel stay while maintaining productivity and well-being.

- -

Choose the Right Location

-

Select hotels close to your meetings or with excellent transportation links. This saves time and reduces stress during your business trip.

- -

Leverage Business Services

-

Take advantage of business centers, meeting rooms, and high-speed internet. Many hotels offer complimentary business services for corporate guests.

- -

Maintain Your Routine

-

Use hotel fitness centers to maintain your exercise routine. Regular workouts help manage stress and maintain energy levels during business trips.

- -

Optimize Your Room

-

Request a quiet room away from elevators and high-traffic areas. Ensure your room has adequate workspace and lighting for productivity.

- -

Use Concierge Services

-

Hotel concierges can handle restaurant reservations, transportation, and local recommendations, freeing your time for business matters.

- -

Join Loyalty Programs

-

Business travelers benefit significantly from hotel loyalty programs, earning points, upgrades, and exclusive perks that enhance every stay.

- -

Conclusion

-

Strategic planning and utilizing hotel services can transform business travel from stressful to seamless, allowing you to focus on your work.

- """, - "featured_image": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop", - "tags": ["Business Travel", "Travel Tips", "Productivity"], - "meta_title": "Business Travel Tips: Making the Most of Your Hotel Stay", - "meta_description": "Essential strategies for business travelers to maximize productivity and comfort during hotel stays. Tips for efficient business travel.", - "meta_keywords": "business travel, corporate travel, hotel business services, travel productivity, business tips" - }, - { - "title": "Family-Friendly Hotels: What to Look For", - "slug": "family-friendly-hotels-what-to-look-for", - "excerpt": "Discover the essential features and amenities that make hotels perfect for families traveling with children.", - "content": """ -

Introduction

-

Traveling with family requires special considerations. Family-friendly hotels offer amenities and services designed to make stays comfortable and enjoyable for all ages.

- -

Family Room Options

-

Look for hotels offering family rooms, suites, or connecting rooms. These provide space for everyone and often include extra beds or pull-out sofas.

- -

Kid-Friendly Amenities

-

Children's programs, kids' clubs, playgrounds, and game rooms keep young guests entertained. Some hotels offer babysitting services for parents' peace of mind.

- -

Dining Options

-

Family-friendly hotels provide children's menus, flexible dining times, and room service options. Buffet breakfasts are particularly convenient for families.

- -

Safety Features

-

Safety is paramount. Look for hotels with pool safety measures, childproofing options, and secure room access. Ground-floor rooms or rooms near elevators can be safer for families.

- -

Entertainment Facilities

-

Pools, game rooms, and outdoor spaces provide entertainment for children while allowing parents to relax. Some hotels offer family activities and events.

- -

Location Considerations

-

Choose hotels near family attractions, parks, or beaches. Easy access to activities reduces travel time and keeps children engaged.

- -

Conclusion

-

Family-friendly hotels create memorable experiences for all family members. Research and choose hotels that cater specifically to families for the best experience.

- """, - "featured_image": "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200&h=800&fit=crop", - "tags": ["Family Travel", "Family Hotels", "Travel Tips"], - "meta_title": "Family-Friendly Hotels: What to Look For | Family Travel Guide", - "meta_description": "Discover essential features that make hotels perfect for families. Learn what to look for when booking family-friendly accommodations.", - "meta_keywords": "family hotels, family travel, kid-friendly hotels, family accommodations, travel with children" - }, - { - "title": "Romantic Getaways: Choosing the Perfect Hotel", - "slug": "romantic-getaways-choosing-perfect-hotel", - "excerpt": "Create unforgettable romantic experiences with our guide to selecting hotels perfect for couples and special occasions.", - "content": """ -

Introduction

-

Romantic getaways require special attention to detail. The right hotel can transform a trip into an unforgettable romantic experience.

- -

Intimate Settings

-

Look for hotels with intimate atmospheres, private balconies, and romantic room designs. Boutique hotels often excel at creating romantic environments.

- -

Special Packages

-

Many hotels offer romantic packages including champagne, flowers, couples' spa treatments, and special dining experiences. These packages add magic to your stay.

- -

Scenic Locations

-

Hotels with stunning views, beachfront locations, or mountain settings create romantic backdrops. Sunsets and scenic vistas enhance the romantic atmosphere.

- -

Fine Dining

-

Romantic restaurants with candlelit dinners, private dining options, and exceptional cuisine create memorable experiences. Room service can also be romantic.

- -

Spa and Wellness

-

Couples' spa treatments, private hot tubs, and wellness facilities provide relaxation and connection. Many hotels offer romantic spa packages.

- -

Privacy and Seclusion

-

Private villas, secluded rooms, and adults-only areas ensure privacy and intimacy. These features are essential for romantic getaways.

- -

Conclusion

-

The perfect romantic hotel combines beautiful settings, intimate atmosphere, and thoughtful amenities to create unforgettable moments for couples.

- """, - "featured_image": "https://images.unsplash.com/photo-1520250497591-112f2f40a3f4?w=1200&h=800&fit=crop", - "tags": ["Romantic", "Couples", "Getaways"], - "meta_title": "Romantic Getaways: Choosing the Perfect Hotel for Couples", - "meta_description": "Create unforgettable romantic experiences with our guide to selecting hotels perfect for couples and special occasions.", - "meta_keywords": "romantic hotels, couples travel, romantic getaways, honeymoon hotels, romantic destinations" - }, - { - "title": "Hotel Booking Strategies: Getting the Best Deals", - "slug": "hotel-booking-strategies-getting-best-deals", - "excerpt": "Master the art of hotel booking with insider tips and strategies to secure the best rates and exclusive deals.", - "content": """ -

Introduction

-

Smart booking strategies can save significant money while securing better rooms and amenities. Learn the secrets to getting the best hotel deals.

- -

Book Directly

-

Booking directly with hotels often provides better rates, room upgrades, and exclusive perks not available through third-party sites.

- -

Flexible Dates

-

Flexibility with travel dates can result in substantial savings. Mid-week stays and off-peak seasons typically offer better rates.

- -

Join Loyalty Programs

-

Hotel loyalty programs provide points, discounts, and member-exclusive rates. Frequent travelers benefit significantly from these programs.

- -

Last-Minute Deals

-

Hotels often offer last-minute deals to fill empty rooms. If you're flexible, these can provide excellent value.

- -

Package Deals

-

Bundling hotel stays with flights or activities can result in savings. Compare package prices with individual bookings.

- -

Negotiate

-

Don't hesitate to call hotels directly and negotiate rates, especially for longer stays or group bookings. Hotels often have flexibility.

- -

Conclusion

-

Strategic booking approaches can significantly reduce costs while enhancing your hotel experience. Combine multiple strategies for maximum savings.

- """, - "featured_image": "https://images.unsplash.com/photo-1559526324-4b87b5e36e44?w=1200&h=800&fit=crop", - "tags": ["Booking Tips", "Travel Deals", "Savings"], - "meta_title": "Hotel Booking Strategies: Getting the Best Deals and Rates", - "meta_description": "Master hotel booking with insider tips to secure the best rates, exclusive deals, and room upgrades.", - "meta_keywords": "hotel deals, booking tips, hotel discounts, travel savings, best hotel rates" - }, - { - "title": "The History of Luxury Hospitality", - "slug": "history-luxury-hospitality", - "excerpt": "Explore the evolution of luxury hospitality from ancient inns to modern five-star hotels and what defines true luxury.", - "content": """ -

Introduction

-

Luxury hospitality has evolved dramatically over centuries, from simple inns to today's sophisticated five-star establishments. Understanding this history enriches our appreciation of modern luxury.

- -

Ancient Origins

-

Hospitality dates back to ancient civilizations where travelers found shelter in inns and guesthouses. These early establishments laid the foundation for modern hospitality.

- -

Grand Hotels Era

-

The 19th century saw the rise of grand hotels in Europe and America. These magnificent establishments set new standards for luxury, service, and architecture.

- -

Modern Luxury

-

Today's luxury hotels combine traditional elegance with modern amenities, technology, and personalized service. The definition of luxury continues to evolve.

- -

Iconic Properties

-

Legendary hotels like The Ritz, The Savoy, and The Plaza have become symbols of luxury hospitality, setting benchmarks for excellence worldwide.

- -

Contemporary Trends

-

Modern luxury emphasizes experiences, sustainability, wellness, and personalization. Hotels adapt to changing guest expectations while maintaining timeless elegance.

- -

Conclusion

-

The history of luxury hospitality reflects our evolving understanding of comfort, service, and elegance. Today's luxury hotels honor tradition while embracing innovation.

- """, - "featured_image": "https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop", - "tags": ["History", "Luxury", "Hospitality"], - "meta_title": "The History of Luxury Hospitality: From Ancient Inns to Modern Hotels", - "meta_description": "Explore the evolution of luxury hospitality from ancient inns to modern five-star hotels and what defines true luxury.", - "meta_keywords": "hotel history, luxury hospitality history, grand hotels, hospitality evolution, luxury travel history" - }, - { - "title": "Hotel Etiquette: Do's and Don'ts for Guests", - "slug": "hotel-etiquette-dos-donts-guests", - "excerpt": "Master proper hotel etiquette to ensure a pleasant stay for yourself and other guests while respecting hotel staff and property.", - "content": """ -

Introduction

-

Proper hotel etiquette ensures pleasant experiences for everyone. Understanding expected behavior helps create positive interactions with staff and fellow guests.

- -

Check-In and Check-Out

-

Arrive on time for check-in and respect check-out times. Be patient and courteous with front desk staff, especially during busy periods.

- -

Room Behavior

-

Keep noise levels reasonable, especially during quiet hours. Respect room capacity limits and treat hotel property with care.

- -

Staff Interactions

-

Treat all hotel staff with respect and kindness. A friendly attitude often results in better service and assistance with special requests.

- -

Common Areas

-

Respect shared spaces like lobbies, pools, and restaurants. Follow dress codes, keep areas clean, and be mindful of other guests.

- -

Tipping Guidelines

-

Tip housekeeping, bellhops, and concierge staff appropriately. Tipping shows appreciation for good service and is standard practice.

- -

Special Requests

-

Make special requests in advance when possible. Be reasonable and understanding if requests cannot be accommodated.

- -

Conclusion

-

Good hotel etiquette creates positive experiences for everyone. Simple courtesy and respect go a long way in ensuring pleasant stays.

- """, - "featured_image": "https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1200&h=800&fit=crop", - "tags": ["Etiquette", "Travel Tips", "Guest Guide"], - "meta_title": "Hotel Etiquette: Do's and Don'ts for Hotel Guests", - "meta_description": "Master proper hotel etiquette to ensure pleasant stays. Learn do's and don'ts for interacting with staff and other guests.", - "meta_keywords": "hotel etiquette, guest etiquette, travel etiquette, hotel manners, guest behavior" - }, - { - "title": "Boutique Hotels vs. Chain Hotels: Which is Right for You?", - "slug": "boutique-hotels-vs-chain-hotels-which-right-for-you", - "excerpt": "Compare boutique and chain hotels to determine which type best suits your travel style, preferences, and needs.", - "content": """ -

Introduction

-

Choosing between boutique and chain hotels depends on your preferences, travel style, and priorities. Each offers distinct advantages.

- -

Boutique Hotels

-

Boutique hotels offer unique character, personalized service, and distinctive design. They provide intimate experiences and local flavor, perfect for travelers seeking authenticity.

- -

Chain Hotels

-

Chain hotels provide consistency, loyalty programs, and predictable experiences. They're ideal for business travelers and those who value reliability and familiarity.

- -

Service Comparison

-

Boutique hotels excel in personalized, attentive service, while chains offer standardized, efficient service. Choose based on your preference for customization vs. consistency.

- -

Amenities and Facilities

-

Chain hotels typically offer extensive amenities and facilities, while boutiques focus on curated, high-quality experiences. Consider what matters most to you.

- -

Location and Design

-

Boutique hotels often feature unique, locally-inspired designs in interesting neighborhoods. Chains provide familiar layouts in convenient, predictable locations.

- -

Value and Pricing

-

Both can offer excellent value. Boutiques provide unique experiences, while chains offer loyalty rewards and package deals. Compare based on total value, not just price.

- -

Conclusion

-

The best choice depends on your priorities. Boutiques offer uniqueness and character, while chains provide consistency and rewards. Consider your travel style and needs.

- """, - "featured_image": "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=1200&h=800&fit=crop", - "tags": ["Hotel Types", "Travel Guide", "Comparison"], - "meta_title": "Boutique Hotels vs. Chain Hotels: Which is Right for You?", - "meta_description": "Compare boutique and chain hotels to determine which type best suits your travel style, preferences, and needs.", - "meta_keywords": "boutique hotels, chain hotels, hotel comparison, hotel types, travel guide" - }, - { - "title": "Accessibility in Hotels: Ensuring Comfortable Stays for All", - "slug": "accessibility-hotels-ensuring-comfortable-stays-all", - "excerpt": "Learn about hotel accessibility features and how to ensure comfortable, accessible stays for guests with disabilities.", - "content": """ -

Introduction

-

Accessible hotels ensure comfortable stays for all guests. Understanding accessibility features helps travelers with disabilities find suitable accommodations.

- -

Accessible Rooms

-

Accessible rooms feature wider doorways, roll-in showers, grab bars, and lower fixtures. These rooms meet ADA standards and provide comfort and safety.

- -

Common Areas

-

Accessible hotels provide ramps, elevators, and accessible paths throughout. Restaurants, pools, and facilities should be accessible to all guests.

- -

Communication Access

-

Hotels should offer visual alarms, TTY devices, and staff trained in sign language or communication assistance for guests with hearing impairments.

- -

Service Animals

-

Hotels must accommodate service animals. Understanding policies and communicating needs ensures smooth stays for guests with service animals.

- -

Booking Considerations

-

When booking, specify accessibility needs clearly. Contact hotels directly to confirm specific features and ensure your requirements are met.

- -

Staff Training

-

Well-trained staff understand accessibility needs and can assist effectively. Choose hotels known for excellent accessibility service.

- -

Conclusion

-

Accessible hotels create inclusive experiences for all guests. Research and communicate needs to ensure comfortable, accessible stays.

- """, - "featured_image": "https://images.unsplash.com/photo-1522771739844-6a9f6d5f14af?w=1200&h=800&fit=crop", - "tags": ["Accessibility", "Inclusive Travel", "Guest Services"], - "meta_title": "Accessibility in Hotels: Ensuring Comfortable Stays for All Guests", - "meta_description": "Learn about hotel accessibility features and how to ensure comfortable, accessible stays for guests with disabilities.", - "meta_keywords": "accessible hotels, hotel accessibility, disability travel, accessible accommodations, inclusive travel" - }, - { - "title": "Hotel Security: Staying Safe During Your Stay", - "slug": "hotel-security-staying-safe-during-stay", - "excerpt": "Essential security tips and practices to ensure your safety and protect your belongings during hotel stays.", - "content": """ -

Introduction

-

Hotel security is a shared responsibility between guests and hotels. Following security best practices ensures safe, worry-free stays.

- -

Room Security

-

Always lock your room door and use deadbolts. Never leave doors propped open, and verify room numbers before entering. Use room safes for valuables.

- -

Valuables Protection

-

Use in-room safes for passports, jewelry, and electronics. Don't leave valuables unattended in public areas. Consider hotel safety deposit boxes for important items.

- -

Personal Information

-

Protect personal information. Be cautious with Wi-Fi networks, and avoid sharing room numbers publicly. Use secure payment methods.

- -

Hotel Facilities

-

Be aware of your surroundings in common areas. Use well-lit paths, and don't hesitate to ask hotel staff for assistance or security escorts.

- -

Emergency Procedures

-

Familiarize yourself with emergency exits and procedures. Know the location of fire exits and emergency contact information.

- -

Trust Your Instincts

-

If something feels wrong, trust your instincts. Report suspicious activity to hotel security immediately. Your safety is the priority.

- -

Conclusion

-

Following security best practices ensures safe hotel stays. Stay vigilant, use available security features, and communicate concerns to hotel staff.

- """, - "featured_image": "https://images.unsplash.com/photo-1557804506-669a67965ba0?w=1200&h=800&fit=crop", - "tags": ["Security", "Safety", "Travel Tips"], - "meta_title": "Hotel Security: Staying Safe During Your Stay | Safety Tips", - "meta_description": "Essential security tips and practices to ensure your safety and protect your belongings during hotel stays.", - "meta_keywords": "hotel security, travel safety, hotel safety tips, guest security, travel security" - }, - { - "title": "Pet-Friendly Hotels: Traveling with Your Furry Friends", - "slug": "pet-friendly-hotels-traveling-furry-friends", - "excerpt": "Discover how to find and enjoy pet-friendly hotels that welcome your four-legged companions with open arms.", - "content": """ -

Introduction

-

Traveling with pets requires special planning. Pet-friendly hotels make it possible to bring your furry friends along, ensuring everyone enjoys the trip.

- -

Finding Pet-Friendly Hotels

-

Research hotels that explicitly welcome pets. Check policies regarding size limits, breed restrictions, and number of pets allowed per room.

- -

Pet Amenities

-

Many pet-friendly hotels offer special amenities like pet beds, bowls, treats, and walking services. Some even have pet spas and play areas.

- -

Pet Fees and Policies

-

Understand pet fees, which can vary significantly. Some hotels charge per night, while others have flat fees. Review cancellation policies for pet-related issues.

- -

Pet Etiquette

-

Keep pets leashed in common areas, clean up after them, and respect other guests. Don't leave pets unattended in rooms for extended periods.

- -

Preparing Your Pet

-

Bring familiar items like beds and toys. Ensure pets are well-behaved and up-to-date on vaccinations. Bring necessary documentation.

- -

Local Pet Services

-

Research nearby veterinarians, pet stores, and dog parks. Hotels in pet-friendly areas often provide recommendations and assistance.

- -

Conclusion

-

Pet-friendly hotels make traveling with pets enjoyable. Research thoroughly, follow policies, and be considerate to ensure positive experiences for all.

- """, - "featured_image": "https://images.unsplash.com/photo-1601758228041-f3b2795255f1?w=1200&h=800&fit=crop", - "tags": ["Pet Travel", "Pet-Friendly", "Travel Tips"], - "meta_title": "Pet-Friendly Hotels: Traveling with Your Furry Friends", - "meta_description": "Discover how to find and enjoy pet-friendly hotels that welcome your four-legged companions with open arms.", - "meta_keywords": "pet-friendly hotels, traveling with pets, pet travel, dog-friendly hotels, pet accommodations" - }, - { - "title": "Hotel Concierge Services: Maximizing Your Experience", - "slug": "hotel-concierge-services-maximizing-experience", - "excerpt": "Learn how to leverage hotel concierge services to enhance your stay with local insights, reservations, and personalized assistance.", - "content": """ -

Introduction

-

Hotel concierges are valuable resources for enhancing your stay. Understanding how to work with them unlocks local expertise and exclusive access.

- -

What Concierges Offer

-

Concierges provide restaurant reservations, event tickets, transportation, local recommendations, and assistance with special requests. They're your local experts.

- -

Restaurant Reservations

-

Concierges often have relationships with popular restaurants, securing tables at hard-to-book establishments. They know the best local dining options.

- -

Local Insights

-

Tap into concierges' local knowledge for hidden gems, cultural experiences, and authentic recommendations beyond tourist guides.

- -

Special Occasions

-

Concierges excel at arranging special occasions—anniversaries, proposals, birthdays. They coordinate flowers, cakes, decorations, and surprises.

- -

Transportation and Logistics

-

From airport transfers to car rentals and private drivers, concierges handle transportation needs efficiently and reliably.

- -

Building Relationships

-

Building rapport with concierges through courtesy and clear communication often results in better service and special considerations.

- -

Conclusion

-

Concierge services transform hotel stays into exceptional experiences. Don't hesitate to utilize their expertise and connections.

- """, - "featured_image": "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800&fit=crop", - "tags": ["Concierge", "Guest Services", "Travel Tips"], - "meta_title": "Hotel Concierge Services: Maximizing Your Experience", - "meta_description": "Learn how to leverage hotel concierge services to enhance your stay with local insights, reservations, and personalized assistance.", - "meta_keywords": "hotel concierge, concierge services, guest services, travel assistance, hotel services" - }, - { - "title": "Seasonal Hotel Stays: Best Times to Visit", - "slug": "seasonal-hotel-stays-best-times-visit", - "excerpt": "Discover the advantages of different seasons for hotel stays and how timing affects rates, crowds, and experiences.", - "content": """ -

Introduction

-

Timing your hotel stay can significantly impact your experience, costs, and enjoyment. Understanding seasonal patterns helps you choose the perfect time to visit.

- -

Peak Season

-

Peak seasons offer ideal weather and full amenities but come with higher prices and crowds. Book well in advance and expect premium rates.

- -

Shoulder Season

-

Shoulder seasons provide a balance of good weather, reasonable prices, and fewer crowds. These periods often offer the best value and experience.

- -

Off-Season Benefits

-

Off-season travel offers significant savings, fewer crowds, and more personalized service. Some amenities may be limited, but value is exceptional.

- -

Weather Considerations

-

Research destination weather patterns. Some locations are beautiful year-round, while others have distinct seasons affecting activities and experiences.

- -

Special Events

-

Consider local festivals, holidays, and events. These can enhance experiences but also increase prices and require early booking.

- -

Business vs. Leisure

-

Business travelers may prefer off-peak times for better rates and availability. Leisure travelers can choose based on weather and activities.

- -

Conclusion

-

Choosing the right season balances your priorities—weather, budget, crowds, and experiences. Research thoroughly to find your ideal timing.

- """, - "featured_image": "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200&h=800&fit=crop", - "tags": ["Travel Planning", "Seasonal Travel", "Travel Tips"], - "meta_title": "Seasonal Hotel Stays: Best Times to Visit | Travel Planning", - "meta_description": "Discover the advantages of different seasons for hotel stays and how timing affects rates, crowds, and experiences.", - "meta_keywords": "seasonal travel, best time to travel, travel seasons, hotel booking seasons, travel planning" - }, - { - "title": "Hotel Loyalty Programs: Maximizing Rewards", - "slug": "hotel-loyalty-programs-maximizing-rewards", - "excerpt": "Unlock the full potential of hotel loyalty programs with strategies to earn points, achieve elite status, and redeem rewards effectively.", - "content": """ -

Introduction

-

Hotel loyalty programs offer significant value for frequent travelers. Understanding how to maximize these programs enhances every stay with rewards and perks.

- -

Choosing the Right Program

-

Select programs aligned with your travel patterns. Consider hotel locations, frequency of travel, and program benefits that matter most to you.

- -

Earning Points

-

Maximize point earning through direct bookings, credit card partnerships, and promotional offers. Some programs offer bonus points for specific activities.

- -

Elite Status Benefits

-

Elite status provides room upgrades, late checkout, breakfast, and lounge access. Focus on one program to achieve status faster.

- -

Point Redemption

-

Redeem points strategically for maximum value. Award nights, upgrades, and experiences offer different redemption values—choose wisely.

- -

Credit Card Partnerships

-

Hotel-branded credit cards accelerate point earning and provide automatic elite status. Evaluate annual fees against benefits received.

- -

Promotions and Bonuses

-

Take advantage of promotions, bonus point offers, and special events. Sign up for program emails to stay informed about opportunities.

- -

Conclusion

-

Strategic participation in loyalty programs provides substantial value. Focus, be consistent, and leverage all available benefits for maximum rewards.

- """, - "featured_image": "https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=1200&h=800&fit=crop", - "tags": ["Loyalty Programs", "Travel Rewards", "Travel Tips"], - "meta_title": "Hotel Loyalty Programs: Maximizing Rewards and Benefits", - "meta_description": "Unlock the full potential of hotel loyalty programs with strategies to earn points, achieve elite status, and redeem rewards effectively.", - "meta_keywords": "hotel loyalty programs, travel rewards, points programs, hotel rewards, loyalty benefits" - }, - { - "title": "Hotel Room Service: A Complete Guide", - "slug": "hotel-room-service-complete-guide", - "excerpt": "Navigate hotel room service menus, etiquette, and tips to enjoy convenient dining in the comfort of your room.", - "content": """ -

Introduction

-

Room service offers convenience and privacy, allowing guests to dine in their rooms. Understanding how to use this service enhances your hotel experience.

- -

Menu Options

-

Room service menus range from simple snacks to full meals. Many hotels offer 24-hour service with diverse options to suit different tastes and times.

- -

Ordering Process

-

Order through phone, in-room tablets, or hotel apps. Specify delivery time, special requests, and dietary requirements when ordering.

- -

Pricing Considerations

-

Room service typically includes service charges and delivery fees. Prices are often higher than restaurant dining, but convenience may justify the cost.

- -

Timing Your Orders

-

Order during off-peak hours for faster service. Breakfast orders placed the night before ensure timely morning delivery.

- -

Special Requests

-

Hotels accommodate dietary restrictions, allergies, and special requests. Communicate needs clearly when ordering for best results.

- -

Tipping Etiquette

-

Tip room service staff appropriately, typically 15-20% of the bill. Some hotels include service charges, but additional tips are appreciated.

- -

Conclusion

-

Room service provides convenient dining options. Understanding menus, ordering processes, and etiquette ensures enjoyable experiences.

- """, - "featured_image": "https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=1200&h=800&fit=crop", - "tags": ["Room Service", "Dining", "Hotel Services"], - "meta_title": "Hotel Room Service: A Complete Guide to In-Room Dining", - "meta_description": "Navigate hotel room service menus, etiquette, and tips to enjoy convenient dining in the comfort of your room.", - "meta_keywords": "room service, hotel dining, in-room dining, hotel food service, room service guide" - }, - { - "title": "Extended Stay Hotels: Living in Comfort", - "slug": "extended-stay-hotels-living-comfort", - "excerpt": "Discover the benefits of extended stay hotels for long-term travel, relocations, and temporary housing needs.", - "content": """ -

Introduction

-

Extended stay hotels provide comfortable, convenient accommodations for longer visits. These properties are designed specifically for guests staying weeks or months.

- -

Apartment-Style Living

-

Extended stay hotels offer apartment-like rooms with kitchens, living areas, and separate bedrooms. These features make long stays comfortable and practical.

- -

Cost Effectiveness

-

Extended stay rates are typically lower than traditional hotels for longer periods. Weekly and monthly rates provide significant savings.

- -

Home-Like Amenities

-

Full kitchens, laundry facilities, and larger spaces create home-like environments. These amenities are essential for comfortable extended stays.

- -

Location Benefits

-

Extended stay hotels are often located near business districts, hospitals, or universities, providing convenient access for work or study.

- -

Flexible Stays

-

Flexible booking options accommodate changing schedules. Many properties offer month-to-month arrangements without long-term commitments.

- -

Services and Support

-

Housekeeping, maintenance, and front desk services provide support while maintaining independence. Balance of service and privacy is key.

- -

Conclusion

-

Extended stay hotels bridge the gap between hotels and apartments, offering comfort and convenience for longer visits.

- """, - "featured_image": "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=1200&h=800&fit=crop", - "tags": ["Extended Stay", "Long-Term Travel", "Accommodation"], - "meta_title": "Extended Stay Hotels: Living in Comfort for Long-Term Travel", - "meta_description": "Discover the benefits of extended stay hotels for long-term travel, relocations, and temporary housing needs.", - "meta_keywords": "extended stay hotels, long-term hotels, temporary housing, relocation hotels, monthly hotels" - }, - { - "title": "Hotel Photography: Capturing Your Stay", - "slug": "hotel-photography-capturing-your-stay", - "excerpt": "Tips and techniques for taking stunning photos of your hotel stay to preserve memories and share experiences.", - "content": """ -

Introduction

-

Hotel photography preserves memories and shares experiences. Whether for personal keepsakes or social media, great photos enhance your travel documentation.

- -

Room Photography

-

Capture rooms in natural light, showing key features and ambiance. Include details like views, amenities, and design elements that make the room special.

- -

Common Areas

-

Photograph lobbies, restaurants, and facilities to document the full hotel experience. These spaces often showcase the hotel's character and style.

- -

Food and Dining

-

Food photography requires good lighting and composition. Capture presentation, ambiance, and memorable meals to preserve dining experiences.

- -

Views and Scenery

-

Hotel views are often highlights. Capture sunrises, sunsets, and scenic vistas from rooms, balconies, and hotel terraces.

- -

Respect and Etiquette

-

Respect other guests' privacy when photographing. Avoid photographing staff without permission and follow hotel photography policies.

- -

Editing and Sharing

-

Edit photos to enhance colors and composition while maintaining authenticity. Share thoughtfully, respecting the hotel and other guests.

- -

Conclusion

-

Hotel photography creates lasting memories. With respect and good technique, you can capture beautiful images that preserve your travel experiences.

- """, - "featured_image": "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=1200&h=800&fit=crop", - "tags": ["Photography", "Travel Tips", "Social Media"], - "meta_title": "Hotel Photography: Capturing Your Stay | Travel Photography Tips", - "meta_description": "Tips and techniques for taking stunning photos of your hotel stay to preserve memories and share experiences.", - "meta_keywords": "hotel photography, travel photography, hotel photos, travel tips, photography tips" - }, - { - "title": "Eco-Friendly Hotels: Sustainable Travel Choices", - "slug": "eco-friendly-hotels-sustainable-travel-choices", - "excerpt": "Learn how to identify and choose eco-friendly hotels that prioritize sustainability and environmental responsibility.", - "content": """ -

Introduction

-

Eco-friendly hotels combine luxury with environmental responsibility. Choosing sustainable accommodations supports conservation while enjoying comfortable stays.

- -

Sustainability Certifications

-

Look for hotels with recognized certifications like LEED, Green Key, or EarthCheck. These certifications verify genuine environmental commitments.

- -

Energy Efficiency

-

Eco-friendly hotels use renewable energy, efficient systems, and smart technology to reduce energy consumption without compromising comfort.

- -

Water Conservation

-

Sustainable hotels implement water-saving measures, recycling systems, and responsible water management practices throughout their operations.

- -

Waste Reduction

-

Comprehensive recycling, composting, and waste reduction programs minimize environmental impact. Some hotels eliminate single-use plastics entirely.

- -

Local Sourcing

-

Sustainable hotels source locally, supporting communities and reducing transportation impacts. Local food, materials, and services benefit everyone.

- -

Guest Participation

-

Eco-friendly hotels encourage guest participation through towel reuse programs, energy conservation, and educational initiatives about sustainability.

- -

Conclusion

-

Choosing eco-friendly hotels supports environmental conservation while enjoying luxury experiences. Sustainable travel benefits destinations and future generations.

- """, - "featured_image": "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1200&h=800&fit=crop", - "tags": ["Sustainability", "Eco-Friendly", "Green Travel"], - "meta_title": "Eco-Friendly Hotels: Sustainable Travel Choices | Green Hotels", - "meta_description": "Learn how to identify and choose eco-friendly hotels that prioritize sustainability and environmental responsibility.", - "meta_keywords": "eco-friendly hotels, sustainable hotels, green hotels, eco travel, sustainable travel" - }, - { - "title": "Hotel Reviews: How to Write Helpful Reviews", - "slug": "hotel-reviews-how-write-helpful-reviews", - "excerpt": "Master the art of writing hotel reviews that help other travelers make informed decisions while providing valuable feedback to hotels.", - "content": """ -

Introduction

-

Well-written hotel reviews help travelers make informed decisions and provide valuable feedback to hotels. Learning to write effective reviews benefits everyone.

- -

Be Specific

-

Provide specific details about your experience—room features, service interactions, amenities used. Specific information is more helpful than general statements.

- -

Balance Positives and Negatives

-

Honest reviews include both positive aspects and areas for improvement. Balanced perspectives help readers understand the full experience.

- -

Focus on Facts

-

Stick to factual observations about your stay. Avoid speculation or assumptions about things you didn't directly experience.

- -

Consider Context

-

Mention relevant context—travel purpose, time of year, special occasions. This helps readers understand if your experience applies to their situation.

- -

Be Respectful

-

Write respectfully, even when reporting negative experiences. Constructive criticism is more valuable than harsh complaints.

- -

Include Photos

-

Photos enhance reviews significantly, showing actual conditions and experiences. Include images of rooms, amenities, and notable features.

- -

Conclusion

-

Helpful reviews require thoughtfulness, honesty, and respect. Well-written reviews create value for travelers and hotels alike.

- """, - "featured_image": "https://images.unsplash.com/photo-1450101499163-c8848c66ca85?w=1200&h=800&fit=crop", - "tags": ["Reviews", "Travel Tips", "Guest Feedback"], - "meta_title": "Hotel Reviews: How to Write Helpful Reviews for Travelers", - "meta_description": "Master the art of writing hotel reviews that help other travelers make informed decisions while providing valuable feedback to hotels.", - "meta_keywords": "hotel reviews, writing reviews, travel reviews, review tips, guest feedback" - }, - { - "title": "Hotel Cancellation Policies: Understanding Your Options", - "slug": "hotel-cancellation-policies-understanding-options", - "excerpt": "Navigate hotel cancellation policies to understand your rights, options, and how to handle changes to your travel plans.", - "content": """ -

Introduction

-

Understanding hotel cancellation policies protects you from unexpected charges and helps you make informed booking decisions.

- -

Policy Types

-

Hotels offer various cancellation policies—fully refundable, partially refundable, and non-refundable. Each has different terms and deadlines.

- -

Booking Channels

-

Cancellation policies vary by booking channel. Direct bookings often offer more flexibility than third-party sites. Always review terms carefully.

- -

Deadlines Matter

-

Cancellation deadlines are critical. Missing deadlines can result in charges. Set reminders and understand exact cutoff times.

- -

Special Circumstances

-

Hotels may offer flexibility for emergencies, weather, or other extenuating circumstances. Contact hotels directly to discuss options.

- -

Travel Insurance

-

Travel insurance can protect against cancellation fees for covered reasons. Review policies to understand what's covered and what's not.

- -

Modification Options

-

Some hotels allow date modifications instead of cancellations. This can preserve your booking while accommodating schedule changes.

- -

Conclusion

-

Understanding cancellation policies prevents surprises and helps you choose bookings that match your flexibility needs.

- """, - "featured_image": "https://images.unsplash.com/photo-1559526324-4b87b5e36e44?w=1200&h=800&fit=crop", - "tags": ["Booking", "Travel Tips", "Policies"], - "meta_title": "Hotel Cancellation Policies: Understanding Your Options", - "meta_description": "Navigate hotel cancellation policies to understand your rights, options, and how to handle changes to your travel plans.", - "meta_keywords": "cancellation policies, hotel booking, travel policies, refund policies, booking terms" - }, - { - "title": "Hotel Wi-Fi: Staying Connected on the Go", - "slug": "hotel-wifi-staying-connected-go", - "excerpt": "Navigate hotel Wi-Fi services, understand connection options, and ensure reliable internet access during your stay.", - "content": """ -

Introduction

-

Reliable hotel Wi-Fi is essential for modern travelers. Understanding Wi-Fi options and how to optimize connections ensures productivity and connectivity.

- -

Wi-Fi Tiers

-

Many hotels offer multiple Wi-Fi tiers—basic free service and premium high-speed options. Choose based on your needs and usage.

- -

Connection Tips

-

Position devices near routers, avoid peak usage times, and disconnect unused devices. These simple steps improve connection quality.

- -

Security Considerations

-

Use hotel Wi-Fi cautiously. Avoid sensitive transactions on public networks. Consider VPN services for enhanced security when traveling.

- -

Business Travel Needs

-

Business travelers may need premium Wi-Fi for video calls and large file transfers. Verify speeds and reliability before booking.

- -

Troubleshooting

-

Hotel IT support can assist with connection issues. Don't hesitate to contact them for help with Wi-Fi problems.

- -

Alternative Options

-

Mobile hotspots and data plans provide alternatives to hotel Wi-Fi. Consider these options for critical connectivity needs.

- -

Conclusion

-

Understanding hotel Wi-Fi options and optimizing connections ensures reliable internet access during your stay.

- """, - "featured_image": "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800&fit=crop", - "tags": ["Technology", "Wi-Fi", "Business Travel"], - "meta_title": "Hotel Wi-Fi: Staying Connected on the Go | Internet Access", - "meta_description": "Navigate hotel Wi-Fi services, understand connection options, and ensure reliable internet access during your stay.", - "meta_keywords": "hotel wifi, hotel internet, wifi tips, travel technology, hotel connectivity" - }, - { - "title": "Hotel Breakfast: Starting Your Day Right", - "slug": "hotel-breakfast-starting-day-right", - "excerpt": "Explore hotel breakfast options from continental to full breakfast buffets and how to make the most of morning dining.", - "content": """ -

Introduction

-

Hotel breakfasts set the tone for your day. Understanding options and making the most of breakfast services enhances your stay experience.

- -

Breakfast Types

-

Hotels offer various breakfast options—continental, buffet, à la carte, and room service. Each provides different experiences and value.

- -

Buffet Benefits

-

Breakfast buffets offer variety and value, allowing you to sample different items and eat as much as you like. They're perfect for families.

- -

Timing Considerations

-

Breakfast service hours vary. Early risers and late sleepers should check times. Some hotels offer extended or 24-hour breakfast options.

- -

Dietary Accommodations

-

Hotels accommodate dietary restrictions, allergies, and preferences. Communicate needs in advance for best results.

- -

Included vs. Paid

-

Some hotels include breakfast in rates, while others charge separately. Compare total costs when evaluating hotel options.

- -

Local Specialties

-

Hotel breakfasts often feature local specialties, providing cultural experiences and authentic flavors of the destination.

- -

Conclusion

-

Hotel breakfasts offer convenience and variety. Understanding options helps you start each day of your stay on the right note.

- """, - "featured_image": "https://images.unsplash.com/photo-1525351484163-7529414344d8?w=1200&h=800&fit=crop", - "tags": ["Dining", "Breakfast", "Hotel Services"], - "meta_title": "Hotel Breakfast: Starting Your Day Right | Breakfast Guide", - "meta_description": "Explore hotel breakfast options from continental to full breakfast buffets and how to make the most of morning dining.", - "meta_keywords": "hotel breakfast, breakfast buffet, hotel dining, morning meals, hotel food" - }, - { - "title": "Hotel Pools: Making the Most of Pool Facilities", - "slug": "hotel-pools-making-most-pool-facilities", - "excerpt": "Discover how to enjoy hotel pool facilities, from infinity pools to rooftop pools, and poolside service experiences.", - "content": """ -

Introduction

-

Hotel pools provide relaxation and recreation. Understanding pool facilities and etiquette ensures enjoyable experiences for all guests.

- -

Pool Types

-

Hotels offer various pool types—outdoor, indoor, rooftop, infinity, and heated pools. Each provides unique experiences and benefits.

- -

Poolside Service

-

Many hotels offer poolside service for food and beverages. Enjoy meals and drinks without leaving the pool area.

- -

Pool Etiquette

-

Respect other guests, follow pool rules, and maintain cleanliness. Proper etiquette ensures pleasant experiences for everyone.

- -

Family Considerations

-

Family-friendly pools often have shallow areas, lifeguards, and children's activities. Some hotels offer separate adult pools for quiet relaxation.

- -

Pool Hours

-

Check pool operating hours, which may vary by season or day. Some pools offer extended hours or 24-hour access.

- -

Pool Amenities

-

Hotels may offer pool amenities like cabanas, loungers, towels, and changing facilities. Inquire about available services.

- -

Conclusion

-

Hotel pools enhance stays with relaxation and recreation. Understanding facilities and following etiquette ensures enjoyable pool experiences.

- """, - "featured_image": "https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1200&h=800&fit=crop", - "tags": ["Amenities", "Pools", "Recreation"], - "meta_title": "Hotel Pools: Making the Most of Pool Facilities", - "meta_description": "Discover how to enjoy hotel pool facilities, from infinity pools to rooftop pools, and poolside service experiences.", - "meta_keywords": "hotel pools, pool facilities, poolside service, hotel amenities, pool guide" - }, - { - "title": "Hotel Spas: Ultimate Relaxation Experiences", - "slug": "hotel-spas-ultimate-relaxation-experiences", - "excerpt": "Explore hotel spa facilities and treatments, from massages to holistic wellness programs, for complete relaxation and rejuvenation.", - "content": """ -

Introduction

-

Hotel spas provide sanctuary for relaxation and rejuvenation. Understanding spa offerings helps you choose treatments and maximize your experience.

- -

Spa Treatments

-

Hotel spas offer diverse treatments—massages, facials, body wraps, and holistic therapies. Each addresses different wellness needs.

- -

Booking Considerations

-

Book spa appointments in advance, especially for popular treatments and peak times. Early booking ensures preferred times and availability.

- -

Treatment Packages

-

Many spas offer packages combining multiple treatments at discounted rates. Packages provide comprehensive wellness experiences.

- -

Spa Facilities

-

Hotel spas often include saunas, steam rooms, hot tubs, and relaxation areas. Arrive early to enjoy these facilities before treatments.

- -

Wellness Programs

-

Some hotel spas offer comprehensive wellness programs including fitness, nutrition, and mindfulness components for holistic health.

- -

Couples Experiences

-

Couples' treatments and private spa suites create romantic, intimate experiences perfect for special occasions or getaways.

- -

Conclusion

-

Hotel spas provide essential relaxation and wellness experiences. Understanding offerings and booking strategically maximizes your spa visit.

- """, - "featured_image": "https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop", - "tags": ["Spa", "Wellness", "Relaxation"], - "meta_title": "Hotel Spas: Ultimate Relaxation Experiences | Spa Guide", - "meta_description": "Explore hotel spa facilities and treatments, from massages to holistic wellness programs, for complete relaxation and rejuvenation.", - "meta_keywords": "hotel spas, spa treatments, wellness, relaxation, spa guide" - }, - { - "title": "Hotel Bars and Lounges: Evening Entertainment", - "slug": "hotel-bars-lounges-evening-entertainment", - "excerpt": "Discover hotel bars and lounges offering craft cocktails, live music, and sophisticated atmospheres for evening relaxation and socializing.", - "content": """ -

Introduction

-

Hotel bars and lounges provide sophisticated settings for evening entertainment, socializing, and enjoying craft beverages in elegant atmospheres.

- -

Cocktail Culture

-

Hotel bars often feature expert mixologists creating craft cocktails with premium ingredients. These establishments showcase beverage artistry.

- -

Atmosphere and Design

-

Hotel bars create distinct atmospheres through design, lighting, and music. From rooftop bars to speakeasies, each offers unique experiences.

- -

Live Entertainment

-

Many hotel bars feature live music, DJs, or entertainment. These elements enhance evening experiences and create memorable nights.

- -

Bar Food

-

Hotel bars often serve excellent bar food, from small plates to full meals. Quality food complements beverage experiences.

- -

Social Spaces

-

Bars and lounges serve as social hubs, perfect for meeting other travelers, business networking, or simply relaxing after a day of activities.

- -

Rooftop Experiences

-

Rooftop bars offer stunning views and unique atmospheres. These elevated spaces provide exceptional evening experiences.

- -

Conclusion

-

Hotel bars and lounges enhance stays with sophisticated evening entertainment. These spaces provide perfect settings for relaxation and socializing.

- """, - "featured_image": "https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?w=1200&h=800&fit=crop", - "tags": ["Entertainment", "Bars", "Nightlife"], - "meta_title": "Hotel Bars and Lounges: Evening Entertainment | Nightlife", - "meta_description": "Discover hotel bars and lounges offering craft cocktails, live music, and sophisticated atmospheres for evening relaxation and socializing.", - "meta_keywords": "hotel bars, hotel lounges, nightlife, cocktails, evening entertainment" - }, - { - "title": "Hotel Fitness Centers: Staying Active While Traveling", - "slug": "hotel-fitness-centers-staying-active-traveling", - "excerpt": "Maintain your fitness routine with hotel fitness centers featuring modern equipment, classes, and wellness facilities.", - "content": """ -

Introduction

-

Hotel fitness centers allow travelers to maintain exercise routines while away from home. Modern facilities provide comprehensive workout options.

- -

Equipment Quality

-

Hotel fitness centers vary in equipment quality and variety. Premium hotels offer state-of-the-art equipment matching commercial gyms.

- -

Operating Hours

-

Many hotel fitness centers offer 24-hour access, accommodating early risers and late-night exercisers. Check hours when booking.

- -

Group Classes

-

Some hotels offer fitness classes like yoga, Pilates, or spinning. These classes provide structured workouts and social interaction.

- -

Personal Training

-

Premium hotels may offer personal training services. These sessions provide personalized guidance and motivation during your stay.

- -

Outdoor Options

-

Hotels in scenic locations may offer outdoor fitness options like running trails, tennis courts, or water sports facilities.

- -

Wellness Integration

-

Fitness centers often integrate with hotel spas and wellness programs, providing comprehensive health and fitness experiences.

- -

Conclusion

-

Hotel fitness centers enable travelers to maintain active lifestyles. Understanding facilities and options helps you stay fit while traveling.

- """, - "featured_image": "https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=1200&h=800&fit=crop", - "tags": ["Fitness", "Wellness", "Health"], - "meta_title": "Hotel Fitness Centers: Staying Active While Traveling", - "meta_description": "Maintain your fitness routine with hotel fitness centers featuring modern equipment, classes, and wellness facilities.", - "meta_keywords": "hotel fitness, gym, fitness centers, travel fitness, hotel gym" - }, - { - "title": "Hotel Meeting Spaces: Hosting Successful Events", - "slug": "hotel-meeting-spaces-hosting-successful-events", - "excerpt": "Plan and execute successful meetings, conferences, and events using hotel meeting spaces and professional event services.", - "content": """ -

Introduction

-

Hotel meeting spaces provide professional settings for business events, conferences, and social gatherings. Understanding options ensures successful events.

- -

Space Types

-

Hotels offer various meeting spaces—boardrooms, ballrooms, conference halls, and breakout rooms. Each serves different event types and sizes.

- -

Technology and Equipment

-

Modern meeting spaces include AV equipment, high-speed internet, video conferencing, and presentation technology. Verify available equipment when booking.

- -

Catering Services

-

Hotel catering provides meals, breaks, and beverages for events. Professional catering ensures quality food service throughout your event.

- -

Event Planning Support

-

Hotel event coordinators assist with planning, setup, and execution. Their expertise ensures smooth, professional events.

- -

Flexible Configurations

-

Meeting spaces can be configured for various setups—theater, classroom, banquet, or boardroom styles. Choose configurations matching your needs.

- -

Accommodation Packages

-

Hotels often offer packages combining meeting spaces with guest rooms, providing convenience and value for event attendees.

- -

Conclusion

-

Hotel meeting spaces provide professional environments for successful events. Leveraging hotel services and expertise ensures memorable gatherings.

- """, - "featured_image": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop", - "tags": ["Business", "Meetings", "Events"], - "meta_title": "Hotel Meeting Spaces: Hosting Successful Events | Business", - "meta_description": "Plan and execute successful meetings, conferences, and events using hotel meeting spaces and professional event services.", - "meta_keywords": "hotel meetings, conference rooms, event spaces, business meetings, hotel events" - } -] - -def seed_blog_posts(): - """Seed the database with sample blog posts""" - db: Session = SessionLocal() - - try: - # Get the first admin user as the author - admin_role = db.query(Role).filter(Role.name == 'admin').first() - if not admin_role: - print("Error: Admin role not found. Please seed roles first.") - return - - admin_user = db.query(User).filter(User.role_id == admin_role.id).first() - - if not admin_user: - print("Error: No admin user found. Please create an admin user first.") - return - - # Check existing posts - existing_posts = db.query(BlogPost).all() - existing_slugs = {post.slug for post in existing_posts} - existing_count = len(existing_posts) - - if existing_count >= len(BLOG_POSTS): - print(f"Blog posts already exist ({existing_count} posts). All posts have been seeded.") - return - - # Filter out posts that already exist - posts_to_create = [post for post in BLOG_POSTS if post["slug"] not in existing_slugs] - - if not posts_to_create: - print(f"All blog posts from seed data already exist ({existing_count} posts).") - return - - print(f"Found {existing_count} existing posts. Adding {len(posts_to_create)} new posts...") - - # Create blog posts - created_posts = [] - # Set base date to 60 days ago to ensure all dates are in the past - base_date = datetime.utcnow() - timedelta(days=60) - - # Start from the number of existing posts to maintain date spacing - for i, post_data in enumerate(posts_to_create): - # Stagger dates going backwards from base_date to ensure all are in the past - # Each post is 2 days earlier than the previous one - post_date = base_date - timedelta(days=i * 2) - post = BlogPost( - title=post_data["title"], - slug=post_data["slug"], - excerpt=post_data["excerpt"], - content=post_data["content"], - featured_image=post_data["featured_image"], - author_id=admin_user.id, - tags=json.dumps(post_data["tags"]), - meta_title=post_data["meta_title"], - meta_description=post_data["meta_description"], - meta_keywords=post_data["meta_keywords"], - is_published=True, - published_at=post_date, # All dates in the past - views_count=0 - ) - db.add(post) - created_posts.append(post) - - db.commit() - - print(f"Successfully created {len(created_posts)} blog posts:") - for post in created_posts: - print(f" - {post.title} (slug: {post.slug})") - - except Exception as e: - db.rollback() - print(f"Error seeding blog posts: {str(e)}") - raise - finally: - db.close() - -if __name__ == "__main__": - print("Seeding blog posts...") - seed_blog_posts() - print("Done!") - diff --git a/Backend/seeds_data/seed_bookings.py b/Backend/seeds_data/seed_bookings.py deleted file mode 100755 index ed200dda..00000000 --- a/Backend/seeds_data/seed_bookings.py +++ /dev/null @@ -1,329 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to seed sample bookings for testing check-in/check-out and housekeeping notifications. -""" - -import sys -import os -from pathlib import Path -# Add both the seeds_data directory and the Backend directory to the path -sys.path.insert(0, str(Path(__file__).parent)) -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from sqlalchemy import and_ -from src.shared.config.database import SessionLocal -# Import all models needed for SQLAlchemy relationship setup -# Import base models first -from src.auth.models.role import Role -from src.auth.models.user import User -from src.rooms.models.room import Room, RoomStatus -from src.rooms.models.room_type import RoomType # For Room relationship -from src.rooms.models.rate_plan import RatePlan # For RoomType relationship -from src.rooms.models.room_maintenance import RoomMaintenance # For Room relationship -from src.rooms.models.room_inspection import RoomInspection # For Room relationship -from src.rooms.models.room_attribute import RoomAttribute # For Room relationship -from src.hotel_services.models.housekeeping_task import HousekeepingTask # For Room relationship -from src.bookings.models.booking import Booking, BookingStatus -from src.bookings.models.group_booking import GroupBooking # For Booking relationship - -# Import all related models to satisfy SQLAlchemy relationships -# This is necessary because SQLAlchemy needs all related models loaded -try: - from src.auth.models.refresh_token import RefreshToken - from src.reviews.models.review import Review - from src.reviews.models.favorite import Favorite - from src.hotel_services.models.service import Service # For ServiceBooking relationship - from src.hotel_services.models.service_booking import ServiceBooking - from src.hotel_services.models.service_usage import ServiceUsage # For Booking relationship - from src.bookings.models.checkin_checkout import CheckInCheckOut - from src.payments.models.payment import Payment - from src.payments.models.invoice import Invoice - from src.ai.models.chat import Chat - from src.loyalty.models.user_loyalty import UserLoyalty - from src.loyalty.models.loyalty_tier import LoyaltyTier # For UserLoyalty relationship - from src.loyalty.models.loyalty_point_transaction import LoyaltyPointTransaction # For UserLoyalty relationship - from src.loyalty.models.referral import Referral - from src.loyalty.models.package import Package # For RoomType relationship - from src.guest_management.models.guest_preference import GuestPreference - from src.guest_management.models.guest_note import GuestNote - from src.guest_management.models.guest_communication import GuestCommunication - from src.guest_management.models.guest_tag import GuestTag, guest_tag_association - from src.guest_management.models.guest_segment import GuestSegment, guest_segment_association - # Import any other models that might be related -except ImportError: - pass # Some models might not exist, that's okay -from datetime import datetime, timedelta -import random -import secrets - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def generate_booking_number() -> str: - """Generate a unique booking number""" - prefix = 'BK' - ts = int(datetime.utcnow().timestamp() * 1000) - rand = secrets.randbelow(9000) + 1000 - return f'{prefix}-{ts}-{rand}' - -def seed_bookings(db: Session): - print('=' * 80) - print('SEEDING SAMPLE BOOKINGS FOR TESTING') - print('=' * 80) - - # Get roles - customer_role = db.query(Role).filter(Role.name == 'customer').first() - staff_role = db.query(Role).filter(Role.name == 'staff').first() - admin_role = db.query(Role).filter(Role.name == 'admin').first() - - if not customer_role: - print(' ❌ Customer role not found! Please seed roles first.') - return - - # Get customers - customers = db.query(User).filter(User.role_id == customer_role.id).all() - if not customers: - print(' ❌ No customer users found! Please seed users first.') - return - - # Get available rooms - rooms = db.query(Room).all() - if not rooms: - print(' ❌ No rooms found! Please seed rooms first.') - return - - print(f'\n✓ Found {len(customers)} customer(s) and {len(rooms)} room(s)') - - # Delete existing bookings (optional - comment out if you want to keep existing bookings) - existing_bookings = db.query(Booking).all() - if existing_bookings: - print(f'\n🗑️ Deleting {len(existing_bookings)} existing booking(s)...') - booking_ids = [b.id for b in existing_bookings] - - # Delete related records first to avoid foreign key constraints - try: - from src.guest_management.models.guest_complaint import GuestComplaint, ComplaintUpdate - complaints = db.query(GuestComplaint).filter(GuestComplaint.booking_id.in_(booking_ids)).all() - if complaints: - complaint_ids = [c.id for c in complaints] - # Delete complaint updates first - updates = db.query(ComplaintUpdate).filter(ComplaintUpdate.complaint_id.in_(complaint_ids)).all() - if updates: - for update in updates: - db.delete(update) - print(f' ✓ Deleted {len(updates)} complaint update(s)') - # Then delete complaints - for complaint in complaints: - db.delete(complaint) - print(f' ✓ Deleted {len(complaints)} guest complaint(s)') - except ImportError: - pass - - for booking in existing_bookings: - db.delete(booking) - db.commit() - print(f'✓ Deleted {len(existing_bookings)} booking(s)') - - # Create sample bookings with different statuses - now = datetime.utcnow() - bookings_data = [] - - # 1. Past bookings (checked out) - These should trigger cleaning notifications - print('\n📅 Creating past bookings (checked out)...') - for i in range(3): - room = random.choice(rooms) - customer = random.choice(customers) - - # Check-out was 1-3 days ago - days_ago = random.randint(1, 3) - check_out_date = now - timedelta(days=days_ago) - check_in_date = check_out_date - timedelta(days=random.randint(1, 5)) - - # Calculate price - base_price = float(room.price) if room.price else 100.0 - num_nights = (check_out_date - check_in_date).days - total_price = base_price * num_nights - - booking = Booking( - booking_number=generate_booking_number(), - user_id=customer.id, - room_id=room.id, - check_in_date=check_in_date, - check_out_date=check_out_date, - num_guests=random.randint(1, min(room.capacity, 4)), - total_price=total_price, - status=BookingStatus.checked_out, - deposit_paid=True, - requires_deposit=False, - special_requests=f'Sample booking for testing - Checked out {days_ago} day(s) ago' - ) - db.add(booking) - db.flush() # Flush to get booking ID - - # Set room status to cleaning for checked out bookings - # Check if there's active maintenance first - try: - from src.rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus - active_maintenance = db.query(RoomMaintenance).filter( - and_( - RoomMaintenance.room_id == room.id, - RoomMaintenance.blocks_room == True, - RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress]) - ) - ).first() - if not active_maintenance: - room.status = RoomStatus.cleaning - - # Create housekeeping task for checkout cleaning - try: - from src.hotel_services.models.housekeeping_task import HousekeepingTask, HousekeepingStatus, HousekeepingType - checkout_checklist = [ - {'item': 'Bathroom cleaned', 'completed': False, 'notes': ''}, - {'item': 'Beds made with fresh linens', 'completed': False, 'notes': ''}, - {'item': 'Trash emptied', 'completed': False, 'notes': ''}, - {'item': 'Towels replaced', 'completed': False, 'notes': ''}, - {'item': 'Amenities restocked', 'completed': False, 'notes': ''}, - {'item': 'Floor vacuumed and mopped', 'completed': False, 'notes': ''}, - {'item': 'Surfaces dusted', 'completed': False, 'notes': ''}, - {'item': 'Windows and mirrors cleaned', 'completed': False, 'notes': ''}, - ] - - housekeeping_task = HousekeepingTask( - room_id=room.id, - booking_id=booking.id, - task_type=HousekeepingType.checkout, - status=HousekeepingStatus.pending, - scheduled_time=datetime.utcnow(), - created_by=admin_role.id if admin_role else (staff_role.id if staff_role else None), - checklist_items=checkout_checklist, - notes=f'Auto-created on checkout for booking {booking.booking_number}', - estimated_duration_minutes=45 - ) - db.add(housekeeping_task) - except ImportError: - pass # If housekeeping models not available, skip task creation - except ImportError: - # If maintenance model not available, just set to cleaning - room.status = RoomStatus.cleaning - bookings_data.append({ - 'number': booking.booking_number, - 'room': room.room_number, - 'status': 'checked_out', - 'check_out': check_out_date.strftime('%Y-%m-%d') - }) - print(f' ✓ Created booking {booking.booking_number} - Room {room.room_number}, Checked out {days_ago} day(s) ago') - - # 2. Current bookings (checked in) - Guests currently staying - print('\n📅 Creating current bookings (checked in)...') - for i in range(2): - room = random.choice(rooms) - customer = random.choice(customers) - - # Checked in 1-3 days ago, checking out in 1-3 days - days_ago = random.randint(1, 3) - check_in_date = now - timedelta(days=days_ago) - check_out_date = now + timedelta(days=random.randint(1, 3)) - - base_price = float(room.price) if room.price else 100.0 - num_nights = (check_out_date - check_in_date).days - total_price = base_price * num_nights - - booking = Booking( - booking_number=generate_booking_number(), - user_id=customer.id, - room_id=room.id, - check_in_date=check_in_date, - check_out_date=check_out_date, - num_guests=random.randint(1, min(room.capacity, 4)), - total_price=total_price, - status=BookingStatus.checked_in, - deposit_paid=True, - requires_deposit=False, - special_requests=f'Sample booking for testing - Currently checked in' - ) - db.add(booking) - db.flush() # Flush to get booking ID - - # Set room status to occupied for checked in bookings - room.status = RoomStatus.occupied - bookings_data.append({ - 'number': booking.booking_number, - 'room': room.room_number, - 'status': 'checked_in', - 'check_out': check_out_date.strftime('%Y-%m-%d') - }) - print(f' ✓ Created booking {booking.booking_number} - Room {room.room_number}, Checked in {days_ago} day(s) ago') - - # 3. Future bookings (confirmed) - Upcoming reservations - print('\n📅 Creating future bookings (confirmed)...') - for i in range(3): - room = random.choice(rooms) - customer = random.choice(customers) - - # Check-in in 1-7 days, staying for 2-5 days - days_ahead = random.randint(1, 7) - check_in_date = now + timedelta(days=days_ahead) - check_out_date = check_in_date + timedelta(days=random.randint(2, 5)) - - base_price = float(room.price) if room.price else 100.0 - num_nights = (check_out_date - check_in_date).days - total_price = base_price * num_nights - - booking = Booking( - booking_number=generate_booking_number(), - user_id=customer.id, - room_id=room.id, - check_in_date=check_in_date, - check_out_date=check_out_date, - num_guests=random.randint(1, min(room.capacity, 4)), - total_price=total_price, - status=BookingStatus.confirmed, - deposit_paid=random.choice([True, False]), - requires_deposit=random.choice([True, False]), - special_requests=f'Sample booking for testing - Future reservation' - ) - db.add(booking) - bookings_data.append({ - 'number': booking.booking_number, - 'room': room.room_number, - 'status': 'confirmed', - 'check_in': check_in_date.strftime('%Y-%m-%d') - }) - print(f' ✓ Created booking {booking.booking_number} - Room {room.room_number}, Check-in in {days_ahead} day(s)') - - db.commit() - - print(f'\n✅ Successfully created {len(bookings_data)} sample bookings!') - print(f'\n📊 Summary:') - checked_out = sum(1 for b in bookings_data if b['status'] == 'checked_out') - checked_in = sum(1 for b in bookings_data if b['status'] == 'checked_in') - confirmed = sum(1 for b in bookings_data if b['status'] == 'confirmed') - print(f' - Checked out: {checked_out} (rooms should be in cleaning status)') - print(f' - Checked in: {checked_in} (guests currently staying)') - print(f' - Confirmed: {confirmed} (upcoming reservations)') - print('\n💡 To test notifications:') - print(' 1. Log in as staff user (staff@gnxsoft.com / staff123)') - print(' 2. Go to Bookings and mark a checked_in booking as checked_out') - print(' 3. Log in as housekeeping user (housekeeping@gnxsoft.com / P4eli240453.)') - print(' 4. You should receive a notification about the room needing cleaning') - print('=' * 80) - -def main(): - db = get_db() - try: - seed_bookings(db) - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_homepage_footer.py b/Backend/seeds_data/seed_homepage_footer.py deleted file mode 100644 index d85da6ad..00000000 --- a/Backend/seeds_data/seed_homepage_footer.py +++ /dev/null @@ -1,392 +0,0 @@ -import sys -import os -import json -from datetime import datetime, timedelta -# Add parent directory to path to import from src -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.content.models.page_content import PageContent, PageType - -def seed_homepage_content(db: Session): - existing = db.query(PageContent).filter(PageContent.page_type == PageType.HOME).first() - luxury_features = [{'icon': 'Sparkles', 'title': 'Premium Amenities', 'description': 'World-class facilities designed for your comfort and relaxation'}, {'icon': 'Crown', 'title': 'Royal Service', 'description': 'Dedicated concierge service available 24/7 for all your needs'}, {'icon': 'Award', 'title': 'Award-Winning', 'description': 'Recognized for excellence in hospitality and guest satisfaction'}, {'icon': 'Shield', 'title': 'Secure & Private', 'description': 'Your privacy and security are our top priorities'}, {'icon': 'Heart', 'title': 'Personalized Care', 'description': 'Tailored experiences crafted just for you'}, {'icon': 'Gem', 'title': 'Luxury Design', 'description': 'Elegantly designed spaces with attention to every detail'}] - luxury_gallery = ['https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800', 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800', 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=800', 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=800', 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=800', 'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=800'] - luxury_testimonials = [{'name': 'Sarah Johnson', 'title': 'Business Executive', 'quote': 'An absolutely stunning experience. The attention to detail and level of service exceeded all expectations.', 'image': 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200'}, {'name': 'Michael Chen', 'title': 'Travel Enthusiast', 'quote': 'The epitome of luxury. Every moment was perfect, from check-in to check-out.', 'image': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200'}, {'name': 'Emma Williams', 'title': 'Luxury Traveler', 'quote': 'This hotel redefines what luxury means. I will definitely return.', 'image': 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200'}] - luxury_services = [ - { - 'icon': 'UtensilsCrossed', - 'title': 'Fine Dining', - 'description': 'Michelin-starred restaurants offering world-class cuisine', - 'image': 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=600', - 'slug': 'fine-dining', - 'category': 'Dining', - 'content': '

Experience culinary excellence at our Michelin-starred restaurants. Our world-renowned chefs craft exquisite dishes using the finest ingredients sourced from around the globe. From intimate dining experiences to grand celebrations, we offer a variety of settings to suit every occasion.

Our restaurants feature seasonal menus that showcase the best of local and international cuisine, paired with an extensive wine collection curated by our master sommelier.

', - 'sections': [], - 'meta_title': 'Fine Dining Experience - Luxury Hotel', - 'meta_description': 'Discover our Michelin-starred restaurants offering world-class cuisine in elegant settings.', - 'meta_keywords': 'fine dining, michelin star, luxury restaurant, gourmet cuisine' - }, - { - 'icon': 'Wine', - 'title': 'Premium Bar', - 'description': 'Extensive wine collection and craft cocktails in elegant settings', - 'image': 'https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?w=600', - 'slug': 'premium-bar', - 'category': 'Dining', - 'content': '

Unwind in sophistication at our premium bar, featuring an extensive collection of rare wines, vintage spirits, and expertly crafted cocktails. Our master mixologists create unique beverages tailored to your preferences.

The elegant ambiance, combined with live music on select evenings, creates the perfect setting for business meetings, romantic evenings, or casual gatherings with friends.

', - 'sections': [], - 'meta_title': 'Premium Bar & Lounge - Luxury Hotel', - 'meta_description': 'Enjoy an extensive wine collection and craft cocktails in our elegant bar and lounge.', - 'meta_keywords': 'premium bar, wine collection, craft cocktails, luxury lounge' - }, - { - 'icon': 'Dumbbell', - 'title': 'Spa & Wellness', - 'description': 'Rejuvenating spa treatments and state-of-the-art fitness center', - 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=600', - 'slug': 'spa-wellness', - 'category': 'Wellness', - 'content': '

Escape to tranquility at our world-class spa and wellness center. Indulge in a range of rejuvenating treatments designed to restore balance and vitality. Our expert therapists use premium products and ancient techniques to provide an unparalleled wellness experience.

Our state-of-the-art fitness center is equipped with the latest equipment and offers personal training sessions, yoga classes, and wellness programs tailored to your needs.

', - 'sections': [], - 'meta_title': 'Spa & Wellness Center - Luxury Hotel', - 'meta_description': 'Rejuvenate with our spa treatments and state-of-the-art fitness facilities.', - 'meta_keywords': 'spa, wellness, fitness center, massage, luxury spa' - }, - { - 'icon': 'Car', - 'title': 'Concierge Services', - 'description': 'Personalized assistance for all your travel and entertainment needs', - 'image': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600', - 'slug': 'concierge-services', - 'category': 'Services', - 'content': '

Our dedicated concierge team is available 24/7 to ensure your stay is nothing short of extraordinary. From restaurant reservations and event tickets to private tours and transportation arrangements, we handle every detail with precision and care.

Whether you need assistance with business arrangements, special celebrations, or unique local experiences, our concierge team has the expertise and connections to make it happen.

', - 'sections': [], - 'meta_title': 'Concierge Services - Luxury Hotel', - 'meta_description': 'Personalized assistance for all your travel and entertainment needs, available 24/7.', - 'meta_keywords': 'concierge, personal assistant, travel services, luxury service' - } - ] - luxury_experiences = [{'icon': 'Sunset', 'title': 'Sunset Rooftop', 'description': 'Breathtaking views and exclusive rooftop experiences', 'image': 'https://images.unsplash.com/photo-1514933651103-005eec06c04b?w=600'}, {'icon': 'Ship', 'title': 'Yacht Excursions', 'description': 'Private yacht charters for unforgettable sea adventures', 'image': 'https://images.unsplash.com/photo-1544551763-46a013bb70d5?w=600'}, {'icon': 'Music', 'title': 'Live Entertainment', 'description': 'World-class performances and exclusive events', 'image': 'https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?w=600'}, {'icon': 'Palette', 'title': 'Art & Culture', 'description': 'Curated art collections and cultural experiences', 'image': 'https://images.unsplash.com/photo-1578301978018-3005759f48f7?w=600'}] - awards = [{'icon': 'Trophy', 'title': 'Best Luxury Hotel 2024', 'description': 'Awarded by International Luxury Travel Association', 'image': 'https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=400', 'year': '2024'}, {'icon': 'Star', 'title': '5-Star Excellence', 'description': 'Consistently rated 5 stars by leading travel publications', 'image': 'https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400', 'year': '2023'}, {'icon': 'Award', 'title': 'Sustainable Luxury', 'description': 'Recognized for environmental responsibility and sustainability', 'image': 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=400', 'year': '2024'}] - partners = [{'name': 'Luxury Travel Group', 'logo': 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200', 'link': '#'}, {'name': 'Premium Airlines', 'logo': 'https://images.unsplash.com/photo-1436491865332-7a61a109cc05?w=200', 'link': '#'}, {'name': 'Exclusive Events', 'logo': 'https://images.unsplash.com/photo-1511578314322-379afb476865?w=200', 'link': '#'}, {'name': 'Fine Dining Network', 'logo': 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=200', 'link': '#'}] - stats = [{'icon': 'Users', 'number': '50,000+', 'label': 'Happy Guests'}, {'icon': 'Award', 'number': '25+', 'label': 'Awards Won'}, {'icon': 'Star', 'number': '4.9', 'label': 'Average Rating'}, {'icon': 'Globe', 'number': '100+', 'label': 'Countries Served'}] - amenities = [{'icon': 'Wifi', 'title': 'High-Speed WiFi', 'description': 'Complimentary high-speed internet throughout the property', 'image': ''}, {'icon': 'Coffee', 'title': '24/7 Room Service', 'description': 'Round-the-clock dining and beverage service', 'image': ''}, {'icon': 'Car', 'title': 'Valet Parking', 'description': 'Complimentary valet parking for all guests', 'image': ''}, {'icon': 'Plane', 'title': 'Airport Transfer', 'description': 'Luxury airport transfer service available', 'image': ''}] - features = [{'icon': 'Shield', 'title': 'Secure & Safe', 'description': '24/7 security and state-of-the-art safety systems'}, {'icon': 'Wifi', 'title': 'Free WiFi', 'description': 'High-speed internet access throughout the property'}, {'icon': 'Coffee', 'title': 'Room Service', 'description': '24/7 room service available for your convenience'}, {'icon': 'Car', 'title': 'Parking', 'description': 'Complimentary valet parking for all guests'}, {'icon': 'UtensilsCrossed', 'title': 'Fine Dining', 'description': 'World-class restaurants and dining experiences'}, {'icon': 'Dumbbell', 'title': 'Fitness Center', 'description': 'State-of-the-art fitness facilities'}] - testimonials = [{'name': 'Robert Martinez', 'role': 'CEO, Tech Corp', 'image': 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200', 'rating': 5, 'comment': 'Exceptional service and attention to detail. The staff went above and beyond to make our stay memorable.'}, {'name': 'Lisa Anderson', 'role': 'Travel Blogger', 'image': 'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?w=200', 'rating': 5, 'comment': "The most luxurious hotel experience I've ever had. Every detail was perfect."}, {'name': 'David Thompson', 'role': 'Investment Banker', 'image': 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200', 'rating': 5, 'comment': 'Outstanding facilities and impeccable service. Highly recommend for business travelers.'}] - gallery_images = ['https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=800', 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=800', 'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=800', 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800', 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=800', 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=800'] - - # Enterprise homepage fields - hero_video_url = 'https://videos.unsplash.com/video-1564501049412-61c2a3083791' - hero_video_poster = 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200' - features_section_title = 'Why Choose Us' - features_section_subtitle = 'Discover what makes us the perfect choice for your stay' - stats_section_title = 'Our Achievements' - stats_section_subtitle = 'Numbers that speak for themselves' - rooms_section_title = 'Luxurious Rooms & Suites' - rooms_section_subtitle = 'Experience comfort and elegance in every room' - rooms_section_button_text = 'View All Rooms' - rooms_section_button_link = '/rooms' - rooms_section_enabled = True - services_section_button_text = 'Explore All Services' - services_section_button_link = '/services' - services_section_limit = 4 - - # Sections enabled configuration - sections_enabled = { - 'features': True, - 'luxury': True, - 'gallery': True, - 'testimonials': True, - 'stats': True, - 'amenities': True, - 'about_preview': True, - 'services': True, - 'experiences': True, - 'awards': True, - 'cta': True, - 'partners': True, - 'rooms': True, - 'newsletter': True, - 'trust_badges': True, - 'promotions': True, - 'blog': True - } - - # Newsletter section - newsletter_section_title = 'Stay Connected' - newsletter_section_subtitle = 'Subscribe to our newsletter for exclusive offers and updates' - newsletter_placeholder = 'Enter your email address' - newsletter_button_text = 'Subscribe' - newsletter_enabled = True - - # Trust badges - trust_badges = [ - { - 'name': '5-Star Rating', - 'logo': 'https://images.unsplash.com/photo-1579783902614-a3fb3927b6a5?w=200', - 'description': 'Awarded 5 stars by leading travel organizations', - 'link': '#' - }, - { - 'name': 'TripAdvisor Excellence', - 'logo': 'https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=200', - 'description': 'Certificate of Excellence winner', - 'link': '#' - }, - { - 'name': 'Green Certified', - 'logo': 'https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=200', - 'description': 'Eco-friendly and sustainable practices', - 'link': '#' - }, - { - 'name': 'Luxury Collection', - 'logo': 'https://images.unsplash.com/photo-1599305445671-ac291c95aaa9?w=200', - 'description': 'Member of the world\'s finest luxury hotels', - 'link': '#' - } - ] - trust_badges_section_title = 'Trusted & Recognized' - trust_badges_section_subtitle = 'Awards and certifications that validate our commitment to excellence' - trust_badges_enabled = True - - # Promotions - Mix of valid and expired for testing - # Calculate dates relative to current date - today = datetime.now() - next_month = today + timedelta(days=30) - next_3_months = today + timedelta(days=90) - next_6_months = today + timedelta(days=180) - expired_1_month_ago = today - timedelta(days=30) - expired_3_months_ago = today - timedelta(days=90) - - promotions = [ - { - 'title': 'Early Bird Special', - 'description': 'Book 30 days in advance and save 20% on your stay. Perfect for planning ahead!', - 'image': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=600', - 'discount': '20% OFF', - 'valid_until': next_3_months.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Book Now', - 'code': 'EARLYBIRD20' - }, - { - 'title': 'Weekend Getaway', - 'description': 'Perfect weekend escape with complimentary breakfast and spa access. Relax and unwind!', - 'image': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=600', - 'discount': '30% OFF', - 'valid_until': next_month.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'View Offer', - 'code': 'WEEKEND30' - }, - { - 'title': 'Luxury Suite Package', - 'description': 'Experience our premium suites with exclusive amenities, fine dining, and concierge service', - 'image': 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=600', - 'discount': 'Save $200', - 'valid_until': next_6_months.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Explore Suites', - 'code': 'LUXURY200' - }, - { - 'title': 'Honeymoon Special', - 'description': 'Romantic getaway with champagne, flowers, special amenities, and complimentary room upgrade', - 'image': 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=600', - 'discount': '25% OFF', - 'valid_until': next_3_months.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Book Package', - 'code': 'HONEYMOON25' - }, - { - 'title': 'Family Fun Package', - 'description': 'Perfect for families! Includes family room, kids activities, and complimentary meals for children under 12', - 'image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=600', - 'discount': '15% OFF', - 'valid_until': next_6_months.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Book Now', - 'code': 'FAMILY15' - }, - { - 'title': 'Business Traveler', - 'description': 'Extended stay discounts for business travelers. Includes high-speed WiFi, workspace, and airport transfer', - 'image': 'https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?w=600', - 'discount': '10% OFF', - 'valid_until': next_3_months.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Learn More', - 'code': 'BUSINESS10' - }, - # Expired promotions for testing display logic - { - 'title': 'Summer Special', - 'description': 'Enjoy 25% off on all room bookings this summer. Limited time offer!', - 'image': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=600', - 'discount': '25% OFF', - 'valid_until': expired_3_months_ago.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'Book Now', - 'code': 'SUMMER25' - }, - { - 'title': 'New Year Celebration', - 'description': 'Ring in the new year with our special celebration package. Includes party access and premium amenities', - 'image': 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=600', - 'discount': '35% OFF', - 'valid_until': expired_1_month_ago.strftime('%Y-%m-%d'), - 'link': '/rooms', - 'button_text': 'View Offer', - 'code': 'NEWYEAR35' - } - ] - promotions_section_title = 'Special Offers' - promotions_section_subtitle = 'Exclusive deals and packages designed just for you' - promotions_enabled = True - - # Blog section - blog_section_title = 'Latest News & Updates' - blog_section_subtitle = 'Stay informed about our latest news, events, and travel tips' - blog_posts_limit = 3 - blog_enabled = True - - homepage_data = { - 'page_type': PageType.HOME, - 'title': 'Luxury Hotel - Experience Unparalleled Elegance', - 'subtitle': 'Where timeless luxury meets modern sophistication', - 'description': 'Discover a world of refined elegance and exceptional service', - 'hero_title': 'Welcome to Luxury', - 'hero_subtitle': 'Experience the pinnacle of hospitality', - 'hero_image': 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200', - 'hero_video_url': hero_video_url, - 'hero_video_poster': hero_video_poster, - 'features': json.dumps(features), - 'features_section_title': features_section_title, - 'features_section_subtitle': features_section_subtitle, - 'luxury_section_title': 'Experience Unparalleled Luxury', - 'luxury_section_subtitle': 'Where elegance meets comfort in every detail', - 'luxury_section_image': 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=1200', - 'luxury_features': json.dumps(luxury_features), - 'luxury_gallery_section_title': 'Our Luxury Gallery', - 'luxury_gallery_section_subtitle': 'A glimpse into our world of elegance', - 'luxury_gallery': json.dumps(luxury_gallery), - 'luxury_testimonials_section_title': 'What Our Guests Say', - 'luxury_testimonials_section_subtitle': 'Testimonials from our valued guests', - 'luxury_testimonials': json.dumps(luxury_testimonials), - 'luxury_services_section_title': 'Premium Services', - 'luxury_services_section_subtitle': 'Indulge in our world-class amenities', - 'luxury_services': json.dumps(luxury_services), - 'services_section_button_text': services_section_button_text, - 'services_section_button_link': services_section_button_link, - 'services_section_limit': services_section_limit, - 'luxury_experiences_section_title': 'Exclusive Experiences', - 'luxury_experiences_section_subtitle': 'Create unforgettable memories', - 'luxury_experiences': json.dumps(luxury_experiences), - 'awards_section_title': 'Awards & Recognition', - 'awards_section_subtitle': 'Recognized for excellence worldwide', - 'awards': json.dumps(awards), - 'partners_section_title': 'Our Partners', - 'partners_section_subtitle': 'Trusted by leading brands', - 'partners': json.dumps(partners), - 'amenities_section_title': 'Premium Amenities', - 'amenities_section_subtitle': 'Everything you need for a perfect stay', - 'amenities': json.dumps(amenities), - 'testimonials_section_title': 'Guest Reviews', - 'testimonials_section_subtitle': 'Hear from our satisfied guests', - 'testimonials': json.dumps(testimonials), - 'gallery_section_title': 'Photo Gallery', - 'gallery_section_subtitle': 'Explore our beautiful spaces', - 'gallery_images': json.dumps(gallery_images), - 'about_preview_title': 'About Our Luxury Hotel', - 'about_preview_subtitle': 'A legacy of excellence', - 'about_preview_content': 'Discover a world of refined elegance and exceptional service. Our hotel combines timeless luxury with modern amenities to create an unforgettable experience. With over 50,000 satisfied guests and numerous awards, we continue to set the standard for luxury hospitality.', - 'about_preview_image': 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=800', - 'stats': json.dumps(stats), - 'stats_section_title': stats_section_title, - 'stats_section_subtitle': stats_section_subtitle, - 'rooms_section_title': rooms_section_title, - 'rooms_section_subtitle': rooms_section_subtitle, - 'rooms_section_button_text': rooms_section_button_text, - 'rooms_section_button_link': rooms_section_button_link, - 'rooms_section_enabled': rooms_section_enabled, - 'sections_enabled': json.dumps(sections_enabled), - 'newsletter_section_title': newsletter_section_title, - 'newsletter_section_subtitle': newsletter_section_subtitle, - 'newsletter_placeholder': newsletter_placeholder, - 'newsletter_button_text': newsletter_button_text, - 'newsletter_enabled': newsletter_enabled, - 'trust_badges_section_title': trust_badges_section_title, - 'trust_badges_section_subtitle': trust_badges_section_subtitle, - 'trust_badges': json.dumps(trust_badges), - 'trust_badges_enabled': trust_badges_enabled, - 'promotions_section_title': promotions_section_title, - 'promotions_section_subtitle': promotions_section_subtitle, - 'promotions': json.dumps(promotions), - 'promotions_enabled': promotions_enabled, - 'blog_section_title': blog_section_title, - 'blog_section_subtitle': blog_section_subtitle, - 'blog_posts_limit': blog_posts_limit, - 'blog_enabled': blog_enabled, - 'cta_title': 'Ready to Experience Luxury?', - 'cta_subtitle': 'Book your stay today and discover the difference', - 'cta_button_text': 'Book Now', - 'cta_button_link': '/rooms', - 'cta_image': 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200', - 'is_active': True - } - if existing: - for key, value in homepage_data.items(): - if key != 'page_type': - setattr(existing, key, value) - print('✓ Updated existing homepage content') - else: - new_content = PageContent(**homepage_data) - db.add(new_content) - print('✓ Created new homepage content') - db.commit() - -def seed_footer_content(db: Session): - existing = db.query(PageContent).filter(PageContent.page_type == PageType.FOOTER).first() - contact_info = {'phone': '+1 (555) 123-4567', 'email': 'info@luxuryhotel.com', 'address': '123 Luxury Avenue, Premium District, City 12345'} - social_links = {'facebook': 'https://facebook.com/luxuryhotel', 'twitter': 'https://twitter.com/luxuryhotel', 'instagram': 'https://instagram.com/luxuryhotel', 'linkedin': 'https://linkedin.com/company/luxuryhotel', 'youtube': 'https://youtube.com/luxuryhotel'} - footer_links = {'quick_links': [{'label': 'Home', 'url': '/'}, {'label': 'Rooms & Suites', 'url': '/rooms'}, {'label': 'About Us', 'url': '/about'}, {'label': 'Contact', 'url': '/contact'}, {'label': 'Gallery', 'url': '/gallery'}], 'support_links': [{'label': 'FAQ', 'url': '/faq'}, {'label': 'Privacy Policy', 'url': '/privacy'}, {'label': 'Terms of Service', 'url': '/terms'}, {'label': 'Cancellation Policy', 'url': '/cancellation'}, {'label': 'Accessibility', 'url': '/accessibility'}]} - badges = [{'text': '5-Star Rated', 'icon': 'Star'}, {'text': 'Award Winning', 'icon': 'Award'}, {'text': 'Eco Certified', 'icon': 'Leaf'}, {'text': 'Luxury Collection', 'icon': 'Crown'}] - footer_data = {'page_type': PageType.FOOTER, 'title': 'Luxury Hotel', 'subtitle': 'Experience Unparalleled Elegance', 'description': 'Your gateway to luxury hospitality and exceptional service', 'contact_info': json.dumps(contact_info), 'social_links': json.dumps(social_links), 'footer_links': json.dumps(footer_links), 'badges': json.dumps(badges), 'copyright_text': '© {YEAR} Luxury Hotel. All rights reserved.', 'is_active': True} - if existing: - for key, value in footer_data.items(): - if key != 'page_type': - setattr(existing, key, value) - print('✓ Updated existing footer content') - else: - new_content = PageContent(**footer_data) - db.add(new_content) - print('✓ Created new footer content') - db.commit() - -def main(): - db: Session = SessionLocal() - try: - print('=' * 80) - print('SEEDING HOMEPAGE AND FOOTER CONTENT') - print('=' * 80) - print() - print('Seeding homepage content...') - seed_homepage_content(db) - print('\nSeeding footer content...') - seed_footer_content(db) - print('\n' + '=' * 80) - print('✓ All content seeded successfully!') - print('=' * 80) - except Exception as e: - db.rollback() - print(f'\n✗ Error seeding content: {e}') - import traceback - traceback.print_exc() - raise - finally: - db.close() -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/Backend/seeds_data/seed_initial_data.py b/Backend/seeds_data/seed_initial_data.py deleted file mode 100644 index 0cce2563..00000000 --- a/Backend/seeds_data/seed_initial_data.py +++ /dev/null @@ -1,163 +0,0 @@ -import sys -import os -from pathlib import Path -# Add both the seeds_data directory and the Backend directory to the path -sys.path.insert(0, str(Path(__file__).parent)) -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -# Import all models needed for SQLAlchemy relationship setup -from src.auth.models.role import Role -from src.auth.models.user import User -from src.rooms.models.room_type import RoomType -from src.rooms.models.room import Room -from src.rooms.models.rate_plan import RatePlan # For RoomType relationship -from src.bookings.models.booking import Booking # For Room relationship -import bcrypt -from datetime import datetime - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_roles(db: Session): - print('=' * 80) - print('SEEDING ROLES') - print('=' * 80) - - roles_data = [ - {'name': 'admin', 'description': 'Administrator with full access'}, - {'name': 'staff', 'description': 'Staff member with limited admin access'}, - {'name': 'customer', 'description': 'Regular customer'}, - {'name': 'accountant', 'description': 'Accountant role with access to financial data, payments, and invoices'}, - {'name': 'housekeeping', 'description': 'Housekeeping staff role with access to room cleaning tasks and status updates'} - ] - - for role_data in roles_data: - existing = db.query(Role).filter(Role.name == role_data['name']).first() - if existing: - print(f' ✓ Role "{role_data["name"]}" already exists') - else: - role = Role(**role_data) - db.add(role) - print(f' ✓ Created role: {role_data["name"]}') - - db.commit() - print('✓ Roles seeded successfully!\n') - -def seed_room_types(db: Session): - print('=' * 80) - print('SEEDING ROOM TYPES') - print('=' * 80) - - room_types_data = [ - { - 'name': 'Standard Room', - 'description': 'Comfortable and well-appointed standard accommodation', - 'base_price': 150.00, - 'capacity': 2, - 'amenities': ['Free WiFi', 'Air Conditioning', 'TV', 'Minibar', 'Safe'] - }, - { - 'name': 'Superior Room', - 'description': 'Spacious room with enhanced amenities and better views', - 'base_price': 200.00, - 'capacity': 2, - 'amenities': ['Free WiFi', 'Air Conditioning', 'Smart TV', 'Minibar', 'Safe', 'Coffee Maker'] - }, - { - 'name': 'Deluxe Room', - 'description': 'Luxurious room with premium furnishings and amenities', - 'base_price': 280.00, - 'capacity': 3, - 'amenities': ['Free WiFi', 'Air Conditioning', 'Smart TV', 'Netflix', 'Minibar', 'Safe', 'Coffee Maker', 'Premium Toiletries'] - }, - { - 'name': 'Executive Suite', - 'description': 'Elegant suite with separate living area and premium amenities', - 'base_price': 400.00, - 'capacity': 4, - 'amenities': ['Free WiFi', 'Air Conditioning', 'Smart TV', 'Netflix', 'Minibar', 'Safe', 'Espresso Machine', 'Premium Toiletries', 'Bathrobes', 'Work Desk'] - }, - { - 'name': 'Presidential Suite', - 'description': 'The ultimate in luxury with expansive space and exclusive amenities', - 'base_price': 800.00, - 'capacity': 6, - 'amenities': ['Free WiFi', 'Air Conditioning', 'Smart TV', 'Netflix', 'Private Bar', 'Jacuzzi', 'Butler Service', 'Premium Toiletries', 'Bathrobes', 'Private Terrace'] - } - ] - - import json - for rt_data in room_types_data: - existing = db.query(RoomType).filter(RoomType.name == rt_data['name']).first() - if existing: - print(f' ✓ Room type "{rt_data["name"]}" already exists') - else: - amenities = rt_data.pop('amenities') - room_type = RoomType(**rt_data, amenities=json.dumps(amenities)) - db.add(room_type) - print(f' ✓ Created room type: {rt_data["name"]} (€{rt_data["base_price"]:.2f}/night)') - - db.commit() - print('✓ Room types seeded successfully!\n') - -def seed_admin_user(db: Session): - print('=' * 80) - print('SEEDING ADMIN USER') - print('=' * 80) - - admin_role = db.query(Role).filter(Role.name == 'admin').first() - if not admin_role: - print(' ❌ Admin role not found! Please seed roles first.') - return - - admin_email = 'admin@hotel.com' - existing_admin = db.query(User).filter(User.email == admin_email).first() - - if existing_admin: - print(f' ✓ Admin user "{admin_email}" already exists') - else: - password = 'admin123' # Default password - should be changed in production - password_bytes = password.encode('utf-8') - salt = bcrypt.gensalt() - hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - - admin_user = User( - email=admin_email, - password=hashed_password, - full_name='Administrator', - role_id=admin_role.id, - is_active=True, - currency='EUR' - ) - db.add(admin_user) - db.commit() - print(f' ✓ Created admin user: {admin_email}') - print(f' ⚠️ Default password: admin123 (please change in production!)') - - print('✓ Admin user seeded successfully!\n') - -def main(): - db = get_db() - try: - seed_roles(db) - seed_room_types(db) - seed_admin_user(db) - print('=' * 80) - print('✅ Initial data seeding completed successfully!') - print('=' * 80) - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_loyalty_rewards.py b/Backend/seeds_data/seed_loyalty_rewards.py deleted file mode 100644 index f55e398f..00000000 --- a/Backend/seeds_data/seed_loyalty_rewards.py +++ /dev/null @@ -1,303 +0,0 @@ -import sys -import os -from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.loyalty_reward import LoyaltyReward, RewardType -from src.models.loyalty_tier import LoyaltyTier, TierLevel -from datetime import datetime, timedelta - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_loyalty_rewards(db: Session): - print('=' * 80) - print('SEEDING LOYALTY REWARDS') - print('=' * 80) - - # Get tier IDs for tier-specific rewards - bronze_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.bronze).first() - silver_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.silver).first() - gold_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.gold).first() - platinum_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.platinum).first() - - # Create default tiers if they don't exist - if not bronze_tier: - from src.services.loyalty_service import LoyaltyService - LoyaltyService.create_default_tiers(db) - db.refresh() - bronze_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.bronze).first() - silver_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.silver).first() - gold_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.gold).first() - platinum_tier = db.query(LoyaltyTier).filter(LoyaltyTier.level == TierLevel.platinum).first() - - # Sample rewards data - rewards_data = [ - # Discount Rewards (Available to all tiers) - { - 'name': '5% Booking Discount', - 'description': 'Get 5% off your next booking. Valid for bookings over $100.', - 'reward_type': RewardType.discount, - 'points_cost': 5000, - 'discount_percentage': 5.0, - 'max_discount_amount': 50.0, - 'min_booking_amount': 100.0, - 'applicable_tier_id': None, # Available to all tiers - 'stock_quantity': None, # Unlimited - 'icon': '🎫', - 'is_active': True - }, - { - 'name': '10% Booking Discount', - 'description': 'Get 10% off your next booking. Valid for bookings over $200.', - 'reward_type': RewardType.discount, - 'points_cost': 10000, - 'discount_percentage': 10.0, - 'max_discount_amount': 100.0, - 'min_booking_amount': 200.0, - 'applicable_tier_id': silver_tier.id if silver_tier else None, - 'stock_quantity': None, - 'icon': '🎫', - 'is_active': True - }, - { - 'name': '15% Premium Discount', - 'description': 'Get 15% off your next booking. Maximum discount $150. Valid for bookings over $300.', - 'reward_type': RewardType.discount, - 'points_cost': 15000, - 'discount_percentage': 15.0, - 'max_discount_amount': 150.0, - 'min_booking_amount': 300.0, - 'applicable_tier_id': gold_tier.id if gold_tier else None, - 'stock_quantity': None, - 'icon': '💎', - 'is_active': True - }, - { - 'name': '20% VIP Discount', - 'description': 'Exclusive 20% discount for Platinum members. Maximum discount $200. Valid for bookings over $500.', - 'reward_type': RewardType.discount, - 'points_cost': 25000, - 'discount_percentage': 20.0, - 'max_discount_amount': 200.0, - 'min_booking_amount': 500.0, - 'applicable_tier_id': platinum_tier.id if platinum_tier else None, - 'stock_quantity': None, - 'icon': '👑', - 'is_active': True - }, - - # Room Upgrade Rewards - { - 'name': 'Complimentary Room Upgrade', - 'description': 'Upgrade to the next room category at no extra cost. Subject to availability at check-in.', - 'reward_type': RewardType.room_upgrade, - 'points_cost': 20000, - 'applicable_tier_id': silver_tier.id if silver_tier else None, - 'stock_quantity': 50, # Limited stock - 'icon': '🛏️', - 'is_active': True - }, - { - 'name': 'Premium Suite Upgrade', - 'description': 'Upgrade to a premium suite or executive room. Subject to availability.', - 'reward_type': RewardType.room_upgrade, - 'points_cost': 35000, - 'applicable_tier_id': gold_tier.id if gold_tier else None, - 'stock_quantity': 30, - 'icon': '🏨', - 'is_active': True - }, - { - 'name': 'Luxury Suite Upgrade', - 'description': 'Upgrade to our most luxurious suite category. Ultimate comfort guaranteed. Subject to availability.', - 'reward_type': RewardType.room_upgrade, - 'points_cost': 50000, - 'applicable_tier_id': platinum_tier.id if platinum_tier else None, - 'stock_quantity': 20, - 'icon': '✨', - 'is_active': True - }, - - # Amenity Rewards - { - 'name': 'Welcome Amenity Package', - 'description': 'Complimentary welcome basket with fruits, chocolates, and wine upon arrival.', - 'reward_type': RewardType.amenity, - 'points_cost': 3000, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '🍾', - 'is_active': True - }, - { - 'name': 'Breakfast for Two', - 'description': 'Complimentary breakfast buffet for two guests during your stay.', - 'reward_type': RewardType.amenity, - 'points_cost': 8000, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '🍳', - 'is_active': True - }, - { - 'name': 'Spa Treatment Voucher', - 'description': 'One complimentary spa treatment of your choice (60 minutes). Valid for 90 days.', - 'reward_type': RewardType.amenity, - 'points_cost': 12000, - 'applicable_tier_id': silver_tier.id if silver_tier else None, - 'stock_quantity': 40, - 'icon': '💆', - 'is_active': True, - 'valid_until': datetime.utcnow() + timedelta(days=90) - }, - { - 'name': 'Romantic Dinner Package', - 'description': 'Private romantic dinner for two with wine pairing at our fine dining restaurant.', - 'reward_type': RewardType.amenity, - 'points_cost': 18000, - 'applicable_tier_id': gold_tier.id if gold_tier else None, - 'stock_quantity': 25, - 'icon': '🍽️', - 'is_active': True - }, - { - 'name': 'VIP Airport Transfer', - 'description': 'Complimentary luxury airport transfer (one-way) in premium vehicle.', - 'reward_type': RewardType.amenity, - 'points_cost': 15000, - 'applicable_tier_id': platinum_tier.id if platinum_tier else None, - 'stock_quantity': 20, - 'icon': '🚗', - 'is_active': True - }, - - # Voucher Rewards - { - 'name': '$50 Hotel Credit', - 'description': 'Redeem for $50 credit towards room service, spa, or dining at the hotel. Valid for 6 months.', - 'reward_type': RewardType.voucher, - 'points_cost': 10000, - 'discount_amount': 50.0, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '💳', - 'is_active': True, - 'valid_until': datetime.utcnow() + timedelta(days=180) - }, - { - 'name': '$100 Hotel Credit', - 'description': 'Redeem for $100 credit towards any hotel service. Valid for 6 months.', - 'reward_type': RewardType.voucher, - 'points_cost': 20000, - 'discount_amount': 100.0, - 'applicable_tier_id': silver_tier.id if silver_tier else None, - 'stock_quantity': None, - 'icon': '💵', - 'is_active': True, - 'valid_until': datetime.utcnow() + timedelta(days=180) - }, - { - 'name': '$200 Premium Credit', - 'description': 'Redeem for $200 credit towards premium services. Perfect for special occasions. Valid for 1 year.', - 'reward_type': RewardType.voucher, - 'points_cost': 40000, - 'discount_amount': 200.0, - 'applicable_tier_id': gold_tier.id if gold_tier else None, - 'stock_quantity': None, - 'icon': '💰', - 'is_active': True, - 'valid_until': datetime.utcnow() + timedelta(days=365) - }, - - # Cashback Rewards - { - 'name': 'Early Check-in Benefit', - 'description': 'Guaranteed early check-in (12:00 PM) at no extra charge. Subject to availability.', - 'reward_type': RewardType.amenity, - 'points_cost': 2000, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '⏰', - 'is_active': True - }, - { - 'name': 'Late Check-out Benefit', - 'description': 'Extended check-out until 2:00 PM at no extra charge. Subject to availability.', - 'reward_type': RewardType.amenity, - 'points_cost': 2000, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '🕐', - 'is_active': True - }, - { - 'name': 'Free Night Stay', - 'description': 'One complimentary night stay in a standard room. Valid for bookings of 2+ nights.', - 'reward_type': RewardType.voucher, - 'points_cost': 30000, - 'applicable_tier_id': gold_tier.id if gold_tier else None, - 'stock_quantity': 15, - 'icon': '🌙', - 'is_active': True, - 'valid_until': datetime.utcnow() + timedelta(days=180) - }, - { - 'name': 'Complimentary Room Service', - 'description': '$75 credit for room service orders. Valid for one stay.', - 'reward_type': RewardType.amenity, - 'points_cost': 6000, - 'discount_amount': 75.0, - 'applicable_tier_id': None, - 'stock_quantity': None, - 'icon': '🍽️', - 'is_active': True - }, - ] - - created_count = 0 - skipped_count = 0 - - for reward_data in rewards_data: - # Check if reward already exists by name - existing = db.query(LoyaltyReward).filter(LoyaltyReward.name == reward_data['name']).first() - - if existing: - print(f' ⚠️ Reward "{reward_data["name"]}" already exists, skipping...') - skipped_count += 1 - continue - - # Create reward object - reward = LoyaltyReward(**reward_data) - db.add(reward) - print(f' ✓ Created reward: {reward_data["name"]} ({reward_data["points_cost"]:,} points)') - created_count += 1 - - db.commit() - print('\n✓ Loyalty rewards seeded successfully!') - print(f' - Created: {created_count} reward(s)') - print(f' - Skipped: {skipped_count} reward(s) (already exist)') - print('=' * 80) - -def main(): - db = get_db() - try: - seed_loyalty_rewards(db) - print('\n✅ Loyalty rewards seeding completed successfully!') - print('=' * 80) - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_luxury_content.py b/Backend/seeds_data/seed_luxury_content.py deleted file mode 100644 index a4c31127..00000000 --- a/Backend/seeds_data/seed_luxury_content.py +++ /dev/null @@ -1,42 +0,0 @@ -import sys -import os -import json -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal, engine -from src.models.page_content import PageContent -from src.models.user import User - -def seed_luxury_content(): - db: Session = SessionLocal() - try: - existing = db.query(PageContent).filter(PageContent.page_type == 'home').first() - luxury_features = [{'icon': 'Sparkles', 'title': 'Premium Amenities', 'description': 'World-class facilities designed for your comfort and relaxation'}, {'icon': 'Crown', 'title': 'Royal Service', 'description': 'Dedicated concierge service available 24/7 for all your needs'}, {'icon': 'Award', 'title': 'Award-Winning', 'description': 'Recognized for excellence in hospitality and guest satisfaction'}, {'icon': 'Shield', 'title': 'Secure & Private', 'description': 'Your privacy and security are our top priorities'}, {'icon': 'Heart', 'title': 'Personalized Care', 'description': 'Tailored experiences crafted just for you'}, {'icon': 'Gem', 'title': 'Luxury Design', 'description': 'Elegantly designed spaces with attention to every detail'}] - luxury_testimonials = [{'name': 'Sarah Johnson', 'title': 'Business Executive', 'quote': 'An absolutely stunning experience. The attention to detail and level of service exceeded all expectations.', 'image': ''}, {'name': 'Michael Chen', 'title': 'Travel Enthusiast', 'quote': 'The epitome of luxury. Every moment was perfect, from check-in to check-out.', 'image': ''}, {'name': 'Emma Williams', 'title': 'Luxury Traveler', 'quote': 'This hotel redefines what luxury means. I will definitely return.', 'image': ''}] - if existing: - existing.luxury_section_title = 'Experience Unparalleled Luxury' - existing.luxury_section_subtitle = 'Where elegance meets comfort in every detail' - existing.luxury_section_image = None - existing.luxury_features = json.dumps(luxury_features) - existing.luxury_gallery = json.dumps([]) - existing.luxury_testimonials = json.dumps(luxury_testimonials) - existing.about_preview_title = 'About Our Luxury Hotel' - existing.about_preview_content = 'Discover a world of refined elegance and exceptional service. Our hotel combines timeless luxury with modern amenities to create an unforgettable experience.' - existing.about_preview_image = None - print('✓ Updated existing home page content with luxury sections') - else: - new_content = PageContent(page_type='home', luxury_section_title='Experience Unparalleled Luxury', luxury_section_subtitle='Where elegance meets comfort in every detail', luxury_section_image=None, luxury_features=json.dumps(luxury_features), luxury_gallery=json.dumps([]), luxury_testimonials=json.dumps(luxury_testimonials), about_preview_title='About Our Luxury Hotel', about_preview_content='Discover a world of refined elegance and exceptional service. Our hotel combines timeless luxury with modern amenities to create an unforgettable experience.', about_preview_image=None, is_active=True) - db.add(new_content) - print('✓ Created new home page content with luxury sections') - db.commit() - print('✓ Luxury content seeded successfully!') - except Exception as e: - db.rollback() - print(f'✗ Error seeding luxury content: {e}') - raise - finally: - db.close() -if __name__ == '__main__': - print('Seeding luxury content...') - seed_luxury_content() - print('Done!') \ No newline at end of file diff --git a/Backend/seeds_data/seed_policy_pages.py b/Backend/seeds_data/seed_policy_pages.py deleted file mode 100755 index 6d262696..00000000 --- a/Backend/seeds_data/seed_policy_pages.py +++ /dev/null @@ -1,517 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os -from pathlib import Path -import json - -sys.path.insert(0, str(Path(__file__).parent)) - -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.models.page_content import PageContent, PageType -from datetime import datetime - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_privacy_policy(db: Session): - print("=" * 80) - print("SEEDING PRIVACY POLICY PAGE CONTENT") - print("=" * 80) - - privacy_content = """ -

Introduction

-

At our hotel, we are committed to protecting your privacy and ensuring the security of your personal information. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website or use our services.

- -

Information We Collect

-

We collect information that you provide directly to us, including:

- - -

How We Use Your Information

-

We use the information we collect to:

- - -

Data Security

-

We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.

- -

Your Rights

-

You have the right to:

- - -

Contact Us

-

If you have any questions about this Privacy Policy, please contact us at privacy@hotel.com.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - privacy_data = { - "title": "Privacy Policy", - "subtitle": "Your privacy is important to us", - "description": "Learn how we collect, use, and protect your personal information.", - "content": privacy_content, - "meta_title": "Privacy Policy - Luxury Hotel | Data Protection & Privacy", - "meta_description": "Read our privacy policy to understand how we collect, use, and protect your personal information when you use our hotel booking services." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.PRIVACY).first() - - if existing: - for key, value in privacy_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing privacy policy page content") - else: - new_content = PageContent( - page_type=PageType.PRIVACY, - **privacy_data - ) - db.add(new_content) - print("✓ Created new privacy policy page content") - - db.commit() - print("\n✅ Privacy policy page content seeded successfully!") - print("=" * 80) - -def seed_terms_conditions(db: Session): - print("=" * 80) - print("SEEDING TERMS & CONDITIONS PAGE CONTENT") - print("=" * 80) - - terms_content = """ -

Agreement to Terms

-

By accessing and using our hotel's website and services, you accept and agree to be bound by the terms and provision of this agreement.

- -

Booking Terms

-

When making a reservation with us, you agree to:

- - -

Payment Terms

-

Payment terms include:

- - -

Cancellation Policy

-

Our cancellation policy is as follows:

- - -

Check-in and Check-out

-

Standard check-in time is 3:00 PM and check-out time is 11:00 AM. Early check-in and late check-out may be available upon request and subject to availability.

- -

Guest Responsibilities

-

Guests are responsible for:

- - -

Limitation of Liability

-

The hotel shall not be liable for any loss, damage, or injury to persons or property during your stay, except where such loss, damage, or injury is caused by our negligence.

- -

Modifications to Terms

-

We reserve the right to modify these terms at any time. Changes will be effective immediately upon posting on our website.

- -

Contact Information

-

For questions about these terms, please contact us at legal@hotel.com.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - terms_data = { - "title": "Terms & Conditions", - "subtitle": "Please read these terms carefully", - "description": "Terms and conditions governing your use of our hotel booking services.", - "content": terms_content, - "meta_title": "Terms & Conditions - Luxury Hotel | Booking Terms & Policies", - "meta_description": "Read our terms and conditions to understand the rules and policies governing your bookings and stay at our luxury hotel." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.TERMS).first() - - if existing: - for key, value in terms_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing terms & conditions page content") - else: - new_content = PageContent( - page_type=PageType.TERMS, - **terms_data - ) - db.add(new_content) - print("✓ Created new terms & conditions page content") - - db.commit() - print("\n✅ Terms & conditions page content seeded successfully!") - print("=" * 80) - -def seed_refunds_policy(db: Session): - print("=" * 80) - print("SEEDING REFUNDS POLICY PAGE CONTENT") - print("=" * 80) - - refunds_content = """ -

Refund Policy Overview

-

At our hotel, we understand that plans can change. This policy outlines our refund procedures and timelines for various scenarios.

- -

Booking Cancellations

-

Refunds for cancelled bookings are processed as follows:

- - -

Early Check-out

-

If you check out earlier than your reserved departure date:

- - -

Service Issues

-

If you experience service issues during your stay:

- - -

Refund Processing Time

-

Refunds are typically processed within:

- - -

Refund Method

-

Refunds will be issued to the original payment method used for the booking. If the original payment method is no longer available, please contact us to arrange an alternative refund method.

- -

Non-Refundable Bookings

-

Some special offers or promotional rates may be non-refundable. This will be clearly stated at the time of booking.

- -

Force Majeure

-

In cases of force majeure (natural disasters, pandemics, government restrictions, etc.), we will work with you to reschedule your booking or provide appropriate refunds based on the circumstances.

- -

Dispute Resolution

-

If you have concerns about a refund decision, please contact our customer service team. We are committed to resolving all disputes fairly and promptly.

- -

Contact Us

-

For refund inquiries, please contact us at refunds@hotel.com or call our customer service line.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - refunds_data = { - "title": "Refunds Policy", - "subtitle": "Our commitment to fair refunds", - "description": "Learn about our refund policies and procedures for bookings and cancellations.", - "content": refunds_content, - "meta_title": "Refunds Policy - Luxury Hotel | Cancellation & Refund Terms", - "meta_description": "Understand our refund policy, including cancellation terms, processing times, and how to request refunds for your hotel bookings." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.REFUNDS).first() - - if existing: - for key, value in refunds_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing refunds policy page content") - else: - new_content = PageContent( - page_type=PageType.REFUNDS, - **refunds_data - ) - db.add(new_content) - print("✓ Created new refunds policy page content") - - db.commit() - print("\n✅ Refunds policy page content seeded successfully!") - print("=" * 80) - -def seed_cancellation_policy(db: Session): - print("=" * 80) - print("SEEDING CANCELLATION POLICY PAGE CONTENT") - print("=" * 80) - - cancellation_content = """ -

Cancellation Policy Overview

-

We understand that plans can change. This policy outlines our cancellation procedures and fees.

- -

Standard Cancellation Terms

-

For standard bookings, the following cancellation terms apply:

- - -

Special Rate Bookings

-

Some special rates or promotional offers may have different cancellation terms. Please review your booking confirmation for specific details.

- -

How to Cancel

-

To cancel your booking:

- - -

Refund Processing

-

Refunds will be processed to the original payment method within 5-10 business days after cancellation confirmation.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - cancellation_data = { - "title": "Cancellation Policy", - "subtitle": "Flexible cancellation options for your peace of mind", - "description": "Learn about our cancellation policy, fees, and refund procedures.", - "content": cancellation_content, - "meta_title": "Cancellation Policy - Luxury Hotel | Booking Cancellation Terms", - "meta_description": "Review our cancellation policy to understand cancellation fees, refund procedures, and terms for modifying or canceling your hotel booking." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.CANCELLATION).first() - - if existing: - for key, value in cancellation_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing cancellation policy page content") - else: - new_content = PageContent( - page_type=PageType.CANCELLATION, - **cancellation_data - ) - db.add(new_content) - print("✓ Created new cancellation policy page content") - - db.commit() - print("\n✅ Cancellation policy page content seeded successfully!") - print("=" * 80) - -def seed_accessibility_policy(db: Session): - print("=" * 80) - print("SEEDING ACCESSIBILITY PAGE CONTENT") - print("=" * 80) - - accessibility_content = """ -

Our Commitment to Accessibility

-

We are committed to ensuring that our hotel and website are accessible to all guests, regardless of ability. We strive to provide an inclusive experience for everyone.

- -

Hotel Accessibility Features

-

Our hotel offers the following accessibility features:

- - -

Website Accessibility

-

We are continuously working to improve the accessibility of our website. Our website includes:

- - -

Requesting Accommodations

-

If you require specific accommodations during your stay, please contact us at least 48 hours before your arrival. We will do our best to accommodate your needs.

- -

Feedback

-

We welcome feedback on how we can improve accessibility. Please contact us with your suggestions or concerns.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - accessibility_data = { - "title": "Accessibility", - "subtitle": "Committed to providing an inclusive experience for all guests", - "description": "Learn about our accessibility features and accommodations.", - "content": accessibility_content, - "meta_title": "Accessibility - Luxury Hotel | Accessible Accommodations", - "meta_description": "Discover our commitment to accessibility, including accessible rooms, facilities, and website features for guests with disabilities." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.ACCESSIBILITY).first() - - if existing: - for key, value in accessibility_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing accessibility page content") - else: - new_content = PageContent( - page_type=PageType.ACCESSIBILITY, - **accessibility_data - ) - db.add(new_content) - print("✓ Created new accessibility page content") - - db.commit() - print("\n✅ Accessibility page content seeded successfully!") - print("=" * 80) - -def seed_faq_page(db: Session): - print("=" * 80) - print("SEEDING FAQ PAGE CONTENT") - print("=" * 80) - - faq_content = """ -

Frequently Asked Questions

-

Find answers to common questions about our hotel, bookings, and services.

- -

Booking & Reservations

-

How do I make a reservation?

-

You can make a reservation online through our website, by phone, or by email. Simply select your dates, choose your room, and complete the booking process.

- -

What is the deposit requirement?

-

A 20% deposit is required to secure your booking. The remaining balance is due upon arrival at the hotel.

- -

Can I modify my booking?

-

Yes, you can modify your booking by logging into your account and visiting "My Bookings", or by contacting us directly. Changes are subject to availability and may incur fees.

- -

Check-in & Check-out

-

What are your check-in and check-out times?

-

Check-in is from 3:00 PM, and check-out is by 11:00 AM. Early check-in and late check-out may be available upon request, subject to availability.

- -

Do you offer early check-in or late check-out?

-

Early check-in and late check-out are available upon request, subject to availability. Additional fees may apply.

- -

Payment & Cancellation

-

What payment methods do you accept?

-

We accept major credit cards, debit cards, and bank transfers. Payment can be made online or at the hotel.

- -

What is your cancellation policy?

-

For cancellations made more than 48 hours before check-in, the deposit is fully refundable. Cancellations made 48 hours or less before check-in are non-refundable. Please see our Cancellation Policy page for full details.

- -

Hotel Services & Amenities

-

What amenities are included?

-

Our hotel offers complimentary Wi-Fi, parking, fitness center access, and more. Please check the room details for specific amenities.

- -

Do you have parking available?

-

Yes, we offer complimentary parking for all guests. Valet parking is also available for an additional fee.

- -

Is Wi-Fi available?

-

Yes, complimentary high-speed Wi-Fi is available throughout the hotel.

- -

Special Requests

-

Can I request a specific room?

-

Yes, you can make special requests when booking. We will do our best to accommodate your preferences, subject to availability.

- -

Do you accommodate dietary restrictions?

-

Yes, please inform us of any dietary restrictions or allergies when making your reservation, and we will do our best to accommodate your needs.

- -

Contact & Support

-

How can I contact the hotel?

-

You can contact us by phone, email, or through our website's contact form. Our team is available 24/7 to assist you.

- -

Last updated: """ + datetime.now().strftime("%B %d, %Y") + """

- """ - - faq_data = { - "title": "Frequently Asked Questions", - "subtitle": "Find answers to common questions", - "description": "Get answers to frequently asked questions about bookings, services, and policies.", - "content": faq_content, - "meta_title": "FAQ - Luxury Hotel | Frequently Asked Questions", - "meta_description": "Find answers to common questions about hotel bookings, check-in, payment, cancellation, amenities, and more." - } - - existing = db.query(PageContent).filter(PageContent.page_type == PageType.FAQ).first() - - if existing: - for key, value in faq_data.items(): - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - print("✓ Updated existing FAQ page content") - else: - new_content = PageContent( - page_type=PageType.FAQ, - **faq_data - ) - db.add(new_content) - print("✓ Created new FAQ page content") - - db.commit() - print("\n✅ FAQ page content seeded successfully!") - print("=" * 80) - -def main(): - db = get_db() - try: - print("\n") - seed_privacy_policy(db) - print("\n") - seed_terms_conditions(db) - print("\n") - seed_refunds_policy(db) - print("\n") - seed_cancellation_policy(db) - print("\n") - seed_accessibility_policy(db) - print("\n") - seed_faq_page(db) - print("\n") - print("=" * 80) - print("✅ ALL POLICY PAGES SEEDED SUCCESSFULLY!") - print("=" * 80) - print("\n") - except Exception as e: - print(f"\n❌ Error: {e}") - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == "__main__": - main() - diff --git a/Backend/seeds_data/seed_promotions.py b/Backend/seeds_data/seed_promotions.py deleted file mode 100644 index 12d65624..00000000 --- a/Backend/seeds_data/seed_promotions.py +++ /dev/null @@ -1,157 +0,0 @@ -import sys -import os -from datetime import datetime, timedelta -# Add parent directory to path to import from src -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.loyalty.models.promotion import Promotion, DiscountType - -def seed_promotions(db: Session): - """Seed promotions that match the homepage promotion codes""" - - # Calculate dates relative to current UTC date - # Use UTC consistently to match validation logic - now = datetime.utcnow() - - # Start date: Start of today (00:00:00) to ensure promotion is immediately active - start_date = datetime(now.year, now.month, now.day, 0, 0, 0) - - # End dates: future dates at end of day (23:59:59) - next_month_date = now + timedelta(days=30) - next_month = datetime(next_month_date.year, next_month_date.month, next_month_date.day, 23, 59, 59) - - next_3_months_date = now + timedelta(days=90) - next_3_months = datetime(next_3_months_date.year, next_3_months_date.month, next_3_months_date.day, 23, 59, 59) - - next_6_months_date = now + timedelta(days=180) - next_6_months = datetime(next_6_months_date.year, next_6_months_date.month, next_6_months_date.day, 23, 59, 59) - - promotions_data = [ - { - 'code': 'EARLYBIRD20', - 'name': 'Early Bird Special', - 'description': 'Book 30 days in advance and save 20% on your stay. Perfect for planning ahead!', - 'discount_type': DiscountType.percentage, - 'discount_value': 20.00, - 'start_date': start_date, - 'end_date': next_3_months, - 'is_active': True, - 'min_booking_amount': None, - 'max_discount_amount': None, - 'usage_limit': None, - }, - { - 'code': 'WEEKEND30', - 'name': 'Weekend Getaway', - 'description': 'Perfect weekend escape with complimentary breakfast and spa access. Relax and unwind!', - 'discount_type': DiscountType.percentage, - 'discount_value': 30.00, - 'start_date': start_date, - 'end_date': next_month, - 'is_active': True, - 'min_booking_amount': None, - 'max_discount_amount': None, - 'usage_limit': None, - }, - { - 'code': 'LUXURY200', - 'name': 'Luxury Suite Package', - 'description': 'Experience our premium suites with exclusive amenities, fine dining, and concierge service', - 'discount_type': DiscountType.fixed_amount, - 'discount_value': 200.00, - 'start_date': start_date, - 'end_date': next_6_months, - 'is_active': True, - 'min_booking_amount': None, # No minimum for now - 'max_discount_amount': None, - 'usage_limit': None, - }, - { - 'code': 'HONEYMOON25', - 'name': 'Honeymoon Special', - 'description': 'Romantic getaway with champagne, flowers, special amenities, and complimentary room upgrade', - 'discount_type': DiscountType.percentage, - 'discount_value': 25.00, - 'start_date': start_date, - 'end_date': next_3_months, - 'is_active': True, - 'min_booking_amount': None, - 'max_discount_amount': None, - 'usage_limit': None, - }, - { - 'code': 'FAMILY15', - 'name': 'Family Fun Package', - 'description': 'Perfect for families! Includes family room, kids activities, and complimentary meals for children under 12', - 'discount_type': DiscountType.percentage, - 'discount_value': 15.00, - 'start_date': start_date, - 'end_date': next_6_months, - 'is_active': True, - 'min_booking_amount': None, - 'max_discount_amount': None, - 'usage_limit': None, - }, - { - 'code': 'BUSINESS10', - 'name': 'Business Traveler', - 'description': 'Extended stay discounts for business travelers. Includes high-speed WiFi, workspace, and airport transfer', - 'discount_type': DiscountType.percentage, - 'discount_value': 10.00, - 'start_date': start_date, - 'end_date': next_3_months, - 'is_active': True, - 'min_booking_amount': None, - 'max_discount_amount': None, - 'usage_limit': None, - }, - ] - - created_count = 0 - updated_count = 0 - - for promo_data in promotions_data: - existing = db.query(Promotion).filter(Promotion.code == promo_data['code']).first() - - if existing: - # Update existing promotion - for key, value in promo_data.items(): - if key != 'code': # Don't update the code - setattr(existing, key, value) - existing.updated_at = datetime.utcnow() - updated_count += 1 - print(f'✓ Updated promotion: {promo_data["code"]}') - else: - # Create new promotion - promotion = Promotion(**promo_data) - db.add(promotion) - created_count += 1 - print(f'✓ Created promotion: {promo_data["code"]}') - - db.commit() - print(f'\n✓ Promotions seeded: {created_count} created, {updated_count} updated') - -def main(): - db: Session = SessionLocal() - try: - print('=' * 80) - print('SEEDING PROMOTIONS') - print('=' * 80) - print() - seed_promotions(db) - print('\n' + '=' * 80) - print('✓ All promotions seeded successfully!') - print('=' * 80) - except Exception as e: - db.rollback() - print(f'\n✗ Error seeding promotions: {e}') - import traceback - traceback.print_exc() - raise - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_rooms.py b/Backend/seeds_data/seed_rooms.py deleted file mode 100644 index 9f2c4ca4..00000000 --- a/Backend/seeds_data/seed_rooms.py +++ /dev/null @@ -1,141 +0,0 @@ -import sys -import os -from pathlib import Path -# Add both the seeds_data directory and the Backend directory to the path -sys.path.insert(0, str(Path(__file__).parent)) -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal, engine -from src.rooms.models.room import Room, RoomStatus -from src.rooms.models.room_type import RoomType -from datetime import datetime -import json -import random - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_rooms(db: Session): - print('=' * 80) - print('SEEDING ROOMS - DELETING EXISTING AND CREATING 50 NEW LUXURY ROOMS') - print('=' * 80) - room_types = db.query(RoomType).all() - if not room_types: - print('❌ No room types found! Please create room types first.') - return - print(f'\n✓ Found {len(room_types)} room type(s)') - for rt in room_types: - print(f' - {rt.name} (ID: {rt.id}, Base Price: {rt.base_price})') - from src.bookings.models.booking import Booking - from src.reviews.models.review import Review - from src.reviews.models.favorite import Favorite - existing_rooms = db.query(Room).all() - if existing_rooms: - print(f'\n🗑️ Deleting {len(existing_rooms)} existing room(s)...') - room_ids = [room.id for room in existing_rooms] - bookings_with_rooms = db.query(Booking).filter(Booking.room_id.in_(room_ids)).all() - if bookings_with_rooms: - print(f' ⚠️ Found {len(bookings_with_rooms)} booking(s) referencing these rooms') - for booking in bookings_with_rooms: - db.delete(booking) - print(f' ✓ Deleted {len(bookings_with_rooms)} booking(s)') - reviews_with_rooms = db.query(Review).filter(Review.room_id.in_(room_ids)).all() - if reviews_with_rooms: - print(f' ⚠️ Found {len(reviews_with_rooms)} review(s) referencing these rooms') - for review in reviews_with_rooms: - db.delete(review) - print(f' ✓ Deleted {len(reviews_with_rooms)} review(s)') - favorites_with_rooms = db.query(Favorite).filter(Favorite.room_id.in_(room_ids)).all() - if favorites_with_rooms: - print(f' ⚠️ Found {len(favorites_with_rooms)} favorite(s) referencing these rooms') - for favorite in favorites_with_rooms: - db.delete(favorite) - print(f' ✓ Deleted {len(favorites_with_rooms)} favorite(s)') - for room in existing_rooms: - db.delete(room) - db.commit() - print(f'✓ Deleted {len(existing_rooms)} room(s)') - views = ['Ocean View', 'City View', 'Garden View', 'Mountain View', 'Pool View', 'Beach View', 'Panoramic View', 'Sea View'] - room_sizes = ['35 sqm', '40 sqm', '45 sqm', '50 sqm', '55 sqm', '60 sqm', '70 sqm', '80 sqm', '90 sqm', '100 sqm', '120 sqm', '150 sqm', '180 sqm', '200 sqm', '250 sqm'] - luxury_room_images = ['https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1578683010236-d716f9a3f461?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea8?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1566665797739-1674de7a421a?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1582719508461-905c673771fd?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1595576508898-0ad5c879a061?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1571003123894-1f0594d2b5d9?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1566073771259-6a8506099945?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1578683010236-d716f9a3f461?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1596394516093-501ba68a0ba6?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1631049307264-da0ec9d70304?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1611892440504-42a792e24d32?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1590490360182-c33d57733427?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1564501049412-61c2a3083791?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1582719478250-c89cae4dc85b?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1618221195710-dd6b41faaea8?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1566665797739-1674de7a421a?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1582719508461-905c673771fd?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1595576508898-0ad5c879a061?w=1200&h=800&fit=crop', 'https://images.unsplash.com/photo-1571003123894-1f0594d2b5d9?w=1200&h=800&fit=crop'] - all_amenities = ['Free WiFi', 'High-Speed Internet', 'Smart TV', 'Netflix', 'Air Conditioning', 'Climate Control', 'Private Balcony', 'Ocean View', 'City View', 'Minibar', 'Coffee Maker', 'Espresso Machine', 'Refrigerator', 'Safe', 'Iron & Ironing Board', 'Hair Dryer', 'Premium Toiletries', 'Bathrobes', 'Slippers', 'Work Desk', 'Ergonomic Chair', 'USB Charging Ports', 'Bluetooth Speaker', 'Sound System', 'Blackout Curtains', 'Pillow Menu', 'Turndown Service', 'Room Service', '24/7 Concierge'] - premium_amenities = ['Jacuzzi Bathtub', 'Steam Shower', 'Rain Shower', 'Bidet', 'Private Pool', 'Outdoor Terrace', 'Fireplace', 'Wine Cellar', 'Private Bar', 'Butler Service', 'Private Elevator', 'Helipad Access'] - room_configs = [((1, 3), 8, 'Garden View', (35, 45), 8, False), ((1, 3), 8, 'City View', (40, 50), 9, False), ((4, 6), 6, 'City View', (45, 55), 10, False), ((4, 6), 6, 'Pool View', (50, 60), 11, True), ((7, 9), 5, 'Ocean View', (55, 70), 12, True), ((7, 9), 5, 'Mountain View', (60, 75), 12, False), ((10, 12), 4, 'Panoramic View', (80, 100), 14, True), ((10, 12), 4, 'Sea View', (90, 110), 15, True), ((13, 15), 3, 'Ocean View', (120, 150), 16, True), ((13, 15), 3, 'Beach View', (130, 160), 17, True), ((16, 16), 2, 'Panoramic View', (200, 250), 20, True)] - rooms_created = [] - room_counter = 101 - print(f'\n🏨 Creating 50 luxury rooms...\n') - for config in room_configs: - floor_range, rooms_per_floor, view_type, size_range, amenities_count, featured = config - for floor in range(floor_range[0], floor_range[1] + 1): - for _ in range(rooms_per_floor): - if len(rooms_created) >= 50: - break - if floor <= 3: - room_type = random.choice([rt for rt in room_types if 'standard' in rt.name.lower() or 'superior' in rt.name.lower()] or room_types) - elif floor <= 6: - room_type = random.choice([rt for rt in room_types if 'superior' in rt.name.lower() or 'deluxe' in rt.name.lower()] or room_types) - elif floor <= 9: - room_type = random.choice([rt for rt in room_types if 'deluxe' in rt.name.lower() or 'executive' in rt.name.lower()] or room_types) - elif floor <= 12: - room_type = random.choice([rt for rt in room_types if 'executive' in rt.name.lower() or 'suite' in rt.name.lower()] or room_types) - else: - room_type = random.choice([rt for rt in room_types if 'suite' in rt.name.lower() or 'presidential' in rt.name.lower()] or room_types) - if not room_type: - room_type = random.choice(room_types) - base_price = float(room_type.base_price) - floor_premium = (floor - 1) * 5 - view_premium = 20 if 'Ocean' in view_type or 'Sea' in view_type or 'Beach' in view_type else 0 - view_premium += 15 if 'Panoramic' in view_type else 0 - view_premium += 10 if 'Mountain' in view_type else 0 - view_premium += 5 if 'Pool' in view_type else 0 - random_variation = base_price * random.uniform(-0.05, 0.1) - size_min, size_max = size_range - size_premium = (size_min + size_max) / 2 * 0.5 - price = base_price + floor_premium + view_premium + random_variation + size_premium - price = max(base_price * 0.95, price) - price = round(price, 2) - selected_amenities = random.sample(all_amenities, min(amenities_count, len(all_amenities))) - if floor >= 13: - premium_count = min(2, len(premium_amenities)) - selected_amenities.extend(random.sample(premium_amenities, premium_count)) - size_min, size_max = size_range - room_size = f'{random.randint(size_min, size_max)} sqm' - capacity = room_type.capacity - if random.random() > 0.7: - capacity = max(1, capacity + random.randint(-1, 1)) - room_number = f'{floor}{room_counter % 100:02d}' - room_counter += 1 - shuffled_images = luxury_room_images.copy() - random.shuffle(shuffled_images) - image_urls = shuffled_images[:3] - descriptions = [f'Elegantly designed {view_type.lower()} room with modern luxury amenities and breathtaking views.', f'Spacious {view_type.lower()} accommodation featuring premium furnishings and world-class comfort.', f'Luxurious {view_type.lower()} room with sophisticated decor and exceptional attention to detail.', f'Exquisite {view_type.lower()} suite offering unparalleled elegance and personalized service.', f'Opulent {view_type.lower()} accommodation with bespoke interiors and premium amenities.'] - description = random.choice(descriptions) - status_weights = [0.85, 0.05, 0.05, 0.05] - status = random.choices([RoomStatus.available, RoomStatus.occupied, RoomStatus.maintenance, RoomStatus.cleaning], weights=status_weights)[0] - room = Room(room_type_id=room_type.id, room_number=room_number, floor=floor, status=status, price=price, featured=featured, capacity=capacity, room_size=room_size, view=view_type, images=json.dumps(image_urls), amenities=json.dumps(selected_amenities), description=description) - db.add(room) - rooms_created.append({'number': room_number, 'floor': floor, 'type': room_type.name, 'view': view_type, 'price': price}) - print(f' ✓ Created Room {room_number} - Floor {floor}, {room_type.name}, {view_type}, {room_size}, €{price:.2f}') - db.commit() - print(f'\n✅ Successfully created {len(rooms_created)} luxury rooms!') - print(f'\n📊 Summary:') - featured_count = sum((1 for r in rooms_created if any((config[5] and r['floor'] >= config[0][0] and (r['floor'] <= config[0][1]) for config in room_configs)))) - print(f' - Featured rooms: {featured_count}') - print(f' - Floors: {min((r['floor'] for r in rooms_created))} - {max((r['floor'] for r in rooms_created))}') - print(f' - Price range: €{min((r['price'] for r in rooms_created)):.2f} - €{max((r['price'] for r in rooms_created)):.2f}') - print('=' * 80) -if __name__ == '__main__': - db = get_db() - try: - seed_rooms(db) - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() \ No newline at end of file diff --git a/Backend/seeds_data/seed_services.py b/Backend/seeds_data/seed_services.py deleted file mode 100644 index a382e99f..00000000 --- a/Backend/seeds_data/seed_services.py +++ /dev/null @@ -1,218 +0,0 @@ -import sys -import os -import json -import re -from pathlib import Path -# Add parent directory to path to import from src -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -# Import all models to ensure relationships are set up -from src.models import * # This ensures all models are loaded and relationships are configured -from src.hotel_services.models.service import Service - -def generate_slug(name: str) -> str: - """Generate a URL-friendly slug from a name""" - slug = name.lower() - slug = re.sub(r'[^a-z0-9\s-]', '', slug) - slug = re.sub(r'\s+', '-', slug) - slug = re.sub(r'-+', '-', slug) - return slug.strip('-') - -def seed_services(db: Session): - """Seed initial services data""" - print('Seeding services...') - - services_data = [ - { - 'name': 'Room Service', - 'description': '24/7 in-room dining service with a wide selection of gourmet meals, snacks, and beverages delivered directly to your room.', - 'price': 25.00, - 'category': 'Dining', - 'slug': 'room-service', - 'image': 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=600', - 'content': '

Enjoy the convenience of 24/7 room service with our extensive menu featuring international cuisine, local specialties, and premium beverages. Our professional staff ensures your meals are delivered hot, fresh, and beautifully presented.

From breakfast in bed to late-night snacks, we cater to all your dining needs with the highest standards of quality and service.

', - 'sections': json.dumps([]), - 'meta_title': 'Room Service - Luxury Hotel', - 'meta_description': '24/7 in-room dining service with gourmet meals delivered to your room.', - 'meta_keywords': 'room service, in-room dining, hotel service, 24/7 service', - 'is_active': True - }, - { - 'name': 'Spa & Massage', - 'description': 'Relaxing spa treatments and professional massage therapy to rejuvenate your mind and body.', - 'price': 120.00, - 'category': 'Wellness', - 'slug': 'spa-massage', - 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=600', - 'content': '

Indulge in our world-class spa treatments designed to restore balance and vitality. Our expert therapists offer a range of services including Swedish massage, deep tissue massage, hot stone therapy, and aromatherapy.

Each treatment is customized to your preferences, using premium products and ancient techniques to provide an unparalleled wellness experience.

', - 'sections': json.dumps([]), - 'meta_title': 'Spa & Massage Services - Luxury Hotel', - 'meta_description': 'Professional spa treatments and massage therapy for ultimate relaxation and rejuvenation.', - 'meta_keywords': 'spa, massage, wellness, relaxation, therapy', - 'is_active': True - }, - { - 'name': 'Laundry Service', - 'description': 'Professional dry cleaning and laundry service with same-day or next-day delivery options.', - 'price': 15.00, - 'category': 'Housekeeping', - 'slug': 'laundry-service', - 'image': 'https://images.unsplash.com/photo-1582735689369-4fe89db7114c?w=600', - 'content': '

Keep your wardrobe fresh and clean with our professional laundry and dry cleaning service. We handle everything from delicate garments to business suits with the utmost care and attention.

Choose from same-day express service or standard next-day delivery. All items are professionally cleaned, pressed, and returned to your room.

', - 'sections': json.dumps([]), - 'meta_title': 'Laundry & Dry Cleaning Service - Luxury Hotel', - 'meta_description': 'Professional laundry and dry cleaning service with same-day or next-day delivery.', - 'meta_keywords': 'laundry, dry cleaning, hotel service, cleaning', - 'is_active': True - }, - { - 'name': 'Airport Transfer', - 'description': 'Complimentary airport pickup and drop-off service in luxury vehicles with professional drivers.', - 'price': 50.00, - 'category': 'Transportation', - 'slug': 'airport-transfer', - 'image': 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=600', - 'content': '

Travel in comfort and style with our premium airport transfer service. Our fleet of luxury vehicles, including sedans and SUVs, ensures a smooth and comfortable journey to or from the airport.

Our professional drivers are punctual, courteous, and knowledgeable about the local area. Flight monitoring ensures we adjust to any delays or early arrivals.

', - 'sections': json.dumps([]), - 'meta_title': 'Airport Transfer Service - Luxury Hotel', - 'meta_description': 'Premium airport pickup and drop-off service in luxury vehicles.', - 'meta_keywords': 'airport transfer, transportation, airport shuttle, luxury car', - 'is_active': True - }, - { - 'name': 'Concierge Service', - 'description': 'Personalized assistance for restaurant reservations, event tickets, tours, and special requests.', - 'price': 0.00, - 'category': 'Concierge', - 'slug': 'concierge-service', - 'image': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600', - 'content': '

Our dedicated concierge team is available 24/7 to ensure your stay is nothing short of extraordinary. From restaurant reservations and event tickets to private tours and transportation arrangements, we handle every detail with precision and care.

Whether you need assistance with business arrangements, special celebrations, or unique local experiences, our concierge team has the expertise and connections to make it happen.

', - 'sections': json.dumps([]), - 'meta_title': 'Concierge Services - Luxury Hotel', - 'meta_description': 'Personalized assistance for all your travel and entertainment needs, available 24/7.', - 'meta_keywords': 'concierge, personal assistant, travel services, luxury service', - 'is_active': True - }, - { - 'name': 'Fitness Center Access', - 'description': 'Access to our state-of-the-art fitness center with modern equipment and personal training options.', - 'price': 20.00, - 'category': 'Wellness', - 'slug': 'fitness-center', - 'image': 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=600', - 'content': '

Maintain your fitness routine at our fully equipped fitness center featuring the latest cardio and strength training equipment. Our facilities are open 24/7 for your convenience.

Enhance your workout with personal training sessions, group fitness classes, or yoga sessions led by certified instructors. Towels, water, and fresh fruit are provided.

', - 'sections': json.dumps([]), - 'meta_title': 'Fitness Center - Luxury Hotel', - 'meta_description': 'State-of-the-art fitness center with modern equipment and personal training options.', - 'meta_keywords': 'fitness center, gym, workout, exercise, personal training', - 'is_active': True - }, - { - 'name': 'Business Center', - 'description': 'Fully equipped business center with meeting rooms, printing, copying, and secretarial services.', - 'price': 30.00, - 'category': 'Business', - 'slug': 'business-center', - 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=600', - 'content': '

Stay productive with our fully equipped business center featuring private meeting rooms, high-speed internet, printing and copying services, and professional secretarial support.

Our meeting rooms can accommodate small groups and are equipped with presentation equipment, video conferencing capabilities, and refreshments.

', - 'sections': json.dumps([]), - 'meta_title': 'Business Center Services - Luxury Hotel', - 'meta_description': 'Fully equipped business center with meeting rooms and professional services.', - 'meta_keywords': 'business center, meeting room, conference, office services', - 'is_active': True - }, - { - 'name': 'Valet Parking', - 'description': 'Secure valet parking service with 24/7 vehicle care and quick retrieval.', - 'price': 35.00, - 'category': 'Transportation', - 'slug': 'valet-parking', - 'image': 'https://images.unsplash.com/photo-1502877338535-766e1452684a?w=600', - 'content': '

Enjoy the convenience of valet parking with our professional service. Your vehicle will be safely parked and secured, ready for quick retrieval whenever you need it.

Our valet team is available 24/7 and can also assist with luggage handling and vehicle care services.

', - 'sections': json.dumps([]), - 'meta_title': 'Valet Parking Service - Luxury Hotel', - 'meta_description': 'Secure valet parking service with 24/7 vehicle care and quick retrieval.', - 'meta_keywords': 'valet parking, parking service, car service, vehicle care', - 'is_active': True - }, - { - 'name': 'Babysitting Service', - 'description': 'Professional and certified babysitting service for your peace of mind.', - 'price': 25.00, - 'category': 'Family', - 'slug': 'babysitting-service', - 'image': 'https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=600', - 'content': '

Enjoy your time knowing your children are in safe hands with our professional babysitting service. Our certified caregivers are experienced, background-checked, and trained in child safety and first aid.

Available for both in-room care and supervised activities, we ensure your children are entertained and well-cared for while you enjoy your stay.

', - 'sections': json.dumps([]), - 'meta_title': 'Babysitting Service - Luxury Hotel', - 'meta_description': 'Professional and certified babysitting service for your peace of mind.', - 'meta_keywords': 'babysitting, childcare, kids service, family service', - 'is_active': True - }, - { - 'name': 'Pet Care Service', - 'description': 'Pet-friendly accommodations with pet care services including walking, feeding, and grooming.', - 'price': 20.00, - 'category': 'Pet Services', - 'slug': 'pet-care-service', - 'image': 'https://images.unsplash.com/photo-1601758228041-f3b2795255f1?w=600', - 'content': '

Your furry friends are welcome at our hotel! We offer pet-friendly accommodations and a range of pet care services including daily walks, feeding, and grooming.

Our pet care team ensures your pets are comfortable and well-cared for during your stay. Pet amenities including beds, bowls, and treats are provided.

', - 'sections': json.dumps([]), - 'meta_title': 'Pet Care Services - Luxury Hotel', - 'meta_description': 'Pet-friendly accommodations with professional pet care services.', - 'meta_keywords': 'pet care, pet service, dog walking, pet friendly', - 'is_active': True - } - ] - - created_count = 0 - updated_count = 0 - - for service_data in services_data: - # Check if service already exists by slug - existing = db.query(Service).filter(Service.slug == service_data['slug']).first() - - if existing: - # Update existing service - for key, value in service_data.items(): - if key != 'slug': # Don't update slug - setattr(existing, key, value) - updated_count += 1 - print(f' ✓ Updated service: {service_data["name"]}') - else: - # Create new service - service = Service(**service_data) - db.add(service) - created_count += 1 - print(f' ✓ Created service: {service_data["name"]}') - - db.commit() - print(f'\n✓ Services seeded successfully!') - print(f' - Created: {created_count}') - print(f' - Updated: {updated_count}') - print(f' - Total: {len(services_data)}\n') - -def main(): - db: Session = SessionLocal() - try: - print('=' * 80) - print('SEEDING SERVICES') - print('=' * 80) - print() - seed_services(db) - print('=' * 80) - print('✓ All services data seeded successfully!') - print('=' * 80) - except Exception as e: - db.rollback() - print(f'\n✗ Error seeding services: {e}') - import traceback - traceback.print_exc() - raise - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_services_enterprise.py b/Backend/seeds_data/seed_services_enterprise.py deleted file mode 100644 index b2190617..00000000 --- a/Backend/seeds_data/seed_services_enterprise.py +++ /dev/null @@ -1,756 +0,0 @@ -import sys -import os -import json -import re -from pathlib import Path -# Add parent directory to path to import from src -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -# Import all models to ensure relationships are set up -from src.models import * # This ensures all models are loaded and relationships are configured -from src.hotel_services.models.service import Service - -def generate_slug(name: str) -> str: - """Generate a URL-friendly slug from a name""" - slug = name.lower() - slug = re.sub(r'[^a-z0-9\s-]', '', slug) - slug = re.sub(r'\s+', '-', slug) - slug = re.sub(r'-+', '-', slug) - return slug.strip('-') - -def seed_enterprise_services(db: Session): - """Seed enterprise-level services with rich detail page content""" - print('Seeding enterprise services...') - - services_data = [ - { - 'name': 'Luxury Spa & Wellness Retreat', - 'description': 'Indulge in our world-class spa treatments designed to restore balance and vitality. Experience ultimate relaxation with our expert therapists.', - 'price': 250.00, - 'category': 'Wellness', - 'slug': 'luxury-spa-wellness-retreat', - 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=800&fit=crop', - 'content': '''
-

Experience Ultimate Relaxation

-

Our Luxury Spa & Wellness Retreat offers an unparalleled journey of rejuvenation and tranquility. Nestled in a serene environment, our spa combines ancient healing traditions with modern wellness techniques to create a transformative experience.

- -

What's Included

- - -

Our Signature Treatments

-

Choose from our curated selection of treatments including Swedish massage, deep tissue therapy, hot stone massage, and aromatherapy sessions. Each treatment is customized to your specific needs and preferences.

- -

Expert Therapists

-

Our team of certified therapists brings years of experience and a deep understanding of holistic wellness. They are trained in various massage techniques and wellness practices to ensure you receive the highest quality care.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Transform Your Wellbeing', - 'content': 'Discover a sanctuary of peace and rejuvenation where every detail is designed to restore your mind, body, and spirit.', - 'image': 'https://images.unsplash.com/photo-1544161515-4ab6ce6db874?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Why Choose Our Spa', - 'features': [ - {'title': 'Expert Therapists', 'description': 'Certified professionals with years of experience', 'icon': 'User'}, - {'title': 'Premium Products', 'description': 'Luxury skincare and aromatherapy products', 'icon': 'Sparkles'}, - {'title': 'Customized Treatments', 'description': 'Personalized sessions tailored to your needs', 'icon': 'Heart'}, - {'title': 'Serene Environment', 'description': 'Peaceful setting designed for relaxation', 'icon': 'Leaf'} - ] - }, - { - 'type': 'quote', - 'quote': 'The most luxurious spa experience I\'ve ever had. Truly transformative.', - 'author': 'Sarah M., Guest' - } - ]), - 'meta_title': 'Luxury Spa & Wellness Retreat - Premium Hotel Services', - 'meta_description': 'Experience ultimate relaxation with our world-class spa treatments. Expert therapists, premium products, and a serene environment await you.', - 'meta_keywords': 'luxury spa, wellness retreat, massage therapy, hotel spa, relaxation, aromatherapy, wellness services', - 'is_active': True - }, - { - 'name': 'Fine Dining Experience', - 'description': 'Savor culinary excellence at our Michelin-starred restaurants. World-renowned chefs craft exquisite dishes using the finest ingredients.', - 'price': 150.00, - 'category': 'Dining', - 'slug': 'fine-dining-experience', - 'image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1200&h=800&fit=crop', - 'content': '''
-

Culinary Excellence Awaits

-

Embark on a gastronomic journey at our award-winning restaurants, where culinary artistry meets exceptional service. Our Michelin-starred chefs create masterpieces that celebrate both local traditions and international flavors.

- -

Our Restaurants

- - -

What's Included

- - -

Reservation Information

-

Reservations are recommended and can be made through our concierge or directly at the restaurant. Special dietary requirements and preferences are accommodated with advance notice.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'A Culinary Journey Like No Other', - 'content': 'Where every dish tells a story and every meal becomes a memory.', - 'image': 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1200&h=600&fit=crop' - }, - { - 'type': 'gallery', - 'title': 'Our Culinary Creations', - 'images': [ - 'https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=800', - 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=800', - 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800' - ] - }, - { - 'type': 'quote', - 'quote': 'An extraordinary dining experience that exceeded all expectations. The attention to detail is remarkable.', - 'author': 'Michael R., Food Critic' - } - ]), - 'meta_title': 'Fine Dining Experience - Michelin-Starred Restaurant', - 'meta_description': 'Savor culinary excellence at our Michelin-starred restaurants. World-renowned chefs, exquisite dishes, and exceptional service.', - 'meta_keywords': 'fine dining, michelin star, luxury restaurant, gourmet cuisine, fine dining hotel', - 'is_active': True - }, - { - 'name': '24/7 Premium Room Service', - 'description': 'Enjoy the convenience of round-the-clock in-room dining with our extensive menu featuring international cuisine and premium beverages.', - 'price': 35.00, - 'category': 'Dining', - 'slug': 'premium-room-service', - 'image': 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=1200&h=800&fit=crop', - 'content': '''
-

Dining at Your Convenience

-

Experience the ultimate in-room dining service available 24 hours a day. From breakfast in bed to late-night snacks, our extensive menu caters to every craving with the highest standards of quality and presentation.

- -

Menu Highlights

- - -

Service Features

- - -

How to Order

-

Simply call our room service line from your in-room phone or use our mobile app. Our team is available 24/7 to take your order and ensure prompt delivery.

-
''', - 'sections': json.dumps([ - { - 'type': 'features', - 'title': 'Why Choose Our Room Service', - 'features': [ - {'title': '24/7 Availability', 'description': 'Order anytime, day or night', 'icon': 'Clock'}, - {'title': 'Fast Delivery', 'description': 'Average 25-30 minute delivery time', 'icon': 'Zap'}, - {'title': 'Premium Quality', 'description': 'Restaurant-quality meals in your room', 'icon': 'Award'}, - {'title': 'Professional Service', 'description': 'Elegant table setup and presentation', 'icon': 'UserCheck'} - ] - } - ]), - 'meta_title': '24/7 Premium Room Service - Luxury Hotel', - 'meta_description': 'Enjoy round-the-clock in-room dining with our extensive menu. International cuisine, premium beverages, and professional service delivered to your room.', - 'meta_keywords': 'room service, in-room dining, 24/7 service, hotel dining, room service menu', - 'is_active': True - }, - { - 'name': 'Executive Business Center', - 'description': 'Fully equipped business center with private meeting rooms, high-speed internet, printing services, and professional secretarial support.', - 'price': 75.00, - 'category': 'Business', - 'slug': 'executive-business-center', - 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=800&fit=crop', - 'content': '''
-

Your Office Away From Office

-

Stay productive with our state-of-the-art Executive Business Center, designed to meet all your professional needs. Whether you need a quiet workspace, a meeting room, or professional support services, we have everything you need.

- -

Facilities & Services

- - -

Meeting Room Features

- - -

Reservation

-

Meeting rooms can be reserved in advance through our concierge or business center. Hourly and daily rates available. Special packages for extended stays.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Productivity Meets Luxury', - 'content': 'A professional workspace designed for success, equipped with everything you need to stay productive.', - 'image': 'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Everything You Need', - 'features': [ - {'title': 'Private Meeting Rooms', 'description': 'Various sizes for any business need', 'icon': 'Users'}, - {'title': 'High-Speed Internet', 'description': 'Fiber-optic connection with secure Wi-Fi', 'icon': 'Wifi'}, - {'title': 'Professional Support', 'description': 'Secretarial and administrative services', 'icon': 'Briefcase'}, - {'title': 'Modern Equipment', 'description': 'Latest technology and presentation tools', 'icon': 'Monitor'} - ] - } - ]), - 'meta_title': 'Executive Business Center - Professional Workspace', - 'meta_description': 'Fully equipped business center with meeting rooms, high-speed internet, printing services, and professional support. Your office away from office.', - 'meta_keywords': 'business center, meeting room, conference room, office services, hotel business center', - 'is_active': True - }, - { - 'name': 'Luxury Airport Transfer', - 'description': 'Travel in comfort and style with our premium airport transfer service. Luxury vehicles, professional drivers, and flight monitoring for a seamless journey.', - 'price': 85.00, - 'category': 'Transportation', - 'slug': 'luxury-airport-transfer', - 'image': 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=1200&h=800&fit=crop', - 'content': '''
-

Arrive in Style

-

Begin or end your journey with our premium airport transfer service. Our fleet of luxury vehicles and professional drivers ensure a comfortable, punctual, and stress-free experience.

- -

Our Fleet

- - -

Service Features

- - -

Booking Information

-

Reservations should be made at least 24 hours in advance. Please provide flight details for arrival transfers. Our team monitors flights and adjusts pickup times accordingly.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Your Journey Starts Here', - 'content': 'Experience the comfort and convenience of premium airport transfers.', - 'image': 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Why Choose Our Transfer Service', - 'features': [ - {'title': 'Luxury Fleet', 'description': 'Premium vehicles for every need', 'icon': 'Car'}, - {'title': 'Professional Drivers', 'description': 'Licensed, experienced, and courteous', 'icon': 'User'}, - {'title': 'Flight Monitoring', 'description': 'Automatic updates for delays', 'icon': 'Plane'}, - {'title': 'Complimentary Amenities', 'description': 'Water, Wi-Fi, and newspapers', 'icon': 'Gift'} - ] - } - ]), - 'meta_title': 'Luxury Airport Transfer - Premium Transportation', - 'meta_description': 'Travel in comfort with our premium airport transfer service. Luxury vehicles, professional drivers, and flight monitoring for a seamless journey.', - 'meta_keywords': 'airport transfer, luxury transportation, airport shuttle, hotel transfer, airport service', - 'is_active': True - }, - { - 'name': 'Personal Concierge Service', - 'description': 'Dedicated 24/7 concierge assistance for restaurant reservations, event tickets, tours, transportation, and all your special requests.', - 'price': 0.00, - 'category': 'Concierge', - 'slug': 'personal-concierge-service', - 'image': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800&fit=crop', - 'content': '''
-

Your Personal Assistant

-

Our dedicated concierge team is available 24/7 to ensure your stay is nothing short of extraordinary. From restaurant reservations and event tickets to private tours and unique experiences, we handle every detail with precision and care.

- -

Services We Provide

- - -

How We Can Help

-

Whether you need last-minute reservations at a popular restaurant, tickets to a sold-out show, or help planning a special celebration, our concierge team has the expertise and connections to make it happen. No request is too big or too small.

- -

Contact Us

-

Reach our concierge team 24/7 via phone, email, or in-person at the concierge desk. We're here to make your stay unforgettable.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Making Your Dreams Come True', - 'content': 'Your personal assistant, available 24/7 to fulfill every request.', - 'image': 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=600&fit=crop' - }, - { - 'type': 'cta', - 'title': 'Need Assistance?', - 'cta_text': 'Contact Our Concierge', - 'cta_link': '/contact', - 'content': 'Our team is standing by to help with any request, big or small.' - } - ]), - 'meta_title': 'Personal Concierge Service - 24/7 Assistance', - 'meta_description': 'Dedicated 24/7 concierge service for restaurant reservations, event tickets, tours, and all your special requests. Making your stay extraordinary.', - 'meta_keywords': 'concierge service, personal assistant, hotel concierge, travel services, luxury service', - 'is_active': True - }, - { - 'name': 'State-of-the-Art Fitness Center', - 'description': 'Access our fully equipped fitness center with modern equipment, personal training options, and group fitness classes. Open 24/7 for your convenience.', - 'price': 30.00, - 'category': 'Wellness', - 'slug': 'fitness-center-access', - 'image': 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=1200&h=800&fit=crop', - 'content': '''
-

Stay Fit, Stay Strong

-

Maintain your fitness routine at our state-of-the-art fitness center featuring the latest cardio and strength training equipment. Our facilities are designed to meet the needs of both casual exercisers and serious athletes.

- -

Equipment & Facilities

- - -

Additional Services

- - -

Operating Hours

-

Our fitness center is open 24/7 for your convenience. Personal training and group classes are available by appointment. Check the schedule at the front desk or contact our fitness team.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Your Fitness Journey Continues', - 'content': 'World-class equipment and expert guidance to help you achieve your fitness goals.', - 'image': 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Why Our Fitness Center', - 'features': [ - {'title': '24/7 Access', 'description': 'Work out on your schedule', 'icon': 'Clock'}, - {'title': 'Modern Equipment', 'description': 'Latest cardio and strength training machines', 'icon': 'Activity'}, - {'title': 'Expert Trainers', 'description': 'Certified personal trainers available', 'icon': 'User'}, - {'title': 'Group Classes', 'description': 'Yoga, Pilates, and more', 'icon': 'Users'} - ] - } - ]), - 'meta_title': 'State-of-the-Art Fitness Center - 24/7 Access', - 'meta_description': 'Fully equipped fitness center with modern equipment, personal training, and group classes. Open 24/7 for your convenience.', - 'meta_keywords': 'fitness center, gym, hotel gym, workout, exercise, personal training, 24/7 gym', - 'is_active': True - }, - { - 'name': 'Premium Laundry & Dry Cleaning', - 'description': 'Professional laundry and dry cleaning service with same-day express or next-day standard delivery. Expert care for all your garments.', - 'price': 20.00, - 'category': 'Housekeeping', - 'slug': 'premium-laundry-dry-cleaning', - 'image': 'https://images.unsplash.com/photo-1582735689369-4fe89db7114c?w=1200&h=800&fit=crop', - 'content': '''
-

Expert Garment Care

-

Keep your wardrobe fresh and impeccably maintained with our professional laundry and dry cleaning service. We handle everything from delicate silk garments to business suits with the utmost care and attention to detail.

- -

Our Services

- - -

Delivery Options

- - -

How to Use

-

Simply place your items in the laundry bag provided in your room and call housekeeping. Our team will collect your items, process them with care, and return them to your room at the specified time.

-
''', - 'sections': json.dumps([ - { - 'type': 'features', - 'title': 'Why Choose Our Service', - 'features': [ - {'title': 'Expert Care', 'description': 'Professional handling of all garment types', 'icon': 'Shield'}, - {'title': 'Fast Service', 'description': 'Same-day express option available', 'icon': 'Zap'}, - {'title': 'Quality Guarantee', 'description': 'Satisfaction guaranteed on all services', 'icon': 'CheckCircle'}, - {'title': 'Eco-Friendly', 'description': 'Environmentally conscious cleaning methods', 'icon': 'Leaf'} - ] - } - ]), - 'meta_title': 'Premium Laundry & Dry Cleaning - Professional Service', - 'meta_description': 'Professional laundry and dry cleaning with same-day or next-day delivery. Expert care for all your garments.', - 'meta_keywords': 'laundry service, dry cleaning, hotel laundry, garment care, cleaning service', - 'is_active': True - }, - { - 'name': 'Valet Parking Service', - 'description': 'Secure valet parking with 24/7 vehicle care, quick retrieval, and optional car wash services. Your vehicle is in safe hands.', - 'price': 45.00, - 'category': 'Transportation', - 'slug': 'valet-parking-service', - 'image': 'https://images.unsplash.com/photo-1502877338535-766e1452684a?w=1200&h=800&fit=crop', - 'content': '''
-

Secure & Convenient Parking

-

Enjoy the ultimate convenience of valet parking with our professional service. Your vehicle will be safely parked and secured, ready for quick retrieval whenever you need it. Our valet team is available 24/7 to assist you.

- -

Service Features

- - -

Additional Services

- - -

Pricing

-

Daily rates available for extended stays. Weekly and monthly packages offer significant savings. Contact our valet team for special rates.

-
''', - 'sections': json.dumps([ - { - 'type': 'features', - 'title': 'Why Choose Our Valet Service', - 'features': [ - {'title': 'Secure Facility', 'description': 'Monitored and protected parking', 'icon': 'Shield'}, - {'title': 'Quick Service', 'description': 'Average 5-minute retrieval time', 'icon': 'Clock'}, - {'title': '24/7 Availability', 'description': 'Service available around the clock', 'icon': 'Sun'}, - {'title': 'Additional Services', 'description': 'Car wash and detailing available', 'icon': 'Sparkles'} - ] - } - ]), - 'meta_title': 'Valet Parking Service - Secure & Convenient', - 'meta_description': 'Secure valet parking with 24/7 service, quick retrieval, and optional car care services. Your vehicle is in safe hands.', - 'meta_keywords': 'valet parking, parking service, hotel parking, car service, valet', - 'is_active': True - }, - { - 'name': 'Professional Babysitting Service', - 'description': 'Certified and experienced babysitters available for in-room care and supervised activities. Background-checked professionals for your peace of mind.', - 'price': 35.00, - 'category': 'Family', - 'slug': 'professional-babysitting-service', - 'image': 'https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=1200&h=800&fit=crop', - 'content': '''
-

Your Children in Safe Hands

-

Enjoy your time knowing your children are in the care of our professional babysitting service. Our certified caregivers are experienced, background-checked, and trained in child safety and first aid.

- -

Our Caregivers

- - -

Services Offered

- - -

Booking Information

-

Reservations should be made at least 4 hours in advance. Same-day service may be available subject to caregiver availability. Minimum booking: 2 hours.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Peace of Mind for Parents', - 'content': 'Certified professionals ensuring your children are safe, happy, and well-cared for.', - 'image': 'https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Why Parents Trust Us', - 'features': [ - {'title': 'Certified Caregivers', 'description': 'CPR and first aid certified', 'icon': 'Shield'}, - {'title': 'Background Checked', 'description': 'Thoroughly vetted professionals', 'icon': 'UserCheck'}, - {'title': 'Experienced', 'description': 'Years of childcare experience', 'icon': 'Heart'}, - {'title': 'Flexible Service', 'description': 'In-room or activity supervision', 'icon': 'Clock'} - ] - } - ]), - 'meta_title': 'Professional Babysitting Service - Certified Caregivers', - 'meta_description': 'Certified and experienced babysitters for in-room care and supervised activities. Background-checked professionals for your peace of mind.', - 'meta_keywords': 'babysitting service, childcare, hotel babysitting, kids care, family services', - 'is_active': True - }, - { - 'name': 'Pet Care & Accommodation', - 'description': 'Pet-friendly accommodations with professional pet care services including walking, feeding, grooming, and pet-sitting. Your furry friends are welcome!', - 'price': 25.00, - 'category': 'Pet Services', - 'slug': 'pet-care-accommodation', - 'image': 'https://images.unsplash.com/photo-1601758228041-f3b2795255f1?w=1200&h=800&fit=crop', - 'content': '''
-

Your Pets Are Family Too

-

We welcome your furry family members! Our pet-friendly accommodations and professional pet care services ensure your pets are as comfortable and well-cared for as you are during your stay.

- -

Pet Amenities

- - -

Pet Care Services

- - -

Pet Policy

-

Pets up to 50 lbs are welcome. A pet fee applies per night. Please inform us in advance about your pet's stay. We ask that pets be leashed in public areas and that owners clean up after their pets.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'A Home Away From Home for Your Pets', - 'content': 'Because your pets deserve the same luxury experience as you.', - 'image': 'https://images.unsplash.com/photo-1601758228041-f3b2795255f1?w=1200&h=600&fit=crop' - }, - { - 'type': 'features', - 'title': 'Pet-Friendly Features', - 'features': [ - {'title': 'Pet Amenities', 'description': 'Beds, bowls, treats, and toys provided', 'icon': 'Heart'}, - {'title': 'Professional Care', 'description': 'Walking, feeding, and grooming services', 'icon': 'User'}, - {'title': 'Designated Areas', 'description': 'Pet relief areas and walking paths', 'icon': 'MapPin'}, - {'title': 'Vet Referrals', 'description': 'Local veterinary recommendations', 'icon': 'Stethoscope'} - ] - } - ]), - 'meta_title': 'Pet Care & Accommodation - Pet-Friendly Hotel', - 'meta_description': 'Pet-friendly accommodations with professional pet care services. Walking, feeding, grooming, and pet-sitting available. Your pets are welcome!', - 'meta_keywords': 'pet care, pet friendly hotel, dog walking, pet services, pet accommodation', - 'is_active': True - }, - { - 'name': 'Premium Wine & Spirits Collection', - 'description': 'Extensive collection of fine wines, premium spirits, and craft cocktails. Expert sommelier service and private wine tastings available.', - 'price': 0.00, - 'category': 'Dining', - 'slug': 'premium-wine-spirits-collection', - 'image': 'https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?w=1200&h=800&fit=crop', - 'content': '''
-

Curated Excellence

-

Discover our extensive collection of fine wines, premium spirits, and artisanal cocktails. Our master sommelier has curated a selection that spans the globe, featuring rare vintages, limited editions, and exceptional spirits.

- -

Our Collection

- - -

Services

- - -

Locations

-

Enjoy our collection at our signature bars, restaurants, or through our room service. Our sommelier is available for consultations and recommendations.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'A World of Flavors', - 'content': 'Curated by experts, enjoyed by connoisseurs.', - 'image': 'https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?w=1200&h=600&fit=crop' - }, - { - 'type': 'quote', - 'quote': 'An exceptional collection that rivals the finest establishments in the world.', - 'author': 'Wine Enthusiast Magazine' - } - ]), - 'meta_title': 'Premium Wine & Spirits Collection - Fine Beverages', - 'meta_description': 'Extensive collection of fine wines, premium spirits, and craft cocktails. Expert sommelier service and private tastings available.', - 'meta_keywords': 'wine collection, premium spirits, sommelier, wine tasting, luxury bar, craft cocktails', - 'is_active': True - }, - { - 'name': 'Private Event & Catering Services', - 'description': 'Professional event planning and catering services for weddings, corporate events, celebrations, and private gatherings. Make your special occasion unforgettable.', - 'price': 0.00, - 'category': 'Entertainment', - 'slug': 'private-event-catering-services', - 'image': 'https://images.unsplash.com/photo-1519167758481-83f550bb49b3?w=1200&h=800&fit=crop', - 'content': '''
-

Unforgettable Events

-

From intimate gatherings to grand celebrations, our professional event planning and catering team will ensure your special occasion is executed flawlessly. We handle every detail so you can enjoy your event.

- -

Event Types

- - -

Our Services

- - -

Planning Process

-

Our event specialists will work with you from initial consultation through event execution. We offer flexible packages and can customize services to meet your specific needs and budget.

-
''', - 'sections': json.dumps([ - { - 'type': 'hero', - 'title': 'Making Memories That Last', - 'content': 'Professional event planning and catering for your most important occasions.', - 'image': 'https://images.unsplash.com/photo-1519167758481-83f550bb49b3?w=1200&h=600&fit=crop' - }, - { - 'type': 'cta', - 'title': 'Planning an Event?', - 'cta_text': 'Contact Our Event Team', - 'cta_link': '/contact', - 'content': 'Let us help you create an unforgettable experience.' - } - ]), - 'meta_title': 'Private Event & Catering Services - Professional Planning', - 'meta_description': 'Professional event planning and catering for weddings, corporate events, and celebrations. Make your special occasion unforgettable.', - 'meta_keywords': 'event planning, catering, wedding planning, corporate events, private events, hotel events', - 'is_active': True - } - ] - - created_count = 0 - updated_count = 0 - - for service_data in services_data: - # Check if service already exists by slug - existing = db.query(Service).filter(Service.slug == service_data['slug']).first() - - if existing: - # Update existing service - for key, value in service_data.items(): - if key != 'slug': # Don't update slug - setattr(existing, key, value) - updated_count += 1 - print(f' ✓ Updated service: {service_data["name"]}') - else: - # Create new service - service = Service(**service_data) - db.add(service) - created_count += 1 - print(f' ✓ Created service: {service_data["name"]}') - - db.commit() - print(f'\n✓ Enterprise services seeded successfully!') - print(f' - Created: {created_count}') - print(f' - Updated: {updated_count}') - print(f' - Total: {len(services_data)}\n') - -def main(): - db: Session = SessionLocal() - try: - print('=' * 80) - print('SEEDING ENTERPRISE SERVICES') - print('=' * 80) - print() - seed_enterprise_services(db) - print('=' * 80) - print('✓ All enterprise services data seeded successfully!') - print('=' * 80) - except Exception as e: - db.rollback() - print(f'\n✗ Error seeding services: {e}') - import traceback - traceback.print_exc() - raise - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/seeds_data/seed_users.py b/Backend/seeds_data/seed_users.py deleted file mode 100644 index ec765224..00000000 --- a/Backend/seeds_data/seed_users.py +++ /dev/null @@ -1,194 +0,0 @@ -import sys -import os -from pathlib import Path -# Add both the seeds_data directory and the Backend directory to the path -sys.path.insert(0, str(Path(__file__).parent)) -sys.path.insert(0, str(Path(__file__).parent.parent)) -from sqlalchemy.orm import Session -from src.shared.config.database import SessionLocal -from src.auth.models.role import Role -from src.auth.models.user import User -import bcrypt -from datetime import datetime - -def get_db(): - db = SessionLocal() - try: - return db - finally: - pass - -def seed_users(db: Session): - print('=' * 80) - print('SEEDING USERS') - print('=' * 80) - - # Get roles - admin_role = db.query(Role).filter(Role.name == 'admin').first() - staff_role = db.query(Role).filter(Role.name == 'staff').first() - customer_role = db.query(Role).filter(Role.name == 'customer').first() - housekeeping_role = db.query(Role).filter(Role.name == 'housekeeping').first() - - if not admin_role or not staff_role or not customer_role: - print(' ❌ Roles not found! Please seed roles first.') - return - - users_data = [ - { - 'email': 'gnxsoft@gnxsoft.com', - 'password': 'gnxsoft123', - 'full_name': 'GNXSoft Admin', - 'phone': '+1 (555) 111-2222', - 'role': 'admin', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'admin@gnxsoft.com', - 'password': 'admin123', - 'full_name': 'Administrator', - 'phone': '+1 (555) 222-3333', - 'role': 'admin', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'staff@gnxsoft.com', - 'password': 'staff123', - 'full_name': 'Staff Member', - 'phone': '+1 (555) 333-4444', - 'role': 'staff', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'customer@gnxsoft.com', - 'password': 'customer123', - 'full_name': 'Customer User', - 'phone': '+1 (555) 444-5555', - 'role': 'customer', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'john.doe@gnxsoft.com', - 'password': 'customer123', - 'full_name': 'John Doe', - 'phone': '+1 (555) 555-6666', - 'role': 'customer', - 'currency': 'USD', - 'is_active': True - }, - { - 'email': 'jane.smith@gnxsoft.com', - 'password': 'customer123', - 'full_name': 'Jane Smith', - 'phone': '+1 (555) 666-7777', - 'role': 'customer', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'robert.wilson@gnxsoft.com', - 'password': 'customer123', - 'full_name': 'Robert Wilson', - 'phone': '+1 (555) 777-8888', - 'role': 'customer', - 'currency': 'GBP', - 'is_active': True - }, - { - 'email': 'maria.garcia@gnxsoft.com', - 'password': 'customer123', - 'full_name': 'Maria Garcia', - 'phone': '+1 (555) 888-9999', - 'role': 'customer', - 'currency': 'EUR', - 'is_active': True - } - ] - - # Add housekeeping users if role exists - if housekeeping_role: - users_data.extend([ - { - 'email': 'housekeeping@gnxsoft.com', - 'password': 'housekeeping123', - 'full_name': 'Housekeeping Staff', - 'phone': '+1 (555) 999-0000', - 'role': 'housekeeping', - 'currency': 'EUR', - 'is_active': True - }, - { - 'email': 'housekeeping2@gnxsoft.com', - 'password': 'housekeeping123', - 'full_name': 'Housekeeping Staff 2', - 'phone': '+1 (555) 999-0001', - 'role': 'housekeeping', - 'currency': 'EUR', - 'is_active': True - } - ]) - - role_map = { - 'admin': admin_role.id, - 'staff': staff_role.id, - 'customer': customer_role.id - } - - if housekeeping_role: - role_map['housekeeping'] = housekeeping_role.id - - created_count = 0 - skipped_count = 0 - - for user_data in users_data: - existing = db.query(User).filter(User.email == user_data['email']).first() - if existing: - print(f' ⚠️ User "{user_data["email"]}" already exists, skipping...') - skipped_count += 1 - continue - - password = user_data.pop('password') - role_name = user_data.pop('role') - role_id = role_map[role_name] - - password_bytes = password.encode('utf-8') - salt = bcrypt.gensalt() - hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - - user = User( - email=user_data['email'], - password=hashed_password, - full_name=user_data['full_name'], - phone=user_data.get('phone'), - role_id=role_id, - currency=user_data.get('currency', 'EUR'), - is_active=user_data.get('is_active', True) - ) - db.add(user) - print(f' ✓ Created user: {user_data["email"]} ({role_name}) - Password: {password}') - created_count += 1 - - db.commit() - print(f'\n✓ Users seeded successfully!') - print(f' - Created: {created_count} user(s)') - print(f' - Skipped: {skipped_count} user(s) (already exist)') - print('=' * 80) - -def main(): - db = get_db() - try: - seed_users(db) - except Exception as e: - print(f'\n❌ Error: {e}') - import traceback - traceback.print_exc() - db.rollback() - finally: - db.close() - -if __name__ == '__main__': - main() - diff --git a/Backend/src/__pycache__/__init__.cpython-312.pyc b/Backend/src/__pycache__/__init__.cpython-312.pyc index 2fb27bd0..7e95d83d 100644 Binary files a/Backend/src/__pycache__/__init__.cpython-312.pyc and b/Backend/src/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/__pycache__/main.cpython-312.pyc b/Backend/src/__pycache__/main.cpython-312.pyc index 4382fed8..05eb1750 100644 Binary files a/Backend/src/__pycache__/main.cpython-312.pyc and b/Backend/src/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/src/ai/__pycache__/__init__.cpython-312.pyc b/Backend/src/ai/__pycache__/__init__.cpython-312.pyc index c94ce751..84428b1d 100644 Binary files a/Backend/src/ai/__pycache__/__init__.cpython-312.pyc and b/Backend/src/ai/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/ai/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/ai/models/__pycache__/__init__.cpython-312.pyc index f2c33a9a..12fbfbd2 100644 Binary files a/Backend/src/ai/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/ai/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/ai/models/__pycache__/ai_conversation.cpython-312.pyc b/Backend/src/ai/models/__pycache__/ai_conversation.cpython-312.pyc index 2ebf78ea..d7543c0a 100644 Binary files a/Backend/src/ai/models/__pycache__/ai_conversation.cpython-312.pyc and b/Backend/src/ai/models/__pycache__/ai_conversation.cpython-312.pyc differ diff --git a/Backend/src/ai/models/__pycache__/chat.cpython-312.pyc b/Backend/src/ai/models/__pycache__/chat.cpython-312.pyc index d3ff04aa..381748d4 100644 Binary files a/Backend/src/ai/models/__pycache__/chat.cpython-312.pyc and b/Backend/src/ai/models/__pycache__/chat.cpython-312.pyc differ diff --git a/Backend/src/ai/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/ai/routes/__pycache__/__init__.cpython-312.pyc index 9f3765fe..ad19ba63 100644 Binary files a/Backend/src/ai/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/ai/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/ai/routes/__pycache__/ai_assistant_routes.cpython-312.pyc b/Backend/src/ai/routes/__pycache__/ai_assistant_routes.cpython-312.pyc index 127a01ca..7fec1e92 100644 Binary files a/Backend/src/ai/routes/__pycache__/ai_assistant_routes.cpython-312.pyc and b/Backend/src/ai/routes/__pycache__/ai_assistant_routes.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/ai/services/__pycache__/__init__.cpython-312.pyc index d5f3c54e..debe952c 100644 Binary files a/Backend/src/ai/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_assistant_service.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_assistant_service.cpython-312.pyc index 86b15cec..664c856c 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_assistant_service.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_assistant_service.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_chat_service.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_chat_service.cpython-312.pyc index 4c5c54e9..bf14b009 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_chat_service.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_chat_service.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_knowledge_base.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_knowledge_base.cpython-312.pyc index 019189ac..0f4f3601 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_knowledge_base.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_knowledge_base.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_learning_service.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_learning_service.cpython-312.pyc index c4aaf45e..71ef80b3 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_learning_service.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_learning_service.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_role_access_service.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_role_access_service.cpython-312.pyc index 2ff6bc95..2f0613ef 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_role_access_service.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_role_access_service.cpython-312.pyc differ diff --git a/Backend/src/ai/services/__pycache__/ai_training_scheduler.cpython-312.pyc b/Backend/src/ai/services/__pycache__/ai_training_scheduler.cpython-312.pyc index fd4b1bf2..4fd42325 100644 Binary files a/Backend/src/ai/services/__pycache__/ai_training_scheduler.cpython-312.pyc and b/Backend/src/ai/services/__pycache__/ai_training_scheduler.cpython-312.pyc differ diff --git a/Backend/src/analytics/__pycache__/__init__.cpython-312.pyc b/Backend/src/analytics/__pycache__/__init__.cpython-312.pyc index ef97dbfe..2c6bc7e8 100644 Binary files a/Backend/src/analytics/__pycache__/__init__.cpython-312.pyc and b/Backend/src/analytics/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/analytics/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/analytics/models/__pycache__/__init__.cpython-312.pyc index 79f48225..c91d5fb8 100644 Binary files a/Backend/src/analytics/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/analytics/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/analytics/models/__pycache__/audit_log.cpython-312.pyc b/Backend/src/analytics/models/__pycache__/audit_log.cpython-312.pyc index e07e4bb6..daf0226a 100644 Binary files a/Backend/src/analytics/models/__pycache__/audit_log.cpython-312.pyc and b/Backend/src/analytics/models/__pycache__/audit_log.cpython-312.pyc differ diff --git a/Backend/src/analytics/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/analytics/routes/__pycache__/__init__.cpython-312.pyc index 167a899a..623cb5d6 100644 Binary files a/Backend/src/analytics/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/analytics/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/analytics/routes/__pycache__/analytics_routes.cpython-312.pyc b/Backend/src/analytics/routes/__pycache__/analytics_routes.cpython-312.pyc index af7ad46c..264d6118 100644 Binary files a/Backend/src/analytics/routes/__pycache__/analytics_routes.cpython-312.pyc and b/Backend/src/analytics/routes/__pycache__/analytics_routes.cpython-312.pyc differ diff --git a/Backend/src/analytics/routes/__pycache__/audit_routes.cpython-312.pyc b/Backend/src/analytics/routes/__pycache__/audit_routes.cpython-312.pyc index f32acac8..67048fff 100644 Binary files a/Backend/src/analytics/routes/__pycache__/audit_routes.cpython-312.pyc and b/Backend/src/analytics/routes/__pycache__/audit_routes.cpython-312.pyc differ diff --git a/Backend/src/analytics/routes/__pycache__/report_routes.cpython-312.pyc b/Backend/src/analytics/routes/__pycache__/report_routes.cpython-312.pyc index ec5d350d..9a6491a3 100644 Binary files a/Backend/src/analytics/routes/__pycache__/report_routes.cpython-312.pyc and b/Backend/src/analytics/routes/__pycache__/report_routes.cpython-312.pyc differ diff --git a/Backend/src/analytics/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/analytics/services/__pycache__/__init__.cpython-312.pyc index 75632ca4..19194401 100644 Binary files a/Backend/src/analytics/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/analytics/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/analytics/services/__pycache__/analytics_service.cpython-312.pyc b/Backend/src/analytics/services/__pycache__/analytics_service.cpython-312.pyc index 4ef2b2fe..bbabc37c 100644 Binary files a/Backend/src/analytics/services/__pycache__/analytics_service.cpython-312.pyc and b/Backend/src/analytics/services/__pycache__/analytics_service.cpython-312.pyc differ diff --git a/Backend/src/analytics/services/__pycache__/audit_service.cpython-312.pyc b/Backend/src/analytics/services/__pycache__/audit_service.cpython-312.pyc index ec72cb78..ebf97b8c 100644 Binary files a/Backend/src/analytics/services/__pycache__/audit_service.cpython-312.pyc and b/Backend/src/analytics/services/__pycache__/audit_service.cpython-312.pyc differ diff --git a/Backend/src/auth/__pycache__/__init__.cpython-312.pyc b/Backend/src/auth/__pycache__/__init__.cpython-312.pyc index c416a74c..2c6959ab 100644 Binary files a/Backend/src/auth/__pycache__/__init__.cpython-312.pyc and b/Backend/src/auth/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/auth/models/__pycache__/__init__.cpython-312.pyc index 423d60ea..bbc1c928 100644 Binary files a/Backend/src/auth/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/email_verification_token.cpython-312.pyc b/Backend/src/auth/models/__pycache__/email_verification_token.cpython-312.pyc new file mode 100644 index 00000000..f1286947 Binary files /dev/null and b/Backend/src/auth/models/__pycache__/email_verification_token.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/password_history.cpython-312.pyc b/Backend/src/auth/models/__pycache__/password_history.cpython-312.pyc new file mode 100644 index 00000000..27fadea6 Binary files /dev/null and b/Backend/src/auth/models/__pycache__/password_history.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/password_reset_token.cpython-312.pyc b/Backend/src/auth/models/__pycache__/password_reset_token.cpython-312.pyc index ee709746..ded34570 100644 Binary files a/Backend/src/auth/models/__pycache__/password_reset_token.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/password_reset_token.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/refresh_token.cpython-312.pyc b/Backend/src/auth/models/__pycache__/refresh_token.cpython-312.pyc index cd3e30ce..332347fd 100644 Binary files a/Backend/src/auth/models/__pycache__/refresh_token.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/refresh_token.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/role.cpython-312.pyc b/Backend/src/auth/models/__pycache__/role.cpython-312.pyc index 00c502df..ecd5c73d 100644 Binary files a/Backend/src/auth/models/__pycache__/role.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/role.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/user.cpython-312.pyc b/Backend/src/auth/models/__pycache__/user.cpython-312.pyc index dcf5d51f..cc9a0948 100644 Binary files a/Backend/src/auth/models/__pycache__/user.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/user.cpython-312.pyc differ diff --git a/Backend/src/auth/models/__pycache__/user_session.cpython-312.pyc b/Backend/src/auth/models/__pycache__/user_session.cpython-312.pyc index 2adb1198..3903c1cb 100644 Binary files a/Backend/src/auth/models/__pycache__/user_session.cpython-312.pyc and b/Backend/src/auth/models/__pycache__/user_session.cpython-312.pyc differ diff --git a/Backend/src/auth/models/email_verification_token.py b/Backend/src/auth/models/email_verification_token.py new file mode 100644 index 00000000..186b5d4b --- /dev/null +++ b/Backend/src/auth/models/email_verification_token.py @@ -0,0 +1,17 @@ +from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey +from sqlalchemy.orm import relationship +from datetime import datetime +from ...shared.config.database import Base + +class EmailVerificationToken(Base): + __tablename__ = 'email_verification_tokens' + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True) + token = Column(String(255), unique=True, nullable=False, index=True) + email = Column(String(100), nullable=False) # Email being verified (may differ from user.email if email changed) + expires_at = Column(DateTime, nullable=False, index=True) + used = Column(Boolean, default=False, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) + user = relationship('User') + diff --git a/Backend/src/auth/models/password_history.py b/Backend/src/auth/models/password_history.py new file mode 100644 index 00000000..f9dd32ac --- /dev/null +++ b/Backend/src/auth/models/password_history.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey +from sqlalchemy.orm import relationship +from datetime import datetime +from ...shared.config.database import Base + +class PasswordHistory(Base): + __tablename__ = 'password_history' + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True) + password_hash = Column(String(255), nullable=False) # Hashed password + created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True) + user = relationship('User', back_populates='password_history') + diff --git a/Backend/src/auth/models/user.py b/Backend/src/auth/models/user.py index c36af38c..b47283a3 100644 --- a/Backend/src/auth/models/user.py +++ b/Backend/src/auth/models/user.py @@ -15,6 +15,7 @@ class User(Base): avatar = Column(String(255), nullable=True) currency = Column(String(3), nullable=False, default='VND') is_active = Column(Boolean, nullable=False, default=True) + email_verified = Column(Boolean, nullable=False, default=False, index=True) # Email verification status mfa_enabled = Column(Boolean, nullable=False, default=False) mfa_secret = Column(String(255), nullable=True) mfa_backup_codes = Column(Text, nullable=True) @@ -23,6 +24,9 @@ class User(Base): failed_login_attempts = Column(Integer, nullable=False, default=0) locked_until = Column(DateTime, nullable=True) + # Password expiry (optional - can be None for no expiry) + password_changed_at = Column(DateTime, nullable=True, index=True) + # Guest Profile & CRM fields is_vip = Column(Boolean, nullable=False, default=False) lifetime_value = Column(Numeric(10, 2), nullable=True, default=0) # Total revenue from guest @@ -50,4 +54,6 @@ class User(Base): guest_notes = relationship('GuestNote', foreign_keys='GuestNote.user_id', back_populates='user', cascade='all, delete-orphan') guest_tags = relationship('GuestTag', secondary='guest_tag_associations', back_populates='users') guest_communications = relationship('GuestCommunication', foreign_keys='GuestCommunication.user_id', back_populates='user', cascade='all, delete-orphan') - guest_segments = relationship('GuestSegment', secondary='guest_segment_associations', back_populates='users') \ No newline at end of file + guest_segments = relationship('GuestSegment', secondary='guest_segment_associations', back_populates='users') + email_verification_tokens = relationship('EmailVerificationToken', back_populates='user', cascade='all, delete-orphan') + password_history = relationship('PasswordHistory', back_populates='user', cascade='all, delete-orphan', order_by='PasswordHistory.created_at.desc()') \ No newline at end of file diff --git a/Backend/src/auth/models/user_session.py b/Backend/src/auth/models/user_session.py index 78913732..56902b51 100644 --- a/Backend/src/auth/models/user_session.py +++ b/Backend/src/auth/models/user_session.py @@ -3,7 +3,7 @@ User session management model. """ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Text from sqlalchemy.orm import relationship -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from ...shared.config.database import Base from ...shared.config.settings import settings @@ -34,7 +34,7 @@ class UserSession(Base): @property def is_expired(self) -> bool: """Check if session is expired.""" - return datetime.utcnow() > self.expires_at + return datetime.now(timezone.utc) > self.expires_at @property def is_valid(self) -> bool: diff --git a/Backend/src/auth/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/auth/routes/__pycache__/__init__.cpython-312.pyc index 9d3fae39..2d4f573f 100644 Binary files a/Backend/src/auth/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/auth/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/auth/routes/__pycache__/auth_routes.cpython-312.pyc b/Backend/src/auth/routes/__pycache__/auth_routes.cpython-312.pyc index 1e301a81..070930ae 100644 Binary files a/Backend/src/auth/routes/__pycache__/auth_routes.cpython-312.pyc and b/Backend/src/auth/routes/__pycache__/auth_routes.cpython-312.pyc differ diff --git a/Backend/src/auth/routes/__pycache__/session_routes.cpython-312.pyc b/Backend/src/auth/routes/__pycache__/session_routes.cpython-312.pyc index 90723a60..0e7ece27 100644 Binary files a/Backend/src/auth/routes/__pycache__/session_routes.cpython-312.pyc and b/Backend/src/auth/routes/__pycache__/session_routes.cpython-312.pyc differ diff --git a/Backend/src/auth/routes/__pycache__/user_routes.cpython-312.pyc b/Backend/src/auth/routes/__pycache__/user_routes.cpython-312.pyc index 4b677f4e..f7d1c809 100644 Binary files a/Backend/src/auth/routes/__pycache__/user_routes.cpython-312.pyc and b/Backend/src/auth/routes/__pycache__/user_routes.cpython-312.pyc differ diff --git a/Backend/src/auth/routes/auth_routes.py b/Backend/src/auth/routes/auth_routes.py index fe50dbc3..f57f8f03 100644 --- a/Backend/src/auth/routes/auth_routes.py +++ b/Backend/src/auth/routes/auth_routes.py @@ -524,7 +524,15 @@ async def update_profile( # Rate limiting is handled by global middleware (slowapi) # The global rate limiter applies default limits to all endpoints # For stricter limits on profile updates, configure in settings or use endpoint-specific limits + client_ip = request.client.host if request.client else None + user_agent = request.headers.get('User-Agent') + request_id = getattr(request.state, 'request_id', None) + try: + # Track if password is being changed for audit logging + password_changed = bool(profile_data.password) + email_changed = bool(profile_data.email and profile_data.email != current_user.email) + user = await auth_service.update_profile( db=db, user_id=current_user.id, @@ -535,6 +543,36 @@ async def update_profile( current_password=profile_data.currentPassword, currency=profile_data.currency ) + + # Audit logging for sensitive profile changes + if password_changed: + await audit_service.log_action( + db=db, + action='user_password_changed', + resource_type='user', + user_id=current_user.id, + resource_id=current_user.id, + ip_address=client_ip, + user_agent=user_agent, + request_id=request_id, + details={'user_email': current_user.email}, + status='success' + ) + + if email_changed: + await audit_service.log_action( + db=db, + action='user_email_change_requested', + resource_type='user', + user_id=current_user.id, + resource_id=current_user.id, + ip_address=client_ip, + user_agent=user_agent, + request_id=request_id, + details={'old_email': current_user.email, 'new_email': profile_data.email}, + status='success' + ) + return {'status': 'success', 'message': 'Profile updated successfully', 'data': {'user': user}} except ValueError as e: error_message = str(e) @@ -551,15 +589,67 @@ async def forgot_password(request: ForgotPasswordRequest, db: Session=Depends(ge return {'status': 'success', 'message': result['message']} @router.post('/reset-password', response_model=MessageResponse) -async def reset_password(request: ResetPasswordRequest, db: Session=Depends(get_db)): +async def reset_password(request: Request, reset_request: ResetPasswordRequest, db: Session=Depends(get_db)): + client_ip = request.client.host if request.client else None + user_agent = request.headers.get('User-Agent') + request_id = getattr(request.state, 'request_id', None) + try: - result = await auth_service.reset_password(db=db, token=request.token, password=request.password) + result = await auth_service.reset_password(db=db, token=reset_request.token, password=reset_request.password) + + # Get user ID from reset token for audit logging + from ..models.password_reset_token import PasswordResetToken + import hashlib + hashed_token = hashlib.sha256(reset_request.token.encode()).hexdigest() + reset_token_obj = db.query(PasswordResetToken).filter( + PasswordResetToken.token == hashed_token + ).first() + + if reset_token_obj: + await audit_service.log_action( + db=db, + action='user_password_reset', + resource_type='user', + user_id=reset_token_obj.user_id, + resource_id=reset_token_obj.user_id, + ip_address=client_ip, + user_agent=user_agent, + request_id=request_id, + details={'reset_method': 'token'}, + status='success' + ) + return {'status': 'success', 'message': result['message']} except ValueError as e: status_code = status.HTTP_400_BAD_REQUEST if 'User not found' in str(e): status_code = status.HTTP_404_NOT_FOUND raise HTTPException(status_code=status_code, detail=str(e)) + +@router.post('/verify-email/{token}', response_model=MessageResponse) +async def verify_email(token: str, db: Session=Depends(get_db)): + """Verify email address using verification token.""" + try: + result = await auth_service.verify_email(db=db, token=token) + return {'status': 'success', 'message': result['message']} + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + except Exception as e: + logger.error(f'Error verifying email: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='An error occurred while verifying your email') + +@router.post('/resend-verification', response_model=MessageResponse) +async def resend_verification(current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): + """Resend email verification link.""" + try: + result = await auth_service.resend_verification_email(db=db, user_id=current_user.id) + return {'status': 'success', 'message': result['message']} + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + except Exception as e: + logger.error(f'Error resending verification email: {str(e)}', exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='An error occurred while resending verification email') + from ..services.mfa_service import mfa_service from ...shared.config.settings import settings @@ -578,9 +668,28 @@ async def init_mfa(current_user: User=Depends(get_current_user), db: Session=Dep raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error initializing MFA: {str(e)}') @router.post('/mfa/enable') -async def enable_mfa(request: EnableMFARequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): +async def enable_mfa(request: Request, mfa_request: EnableMFARequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): + client_ip = request.client.host if request.client else None + user_agent = request.headers.get('User-Agent') + request_id = getattr(request.state, 'request_id', None) + try: - success, backup_codes = mfa_service.enable_mfa(db=db, user_id=current_user.id, secret=request.secret, verification_token=request.verification_token) + success, backup_codes = mfa_service.enable_mfa(db=db, user_id=current_user.id, secret=mfa_request.secret, verification_token=mfa_request.verification_token) + + # Audit logging for MFA enable + await audit_service.log_action( + db=db, + action='user_mfa_enabled', + resource_type='user', + user_id=current_user.id, + resource_id=current_user.id, + ip_address=client_ip, + user_agent=user_agent, + request_id=request_id, + details={'user_email': current_user.email}, + status='success' + ) + return {'status': 'success', 'message': 'MFA enabled successfully', 'data': {'backup_codes': backup_codes}} except ValueError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) @@ -588,9 +697,28 @@ async def enable_mfa(request: EnableMFARequest, current_user: User=Depends(get_c raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error enabling MFA: {str(e)}') @router.post('/mfa/disable') -async def disable_mfa(current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): +async def disable_mfa(request: Request, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): + client_ip = request.client.host if request.client else None + user_agent = request.headers.get('User-Agent') + request_id = getattr(request.state, 'request_id', None) + try: mfa_service.disable_mfa(db=db, user_id=current_user.id) + + # Audit logging for MFA disable + await audit_service.log_action( + db=db, + action='user_mfa_disabled', + resource_type='user', + user_id=current_user.id, + resource_id=current_user.id, + ip_address=client_ip, + user_agent=user_agent, + request_id=request_id, + details={'user_email': current_user.email}, + status='success' + ) + return {'status': 'success', 'message': 'MFA disabled successfully'} except ValueError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) diff --git a/Backend/src/auth/routes/user_routes.py b/Backend/src/auth/routes/user_routes.py index 30bf9084..d48a5922 100644 --- a/Backend/src/auth/routes/user_routes.py +++ b/Backend/src/auth/routes/user_routes.py @@ -5,6 +5,7 @@ from typing import Optional import bcrypt from ...shared.config.database import get_db from ...security.middleware.auth import get_current_user, authorize_roles +from ...security.middleware.step_up_auth import require_step_up_auth from ..models.user import User from ..models.role import Role from ...bookings.models.booking import Booking, BookingStatus @@ -53,7 +54,7 @@ async def get_user_by_id(id: int, current_user: User=Depends(authorize_roles('ad except Exception as e: raise HTTPException(status_code=500, detail=str(e)) -@router.post('/', dependencies=[Depends(authorize_roles('admin'))]) +@router.post('/', dependencies=[Depends(authorize_roles('admin')), Depends(require_step_up_auth("user creation"))]) async def create_user( request: Request, user_data: CreateUserRequest, @@ -77,7 +78,17 @@ async def create_user( password_bytes = password.encode('utf-8') salt = bcrypt.gensalt() hashed_password = bcrypt.hashpw(password_bytes, salt).decode('utf-8') - user = User(email=email, password=hashed_password, full_name=full_name, phone=phone_number, role_id=role_id, is_active=True) + from datetime import datetime + user = User( + email=email, + password=hashed_password, + full_name=full_name, + phone=phone_number, + role_id=role_id, + is_active=True, + email_verified=False, # Admin-created users need email verification + password_changed_at=datetime.utcnow() # Set initial password change timestamp + ) db.add(user) db.commit() db.refresh(user) @@ -110,7 +121,13 @@ async def create_user( raise HTTPException(status_code=500, detail=str(e)) @router.put('/{id}') -async def update_user(id: int, request: Request, user_data: UpdateUserRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)): +async def update_user( + id: int, + request: Request, + user_data: UpdateUserRequest, + current_user: User=Depends(get_current_user), + db: Session=Depends(get_db) +): """Update a user with validated input using Pydantic schema.""" client_ip = request.client.host if request.client else None user_agent = request.headers.get('User-Agent') @@ -119,6 +136,30 @@ async def update_user(id: int, request: Request, user_data: UpdateUserRequest, c try: if not can_manage_users(current_user, db) and current_user.id != id: raise HTTPException(status_code=403, detail='Forbidden') + + # SECURITY: Require step-up auth for admin user management operations + is_admin_managing_user = can_manage_users(current_user, db) and current_user.id != id + if is_admin_managing_user: + # Check if step-up auth is required (this will raise if not authenticated) + from ...payments.services.accountant_security_service import accountant_security_service + session_token = request.headers.get('X-Session-Token') or request.cookies.get('session_token') + requires_step_up, reason = accountant_security_service.require_step_up( + db=db, + user_id=current_user.id, + session_token=session_token, + action_description="user management" + ) + if requires_step_up: + from fastapi import status + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={ + 'error': 'step_up_required', + 'message': reason or 'Step-up authentication required for user management operations', + 'action': 'user_management' + } + ) + user = db.query(User).options(joinedload(User.role)).filter(User.id == id).first() if not user: raise HTTPException(status_code=404, detail='User not found') @@ -128,6 +169,7 @@ async def update_user(id: int, request: Request, user_data: UpdateUserRequest, c old_role_id = user.role_id old_role_name = user.role.name if user.role else None old_is_active = user.is_active + old_email_verified = getattr(user, 'email_verified', True) # Check email uniqueness if being updated if user_data.email and user_data.email != user.email: @@ -230,7 +272,7 @@ async def update_user(id: int, request: Request, user_data: UpdateUserRequest, c db.rollback() raise HTTPException(status_code=500, detail=str(e)) -@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin'))]) +@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin')), Depends(require_step_up_auth("user deletion"))]) async def delete_user(id: int, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)): client_ip = request.client.host if request.client else None user_agent = request.headers.get('User-Agent') diff --git a/Backend/src/auth/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/auth/schemas/__pycache__/__init__.cpython-312.pyc index a3b2691c..3e895f58 100644 Binary files a/Backend/src/auth/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/auth/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/auth/schemas/__pycache__/auth.cpython-312.pyc b/Backend/src/auth/schemas/__pycache__/auth.cpython-312.pyc index 90d58157..e78acb8b 100644 Binary files a/Backend/src/auth/schemas/__pycache__/auth.cpython-312.pyc and b/Backend/src/auth/schemas/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/src/auth/schemas/__pycache__/user.cpython-312.pyc b/Backend/src/auth/schemas/__pycache__/user.cpython-312.pyc index 45d9fead..2402639d 100644 Binary files a/Backend/src/auth/schemas/__pycache__/user.cpython-312.pyc and b/Backend/src/auth/schemas/__pycache__/user.cpython-312.pyc differ diff --git a/Backend/src/auth/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/auth/services/__pycache__/__init__.cpython-312.pyc index c7203fab..8294604d 100644 Binary files a/Backend/src/auth/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/auth/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/auth/services/__pycache__/auth_service.cpython-312.pyc b/Backend/src/auth/services/__pycache__/auth_service.cpython-312.pyc index 07bf488c..b0c5da18 100644 Binary files a/Backend/src/auth/services/__pycache__/auth_service.cpython-312.pyc and b/Backend/src/auth/services/__pycache__/auth_service.cpython-312.pyc differ diff --git a/Backend/src/auth/services/__pycache__/mfa_service.cpython-312.pyc b/Backend/src/auth/services/__pycache__/mfa_service.cpython-312.pyc index 702da748..2de1c10d 100644 Binary files a/Backend/src/auth/services/__pycache__/mfa_service.cpython-312.pyc and b/Backend/src/auth/services/__pycache__/mfa_service.cpython-312.pyc differ diff --git a/Backend/src/auth/services/__pycache__/oauth_service.cpython-312.pyc b/Backend/src/auth/services/__pycache__/oauth_service.cpython-312.pyc index 0ec25732..4be9916f 100644 Binary files a/Backend/src/auth/services/__pycache__/oauth_service.cpython-312.pyc and b/Backend/src/auth/services/__pycache__/oauth_service.cpython-312.pyc differ diff --git a/Backend/src/auth/services/__pycache__/session_service.cpython-312.pyc b/Backend/src/auth/services/__pycache__/session_service.cpython-312.pyc index 98e22c24..ee46edf7 100644 Binary files a/Backend/src/auth/services/__pycache__/session_service.cpython-312.pyc and b/Backend/src/auth/services/__pycache__/session_service.cpython-312.pyc differ diff --git a/Backend/src/auth/services/auth_service.py b/Backend/src/auth/services/auth_service.py index 244ecd15..a8e3e1f1 100644 --- a/Backend/src/auth/services/auth_service.py +++ b/Backend/src/auth/services/auth_service.py @@ -10,13 +10,18 @@ import logging from ..models.user import User from ..models.refresh_token import RefreshToken from ..models.password_reset_token import PasswordResetToken +from ..models.email_verification_token import EmailVerificationToken +from ..models.password_history import PasswordHistory from ..models.role import Role from ...shared.utils.mailer import send_email from ...shared.utils.email_templates import ( welcome_email_template, password_reset_email_template, - password_changed_email_template + password_changed_email_template, + email_verification_template, + email_changed_verification_template ) +from ...auth.services.session_service import session_service from ...shared.config.settings import settings import os @@ -137,6 +142,7 @@ class AuthService: "avatar": user.avatar, "currency": getattr(user, 'currency', 'VND'), "role": user.role.name if user.role else "customer", + "emailVerified": getattr(user, 'email_verified', True), # Default to True for backward compatibility "createdAt": user.created_at.isoformat() if user.created_at else None, "updatedAt": user.updated_at.isoformat() if user.updated_at else None, } @@ -160,7 +166,9 @@ class AuthService: email=email, password=hashed_password, phone=phone, - role_id=3 + role_id=3, + email_verified=False, # Email not verified on registration + password_changed_at=datetime.utcnow() # Set initial password change timestamp ) db.add(user) db.commit() @@ -168,6 +176,20 @@ class AuthService: user.role = db.query(Role).filter(Role.id == user.role_id).first() + # Generate email verification token + verification_token = secrets.token_hex(32) + hashed_verification_token = hashlib.sha256(verification_token.encode()).hexdigest() + expires_at = datetime.utcnow() + timedelta(hours=24) # 24 hour expiration + + verification_token_obj = EmailVerificationToken( + user_id=user.id, + token=hashed_verification_token, + email=email, + expires_at=expires_at + ) + db.add(verification_token_obj) + db.commit() + tokens = self.generate_tokens(user.id) expires_at = datetime.utcnow() + timedelta(days=7) @@ -179,17 +201,19 @@ class AuthService: db.add(refresh_token) db.commit() + # Send welcome email with verification link try: client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173") - email_html = welcome_email_template(user.full_name, user.email, client_url) + verification_url = f"{client_url}/verify-email/{verification_token}" + email_html = email_verification_template(verification_url, user.full_name) await send_email( to=user.email, - subject="Welcome to Hotel Booking", + subject="Welcome to Hotel Booking - Verify Your Email", html=email_html ) - logger.info(f"Welcome email sent successfully to {user.email}") + logger.info(f"Verification email sent successfully to {user.email}") except Exception as e: - logger.error(f"Failed to send welcome email to {user.email}: {type(e).__name__}: {str(e)}", exc_info=True) + logger.error(f"Failed to send verification email to {user.email}: {type(e).__name__}: {str(e)}", exc_info=True) return { "user": self.format_user_response(user), @@ -212,6 +236,13 @@ class AuthService: if not user.is_active: logger.warning(f"Login attempt for inactive user: {email}") raise ValueError("Account is disabled. Please contact support.") + + # Check password expiry (if enabled) + if settings.PASSWORD_EXPIRY_DAYS > 0 and user.password_changed_at: + expiry_date = user.password_changed_at + timedelta(days=settings.PASSWORD_EXPIRY_DAYS) + if datetime.utcnow() > expiry_date: + days_expired = (datetime.utcnow() - expiry_date).days + raise ValueError(f"Your password has expired {days_expired} day(s) ago. Please reset your password to continue.") # Check if account is locked (reset if lockout expired) if user.locked_until: @@ -248,12 +279,18 @@ class AuthService: # SECURITY: Don't reveal remaining attempts to prevent enumeration raise ValueError("Invalid email or password") + # Check email verification (warn but allow login for better UX) + email_verification_required = not user.email_verified + if email_verification_required: + logger.info(f"Login attempt for unverified email: {email}") + if user.mfa_enabled: if not mfa_token: return { "requires_mfa": True, - "user_id": user.id + "user_id": user.id, + "email_verified": user.email_verified } @@ -411,27 +448,118 @@ class AuthService: if not self.verify_password(current_password, user.password): raise ValueError("Current password is incorrect") + # Check password minimum age + if user.password_changed_at and settings.PASSWORD_MIN_AGE_DAYS > 0: + min_age_date = user.password_changed_at + timedelta(days=settings.PASSWORD_MIN_AGE_DAYS) + if datetime.utcnow() < min_age_date: + days_remaining = (min_age_date - datetime.utcnow()).days + 1 + raise ValueError(f"Password cannot be changed yet. Minimum age is {settings.PASSWORD_MIN_AGE_DAYS} days. Please try again in {days_remaining} day(s).") + # Validate new password strength from ...shared.utils.password_validation import validate_password_strength is_valid, errors = validate_password_strength(password) if not is_valid: error_message = 'New password does not meet requirements: ' + '; '.join(errors) raise ValueError(error_message) + + # Check password history to prevent reuse + hashed_new_password = self.hash_password(password) + if settings.PASSWORD_HISTORY_COUNT > 0: + # Get recent password history + recent_passwords = db.query(PasswordHistory).filter( + PasswordHistory.user_id == user.id + ).order_by(PasswordHistory.created_at.desc()).limit(settings.PASSWORD_HISTORY_COUNT).all() + + # Check if new password matches any recent password + for old_password in recent_passwords: + if self.verify_password(password, old_password.password_hash): + raise ValueError(f"Password cannot be reused. You must use a password that hasn't been used in your last {settings.PASSWORD_HISTORY_COUNT} password changes.") + + # Check if new password is same as current password + if self.verify_password(password, user.password): + raise ValueError("New password must be different from your current password") - user.password = self.hash_password(password) + # SECURITY: Revoke all existing sessions and refresh tokens on password change + # This prevents compromised sessions from remaining valid after password change + from ..models.refresh_token import RefreshToken + revoked_sessions = session_service.revoke_all_user_sessions(db, user.id) + revoked_tokens = db.query(RefreshToken).filter(RefreshToken.user_id == user.id).delete() + logger.info(f"Password change for user {user.id}: Revoked {revoked_sessions} sessions and {revoked_tokens} refresh tokens") + + # Save current password to history before changing + if settings.PASSWORD_HISTORY_COUNT > 0: + password_history_entry = PasswordHistory( + user_id=user.id, + password_hash=user.password + ) + db.add(password_history_entry) + + # Clean up old password history (keep only the configured number) + old_passwords = db.query(PasswordHistory).filter( + PasswordHistory.user_id == user.id + ).order_by(PasswordHistory.created_at.desc()).offset(settings.PASSWORD_HISTORY_COUNT).all() + for old_pwd in old_passwords: + db.delete(old_pwd) + + # Update password and timestamp + user.password = hashed_new_password + user.password_changed_at = datetime.utcnow() if full_name is not None: user.full_name = full_name + email_verification_required = False + verification_token = None + if email is not None: # Normalize email (lowercase and trim) email = email.lower().strip() - existing_user = db.query(User).filter( - User.email == email, - User.id != user_id - ).first() - if existing_user: - raise ValueError("Email already registered") - user.email = email + + # If email is changing, require re-verification + if email != user.email: + existing_user = db.query(User).filter( + User.email == email, + User.id != user_id + ).first() + if existing_user: + raise ValueError("Email already in use") + + # Create email verification token for new email + verification_token = secrets.token_hex(32) + hashed_verification_token = hashlib.sha256(verification_token.encode()).hexdigest() + expires_at = datetime.utcnow() + timedelta(hours=24) + + # Mark email as unverified until new email is verified + user.email_verified = False + email_verification_required = True + + # Delete old verification tokens for this user + db.query(EmailVerificationToken).filter(EmailVerificationToken.user_id == user.id).delete() + + verification_token_obj = EmailVerificationToken( + user_id=user.id, + token=hashed_verification_token, + email=email, # New email to verify + expires_at=expires_at + ) + db.add(verification_token_obj) + + # Update email (but it's not verified yet) + user.email = email + + # Send verification email to new address + try: + client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173") + verification_url = f"{client_url}/verify-email/{verification_token}" + email_html = email_changed_verification_template(verification_url, user.full_name, email) + await send_email( + to=email, + subject="Verify Your New Email Address", + html=email_html + ) + logger.info(f"Email change verification sent to {email}") + except Exception as e: + logger.error(f"Failed to send email change verification to {email}: {type(e).__name__}: {str(e)}", exc_info=True) + if phone_number is not None: user.phone = phone_number if currency is not None: @@ -446,7 +574,12 @@ class AuthService: user.role = db.query(Role).filter(Role.id == user.role_id).first() - return self.format_user_response(user) + response = self.format_user_response(user) + if email_verification_required: + response["email_verification_required"] = True + response["message"] = "Profile updated. Please verify your new email address. A verification link has been sent." + + return response def generate_reset_token(self) -> tuple: reset_token = secrets.token_hex(32) @@ -524,14 +657,50 @@ class AuthService: if self.verify_password(password, user.password): raise ValueError("New password must be different from the old password") - hashed_password = self.hash_password(password) + # Check password history to prevent reuse (if enabled) + hashed_new_password = self.hash_password(password) + if settings.PASSWORD_HISTORY_COUNT > 0: + # Get recent password history + recent_passwords = db.query(PasswordHistory).filter( + PasswordHistory.user_id == user.id + ).order_by(PasswordHistory.created_at.desc()).limit(settings.PASSWORD_HISTORY_COUNT).all() + + # Check if new password matches any recent password + for old_password in recent_passwords: + if self.verify_password(password, old_password.password_hash): + raise ValueError(f"Password cannot be reused. You must use a password that hasn't been used in your last {settings.PASSWORD_HISTORY_COUNT} password changes.") + + # Save current password to history before changing + if settings.PASSWORD_HISTORY_COUNT > 0: + password_history_entry = PasswordHistory( + user_id=user.id, + password_hash=user.password + ) + db.add(password_history_entry) + + # Clean up old password history (keep only the configured number) + old_passwords = db.query(PasswordHistory).filter( + PasswordHistory.user_id == user.id + ).order_by(PasswordHistory.created_at.desc()).offset(settings.PASSWORD_HISTORY_COUNT).all() + for old_pwd in old_passwords: + db.delete(old_pwd) - user.password = hashed_password + user.password = hashed_new_password + user.password_changed_at = datetime.utcnow() + + # SECURITY: Revoke all existing sessions and refresh tokens on password reset + # This prevents compromised sessions from remaining valid after password change + from ..models.refresh_token import RefreshToken + revoked_sessions = session_service.revoke_all_user_sessions(db, user.id) + revoked_tokens = db.query(RefreshToken).filter(RefreshToken.user_id == user.id).delete() + db.commit() reset_token.used = True db.commit() + logger.info(f"Password reset for user {user.id}: Revoked {revoked_sessions} sessions and {revoked_tokens} refresh tokens") + try: logger.info(f"Attempting to send password changed confirmation email to {user.email}") email_html = password_changed_email_template(user.email) @@ -546,7 +715,101 @@ class AuthService: return { "success": True, - "message": "Password has been reset successfully" + "message": "Password has been reset successfully. All existing sessions have been revoked for security." + } + + async def verify_email(self, db: Session, token: str) -> dict: + """Verify email address using verification token.""" + if not token: + raise ValueError("Verification token is required") + + hashed_token = hashlib.sha256(token.encode()).hexdigest() + + verification_token = db.query(EmailVerificationToken).filter( + EmailVerificationToken.token == hashed_token, + EmailVerificationToken.expires_at > datetime.utcnow(), + EmailVerificationToken.used == False + ).first() + + if not verification_token: + raise ValueError("Invalid or expired verification token") + + user = db.query(User).filter(User.id == verification_token.user_id).first() + if not user: + raise ValueError("User not found") + + # Update email if it was changed (verification token contains the new email) + if verification_token.email != user.email: + # Check if new email is already in use + existing_user = db.query(User).filter(User.email == verification_token.email).first() + if existing_user and existing_user.id != user.id: + raise ValueError("Email address is already in use") + user.email = verification_token.email + + # Mark email as verified + user.email_verified = True + verification_token.used = True + + # Delete all other verification tokens for this user + db.query(EmailVerificationToken).filter( + EmailVerificationToken.user_id == user.id, + EmailVerificationToken.id != verification_token.id + ).delete() + + db.commit() + + logger.info(f"Email verified for user {user.id}: {user.email}") + + return { + "success": True, + "message": "Email address verified successfully", + "email": user.email + } + + async def resend_verification_email(self, db: Session, user_id: int) -> dict: + """Resend email verification link.""" + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise ValueError("User not found") + + if user.email_verified: + raise ValueError("Email is already verified") + + # Delete old verification tokens + db.query(EmailVerificationToken).filter(EmailVerificationToken.user_id == user.id).delete() + + # Create new verification token + verification_token = secrets.token_hex(32) + hashed_verification_token = hashlib.sha256(verification_token.encode()).hexdigest() + expires_at = datetime.utcnow() + timedelta(hours=24) + + verification_token_obj = EmailVerificationToken( + user_id=user.id, + token=hashed_verification_token, + email=user.email, + expires_at=expires_at + ) + db.add(verification_token_obj) + db.commit() + + # Send verification email + try: + client_url = settings.CLIENT_URL or os.getenv("CLIENT_URL", "http://localhost:5173") + verification_url = f"{client_url}/verify-email/{verification_token}" + email_html = email_verification_template(verification_url, user.full_name) + await send_email( + to=user.email, + subject="Verify Your Email Address", + html=email_html + ) + logger.info(f"Verification email resent to {user.email}") + except Exception as e: + logger.error(f"Failed to resend verification email to {user.email}: {type(e).__name__}: {str(e)}", exc_info=True) + raise ValueError("Failed to send verification email. Please try again later.") + + return { + "success": True, + "message": "Verification email sent successfully" } auth_service = AuthService() diff --git a/Backend/src/auth/services/session_service.py b/Backend/src/auth/services/session_service.py index cda89bdf..31afba0f 100644 --- a/Backend/src/auth/services/session_service.py +++ b/Backend/src/auth/services/session_service.py @@ -1,9 +1,9 @@ """ User session management service. """ -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, noload from typing import Optional, List -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import secrets from ..models.user_session import UserSession from ...shared.config.settings import settings @@ -27,7 +27,7 @@ class SessionService: refresh_token = secrets.token_urlsafe(64) # Calculate expiration - expires_at = datetime.utcnow() + timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) + expires_at = datetime.now(timezone.utc) + timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) session = UserSession( user_id=user_id, @@ -52,7 +52,7 @@ class SessionService: session_token: str ) -> Optional[UserSession]: """Get a session by token.""" - return db.query(UserSession).filter( + return db.query(UserSession).options(noload(UserSession.user)).filter( UserSession.session_token == session_token, UserSession.is_active == True ).first() @@ -65,7 +65,7 @@ class SessionService: """Update session last activity timestamp.""" session = SessionService.get_session(db, session_token) if session and session.is_valid: - session.last_activity = datetime.utcnow() + session.last_activity = datetime.now(timezone.utc) db.commit() db.refresh(session) return session @@ -119,12 +119,12 @@ class SessionService: active_only: bool = True ) -> List[UserSession]: """Get all sessions for a user.""" - query = db.query(UserSession).filter(UserSession.user_id == user_id) + query = db.query(UserSession).options(noload(UserSession.user)).filter(UserSession.user_id == user_id) if active_only: query = query.filter( UserSession.is_active == True, - UserSession.expires_at > datetime.utcnow() + UserSession.expires_at > datetime.now(timezone.utc) ) return query.order_by(UserSession.last_activity.desc()).all() @@ -133,7 +133,7 @@ class SessionService: def cleanup_expired_sessions(db: Session) -> int: """Clean up expired sessions.""" expired = db.query(UserSession).filter( - UserSession.expires_at < datetime.utcnow(), + UserSession.expires_at < datetime.now(timezone.utc), UserSession.is_active == True ).all() diff --git a/Backend/src/bookings/__pycache__/__init__.cpython-312.pyc b/Backend/src/bookings/__pycache__/__init__.cpython-312.pyc index 7a64eda0..63542337 100644 Binary files a/Backend/src/bookings/__pycache__/__init__.cpython-312.pyc and b/Backend/src/bookings/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/bookings/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/bookings/models/__pycache__/__init__.cpython-312.pyc index 3cef58bd..f970fea6 100644 Binary files a/Backend/src/bookings/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/bookings/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/bookings/models/__pycache__/booking.cpython-312.pyc b/Backend/src/bookings/models/__pycache__/booking.cpython-312.pyc index 3492f990..70572ca2 100644 Binary files a/Backend/src/bookings/models/__pycache__/booking.cpython-312.pyc and b/Backend/src/bookings/models/__pycache__/booking.cpython-312.pyc differ diff --git a/Backend/src/bookings/models/__pycache__/checkin_checkout.cpython-312.pyc b/Backend/src/bookings/models/__pycache__/checkin_checkout.cpython-312.pyc index e592231d..3464404e 100644 Binary files a/Backend/src/bookings/models/__pycache__/checkin_checkout.cpython-312.pyc and b/Backend/src/bookings/models/__pycache__/checkin_checkout.cpython-312.pyc differ diff --git a/Backend/src/bookings/models/__pycache__/group_booking.cpython-312.pyc b/Backend/src/bookings/models/__pycache__/group_booking.cpython-312.pyc index 5fc4f96b..9e0bc6de 100644 Binary files a/Backend/src/bookings/models/__pycache__/group_booking.cpython-312.pyc and b/Backend/src/bookings/models/__pycache__/group_booking.cpython-312.pyc differ diff --git a/Backend/src/bookings/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/bookings/routes/__pycache__/__init__.cpython-312.pyc index c203a0e2..9474a8fa 100644 Binary files a/Backend/src/bookings/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/bookings/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/bookings/routes/__pycache__/booking_routes.cpython-312.pyc b/Backend/src/bookings/routes/__pycache__/booking_routes.cpython-312.pyc index da09494d..c6484928 100644 Binary files a/Backend/src/bookings/routes/__pycache__/booking_routes.cpython-312.pyc and b/Backend/src/bookings/routes/__pycache__/booking_routes.cpython-312.pyc differ diff --git a/Backend/src/bookings/routes/__pycache__/group_booking_routes.cpython-312.pyc b/Backend/src/bookings/routes/__pycache__/group_booking_routes.cpython-312.pyc index a7dc3927..2129db1e 100644 Binary files a/Backend/src/bookings/routes/__pycache__/group_booking_routes.cpython-312.pyc and b/Backend/src/bookings/routes/__pycache__/group_booking_routes.cpython-312.pyc differ diff --git a/Backend/src/bookings/routes/__pycache__/upsell_routes.cpython-312.pyc b/Backend/src/bookings/routes/__pycache__/upsell_routes.cpython-312.pyc index ebe41c94..042eaa90 100644 Binary files a/Backend/src/bookings/routes/__pycache__/upsell_routes.cpython-312.pyc and b/Backend/src/bookings/routes/__pycache__/upsell_routes.cpython-312.pyc differ diff --git a/Backend/src/bookings/routes/booking_routes.py b/Backend/src/bookings/routes/booking_routes.py index b31d5184..70b24c78 100644 --- a/Backend/src/bookings/routes/booking_routes.py +++ b/Backend/src/bookings/routes/booking_routes.py @@ -549,8 +549,9 @@ async def get_booking_by_id(id: int, request: Request, current_user: User=Depend booking = db.query(Booking).options(selectinload(Booking.payments), selectinload(Booking.service_usages).selectinload(ServiceUsage.service), joinedload(Booking.user), joinedload(Booking.room).joinedload(Room.room_type)).filter(Booking.id == id).first() if not booking: raise HTTPException(status_code=404, detail='Booking not found') - from ...shared.utils.role_helpers import is_admin - if not is_admin(current_user, db) and booking.user_id != current_user.id: + from ...shared.utils.role_helpers import can_manage_bookings + # Allow admin/staff to view any booking, customers can only view their own + if not can_manage_bookings(current_user, db) and booking.user_id != current_user.id: raise HTTPException(status_code=403, detail='Forbidden') import logging logger = logging.getLogger(__name__) diff --git a/Backend/src/bookings/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/bookings/schemas/__pycache__/__init__.cpython-312.pyc index c37c7b96..b3369d81 100644 Binary files a/Backend/src/bookings/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/bookings/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/bookings/schemas/__pycache__/admin_booking.cpython-312.pyc b/Backend/src/bookings/schemas/__pycache__/admin_booking.cpython-312.pyc index 323c197c..d84142d8 100644 Binary files a/Backend/src/bookings/schemas/__pycache__/admin_booking.cpython-312.pyc and b/Backend/src/bookings/schemas/__pycache__/admin_booking.cpython-312.pyc differ diff --git a/Backend/src/bookings/schemas/__pycache__/booking.cpython-312.pyc b/Backend/src/bookings/schemas/__pycache__/booking.cpython-312.pyc index 355324fb..5d64bf84 100644 Binary files a/Backend/src/bookings/schemas/__pycache__/booking.cpython-312.pyc and b/Backend/src/bookings/schemas/__pycache__/booking.cpython-312.pyc differ diff --git a/Backend/src/bookings/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/bookings/services/__pycache__/__init__.cpython-312.pyc index a09bd7b3..2f4d8035 100644 Binary files a/Backend/src/bookings/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/bookings/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/bookings/services/__pycache__/group_booking_service.cpython-312.pyc b/Backend/src/bookings/services/__pycache__/group_booking_service.cpython-312.pyc index 790145e4..e80ecf96 100644 Binary files a/Backend/src/bookings/services/__pycache__/group_booking_service.cpython-312.pyc and b/Backend/src/bookings/services/__pycache__/group_booking_service.cpython-312.pyc differ diff --git a/Backend/src/compliance/__pycache__/__init__.cpython-312.pyc b/Backend/src/compliance/__pycache__/__init__.cpython-312.pyc index 9e495488..2a319b99 100644 Binary files a/Backend/src/compliance/__pycache__/__init__.cpython-312.pyc and b/Backend/src/compliance/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/__init__.cpython-312.pyc index b4aa50cf..54397cf9 100644 Binary files a/Backend/src/compliance/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/consent.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/consent.cpython-312.pyc index 11c02ead..de7f4065 100644 Binary files a/Backend/src/compliance/models/__pycache__/consent.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/consent.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/data_breach.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/data_breach.cpython-312.pyc index b21f00db..72868ebe 100644 Binary files a/Backend/src/compliance/models/__pycache__/data_breach.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/data_breach.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/data_processing_record.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/data_processing_record.cpython-312.pyc index ee86fd7f..26b11774 100644 Binary files a/Backend/src/compliance/models/__pycache__/data_processing_record.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/data_processing_record.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/data_retention.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/data_retention.cpython-312.pyc index 675ccae6..1772455d 100644 Binary files a/Backend/src/compliance/models/__pycache__/data_retention.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/data_retention.cpython-312.pyc differ diff --git a/Backend/src/compliance/models/__pycache__/gdpr_request.cpython-312.pyc b/Backend/src/compliance/models/__pycache__/gdpr_request.cpython-312.pyc index 7164ec68..9c892ee7 100644 Binary files a/Backend/src/compliance/models/__pycache__/gdpr_request.cpython-312.pyc and b/Backend/src/compliance/models/__pycache__/gdpr_request.cpython-312.pyc differ diff --git a/Backend/src/compliance/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/compliance/routes/__pycache__/__init__.cpython-312.pyc index e891e0b4..fd195e1e 100644 Binary files a/Backend/src/compliance/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/compliance/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/compliance/routes/__pycache__/gdpr_admin_routes.cpython-312.pyc b/Backend/src/compliance/routes/__pycache__/gdpr_admin_routes.cpython-312.pyc index 3be0d1f9..798e1588 100644 Binary files a/Backend/src/compliance/routes/__pycache__/gdpr_admin_routes.cpython-312.pyc and b/Backend/src/compliance/routes/__pycache__/gdpr_admin_routes.cpython-312.pyc differ diff --git a/Backend/src/compliance/routes/__pycache__/gdpr_routes.cpython-312.pyc b/Backend/src/compliance/routes/__pycache__/gdpr_routes.cpython-312.pyc index 879c0526..59a74da0 100644 Binary files a/Backend/src/compliance/routes/__pycache__/gdpr_routes.cpython-312.pyc and b/Backend/src/compliance/routes/__pycache__/gdpr_routes.cpython-312.pyc differ diff --git a/Backend/src/compliance/services/__pycache__/breach_service.cpython-312.pyc b/Backend/src/compliance/services/__pycache__/breach_service.cpython-312.pyc index 1e5ea226..ed9f7813 100644 Binary files a/Backend/src/compliance/services/__pycache__/breach_service.cpython-312.pyc and b/Backend/src/compliance/services/__pycache__/breach_service.cpython-312.pyc differ diff --git a/Backend/src/compliance/services/__pycache__/consent_service.cpython-312.pyc b/Backend/src/compliance/services/__pycache__/consent_service.cpython-312.pyc index 94a022fc..4bd2dc21 100644 Binary files a/Backend/src/compliance/services/__pycache__/consent_service.cpython-312.pyc and b/Backend/src/compliance/services/__pycache__/consent_service.cpython-312.pyc differ diff --git a/Backend/src/compliance/services/__pycache__/data_processing_service.cpython-312.pyc b/Backend/src/compliance/services/__pycache__/data_processing_service.cpython-312.pyc index 1ded8ca0..67df3812 100644 Binary files a/Backend/src/compliance/services/__pycache__/data_processing_service.cpython-312.pyc and b/Backend/src/compliance/services/__pycache__/data_processing_service.cpython-312.pyc differ diff --git a/Backend/src/compliance/services/__pycache__/gdpr_service.cpython-312.pyc b/Backend/src/compliance/services/__pycache__/gdpr_service.cpython-312.pyc index 806cce88..8eda84e1 100644 Binary files a/Backend/src/compliance/services/__pycache__/gdpr_service.cpython-312.pyc and b/Backend/src/compliance/services/__pycache__/gdpr_service.cpython-312.pyc differ diff --git a/Backend/src/compliance/services/__pycache__/retention_service.cpython-312.pyc b/Backend/src/compliance/services/__pycache__/retention_service.cpython-312.pyc index ef989832..cd3ee083 100644 Binary files a/Backend/src/compliance/services/__pycache__/retention_service.cpython-312.pyc and b/Backend/src/compliance/services/__pycache__/retention_service.cpython-312.pyc differ diff --git a/Backend/src/content/__pycache__/__init__.cpython-312.pyc b/Backend/src/content/__pycache__/__init__.cpython-312.pyc index 897554ea..16867f69 100644 Binary files a/Backend/src/content/__pycache__/__init__.cpython-312.pyc and b/Backend/src/content/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/content/models/__pycache__/__init__.cpython-312.pyc index e850999f..fd5394da 100644 Binary files a/Backend/src/content/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/content/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/banner.cpython-312.pyc b/Backend/src/content/models/__pycache__/banner.cpython-312.pyc index bb7b6a3d..eb53e765 100644 Binary files a/Backend/src/content/models/__pycache__/banner.cpython-312.pyc and b/Backend/src/content/models/__pycache__/banner.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/blog.cpython-312.pyc b/Backend/src/content/models/__pycache__/blog.cpython-312.pyc index 3819401e..11d7e2b7 100644 Binary files a/Backend/src/content/models/__pycache__/blog.cpython-312.pyc and b/Backend/src/content/models/__pycache__/blog.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/cookie_integration_config.cpython-312.pyc b/Backend/src/content/models/__pycache__/cookie_integration_config.cpython-312.pyc index f09cce18..eaa9060a 100644 Binary files a/Backend/src/content/models/__pycache__/cookie_integration_config.cpython-312.pyc and b/Backend/src/content/models/__pycache__/cookie_integration_config.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/cookie_policy.cpython-312.pyc b/Backend/src/content/models/__pycache__/cookie_policy.cpython-312.pyc index 0c3250ab..936470f0 100644 Binary files a/Backend/src/content/models/__pycache__/cookie_policy.cpython-312.pyc and b/Backend/src/content/models/__pycache__/cookie_policy.cpython-312.pyc differ diff --git a/Backend/src/content/models/__pycache__/page_content.cpython-312.pyc b/Backend/src/content/models/__pycache__/page_content.cpython-312.pyc index d6b11905..f682eba8 100644 Binary files a/Backend/src/content/models/__pycache__/page_content.cpython-312.pyc and b/Backend/src/content/models/__pycache__/page_content.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/content/routes/__pycache__/__init__.cpython-312.pyc index dda24567..db43bb5c 100644 Binary files a/Backend/src/content/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/about_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/about_routes.cpython-312.pyc index e444aa69..22507116 100644 Binary files a/Backend/src/content/routes/__pycache__/about_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/about_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/accessibility_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/accessibility_routes.cpython-312.pyc index 0a4fba71..fcb9bc3d 100644 Binary files a/Backend/src/content/routes/__pycache__/accessibility_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/accessibility_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/admin_privacy_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/admin_privacy_routes.cpython-312.pyc index e64893be..f1269c67 100644 Binary files a/Backend/src/content/routes/__pycache__/admin_privacy_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/admin_privacy_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/banner_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/banner_routes.cpython-312.pyc index 9e22c9be..be77e1b6 100644 Binary files a/Backend/src/content/routes/__pycache__/banner_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/banner_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/blog_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/blog_routes.cpython-312.pyc index e1ca137d..fc556006 100644 Binary files a/Backend/src/content/routes/__pycache__/blog_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/blog_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/cancellation_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/cancellation_routes.cpython-312.pyc index 2d339f70..67715db4 100644 Binary files a/Backend/src/content/routes/__pycache__/cancellation_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/cancellation_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/contact_content_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/contact_content_routes.cpython-312.pyc index 859c77d2..375a4196 100644 Binary files a/Backend/src/content/routes/__pycache__/contact_content_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/contact_content_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/contact_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/contact_routes.cpython-312.pyc index 563a58a6..cc4e9a4f 100644 Binary files a/Backend/src/content/routes/__pycache__/contact_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/contact_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/faq_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/faq_routes.cpython-312.pyc index 187b17b6..721b1796 100644 Binary files a/Backend/src/content/routes/__pycache__/faq_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/faq_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/footer_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/footer_routes.cpython-312.pyc index 05ba952b..91fabe7f 100644 Binary files a/Backend/src/content/routes/__pycache__/footer_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/footer_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/home_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/home_routes.cpython-312.pyc index 28d5bd2c..2f87bfd3 100644 Binary files a/Backend/src/content/routes/__pycache__/home_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/home_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/page_content_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/page_content_routes.cpython-312.pyc index 820ac405..e53693f6 100644 Binary files a/Backend/src/content/routes/__pycache__/page_content_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/page_content_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/privacy_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/privacy_routes.cpython-312.pyc index f81c6ac9..128c0176 100644 Binary files a/Backend/src/content/routes/__pycache__/privacy_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/privacy_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/refunds_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/refunds_routes.cpython-312.pyc index 11890cf3..069db114 100644 Binary files a/Backend/src/content/routes/__pycache__/refunds_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/refunds_routes.cpython-312.pyc differ diff --git a/Backend/src/content/routes/__pycache__/terms_routes.cpython-312.pyc b/Backend/src/content/routes/__pycache__/terms_routes.cpython-312.pyc index 345ebbff..71a606fd 100644 Binary files a/Backend/src/content/routes/__pycache__/terms_routes.cpython-312.pyc and b/Backend/src/content/routes/__pycache__/terms_routes.cpython-312.pyc differ diff --git a/Backend/src/content/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/content/schemas/__pycache__/__init__.cpython-312.pyc index 7255efda..700ed089 100644 Binary files a/Backend/src/content/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/content/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/content/schemas/__pycache__/admin_privacy.cpython-312.pyc b/Backend/src/content/schemas/__pycache__/admin_privacy.cpython-312.pyc index 63eb6dcb..2d7a63ab 100644 Binary files a/Backend/src/content/schemas/__pycache__/admin_privacy.cpython-312.pyc and b/Backend/src/content/schemas/__pycache__/admin_privacy.cpython-312.pyc differ diff --git a/Backend/src/content/schemas/__pycache__/blog.cpython-312.pyc b/Backend/src/content/schemas/__pycache__/blog.cpython-312.pyc index 3f6926ef..c15e9703 100644 Binary files a/Backend/src/content/schemas/__pycache__/blog.cpython-312.pyc and b/Backend/src/content/schemas/__pycache__/blog.cpython-312.pyc differ diff --git a/Backend/src/content/schemas/__pycache__/page_content.cpython-312.pyc b/Backend/src/content/schemas/__pycache__/page_content.cpython-312.pyc index ac941a19..43dacdd5 100644 Binary files a/Backend/src/content/schemas/__pycache__/page_content.cpython-312.pyc and b/Backend/src/content/schemas/__pycache__/page_content.cpython-312.pyc differ diff --git a/Backend/src/content/schemas/__pycache__/privacy.cpython-312.pyc b/Backend/src/content/schemas/__pycache__/privacy.cpython-312.pyc index 4f951dbc..dcf8e4db 100644 Binary files a/Backend/src/content/schemas/__pycache__/privacy.cpython-312.pyc and b/Backend/src/content/schemas/__pycache__/privacy.cpython-312.pyc differ diff --git a/Backend/src/guest_management/__pycache__/__init__.cpython-312.pyc b/Backend/src/guest_management/__pycache__/__init__.cpython-312.pyc index 2ed39734..bf5f3acd 100644 Binary files a/Backend/src/guest_management/__pycache__/__init__.cpython-312.pyc and b/Backend/src/guest_management/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/__init__.cpython-312.pyc index d76ca8c0..710cf279 100644 Binary files a/Backend/src/guest_management/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_communication.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_communication.cpython-312.pyc index a9d43166..8b0d8cfa 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_communication.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_communication.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_complaint.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_complaint.cpython-312.pyc index a4444237..f3f49017 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_complaint.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_complaint.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_note.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_note.cpython-312.pyc index fc463d5b..7240649d 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_note.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_note.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_preference.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_preference.cpython-312.pyc index 56de9772..03ef8587 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_preference.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_preference.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_segment.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_segment.cpython-312.pyc index 816f553f..a3de53fd 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_segment.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_segment.cpython-312.pyc differ diff --git a/Backend/src/guest_management/models/__pycache__/guest_tag.cpython-312.pyc b/Backend/src/guest_management/models/__pycache__/guest_tag.cpython-312.pyc index 0ed4ea5b..00bbbbed 100644 Binary files a/Backend/src/guest_management/models/__pycache__/guest_tag.cpython-312.pyc and b/Backend/src/guest_management/models/__pycache__/guest_tag.cpython-312.pyc differ diff --git a/Backend/src/guest_management/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/guest_management/routes/__pycache__/__init__.cpython-312.pyc index 12f2b3f7..3c3f1ff8 100644 Binary files a/Backend/src/guest_management/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/guest_management/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/guest_management/routes/__pycache__/complaint_routes.cpython-312.pyc b/Backend/src/guest_management/routes/__pycache__/complaint_routes.cpython-312.pyc index 2a4db580..6e4a7391 100644 Binary files a/Backend/src/guest_management/routes/__pycache__/complaint_routes.cpython-312.pyc and b/Backend/src/guest_management/routes/__pycache__/complaint_routes.cpython-312.pyc differ diff --git a/Backend/src/guest_management/routes/__pycache__/guest_profile_routes.cpython-312.pyc b/Backend/src/guest_management/routes/__pycache__/guest_profile_routes.cpython-312.pyc index 5a7354dc..8f5acc28 100644 Binary files a/Backend/src/guest_management/routes/__pycache__/guest_profile_routes.cpython-312.pyc and b/Backend/src/guest_management/routes/__pycache__/guest_profile_routes.cpython-312.pyc differ diff --git a/Backend/src/guest_management/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/guest_management/schemas/__pycache__/__init__.cpython-312.pyc index ca27c4e3..c548b20d 100644 Binary files a/Backend/src/guest_management/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/guest_management/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/guest_management/schemas/__pycache__/complaint.cpython-312.pyc b/Backend/src/guest_management/schemas/__pycache__/complaint.cpython-312.pyc index e5647029..498e63ab 100644 Binary files a/Backend/src/guest_management/schemas/__pycache__/complaint.cpython-312.pyc and b/Backend/src/guest_management/schemas/__pycache__/complaint.cpython-312.pyc differ diff --git a/Backend/src/guest_management/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/guest_management/services/__pycache__/__init__.cpython-312.pyc index 7a9e891f..7fa86b58 100644 Binary files a/Backend/src/guest_management/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/guest_management/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/guest_management/services/__pycache__/guest_profile_service.cpython-312.pyc b/Backend/src/guest_management/services/__pycache__/guest_profile_service.cpython-312.pyc index c320d87a..edbcd4f3 100644 Binary files a/Backend/src/guest_management/services/__pycache__/guest_profile_service.cpython-312.pyc and b/Backend/src/guest_management/services/__pycache__/guest_profile_service.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/__pycache__/__init__.cpython-312.pyc b/Backend/src/hotel_services/__pycache__/__init__.cpython-312.pyc index 5ff37bd9..0bc77b95 100644 Binary files a/Backend/src/hotel_services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/hotel_services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/__init__.cpython-312.pyc index 425a2e9a..c0d61e56 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/guest_request.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/guest_request.cpython-312.pyc index 92f3fc2c..44449236 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/guest_request.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/guest_request.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/housekeeping_task.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/housekeeping_task.cpython-312.pyc index 05b8f233..3da370e0 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/housekeeping_task.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/housekeeping_task.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/inventory_item.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/inventory_item.cpython-312.pyc index d5fe91a5..f59132aa 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/inventory_item.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/inventory_item.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/inventory_reorder_request.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/inventory_reorder_request.cpython-312.pyc index bc4bbed6..048a7bc6 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/inventory_reorder_request.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/inventory_reorder_request.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/inventory_task_consumption.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/inventory_task_consumption.cpython-312.pyc index a00b25ab..892d9ca0 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/inventory_task_consumption.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/inventory_task_consumption.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/inventory_transaction.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/inventory_transaction.cpython-312.pyc index 3495eb74..bb450466 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/inventory_transaction.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/inventory_transaction.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/service.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/service.cpython-312.pyc index 1a6121f7..311cce36 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/service.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/service.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/service_booking.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/service_booking.cpython-312.pyc index 51a7e714..2dc1bdfc 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/service_booking.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/service_booking.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/service_usage.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/service_usage.cpython-312.pyc index 6e39411b..c03c1ea2 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/service_usage.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/service_usage.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/models/__pycache__/staff_shift.cpython-312.pyc b/Backend/src/hotel_services/models/__pycache__/staff_shift.cpython-312.pyc index 83821bdf..90a3de92 100644 Binary files a/Backend/src/hotel_services/models/__pycache__/staff_shift.cpython-312.pyc and b/Backend/src/hotel_services/models/__pycache__/staff_shift.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/__init__.cpython-312.pyc index 35009593..6903f844 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/guest_request_routes.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/guest_request_routes.cpython-312.pyc index a99a8339..ec0e1d12 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/guest_request_routes.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/guest_request_routes.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/inventory_routes.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/inventory_routes.cpython-312.pyc index 32ad694e..a129092e 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/inventory_routes.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/inventory_routes.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/service_booking_routes.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/service_booking_routes.cpython-312.pyc index b1ebfc24..45bdcb4e 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/service_booking_routes.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/service_booking_routes.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/service_routes.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/service_routes.cpython-312.pyc index bf7f9ca8..3698439e 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/service_routes.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/service_routes.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/routes/__pycache__/staff_shift_routes.cpython-312.pyc b/Backend/src/hotel_services/routes/__pycache__/staff_shift_routes.cpython-312.pyc index fac73582..6308753d 100644 Binary files a/Backend/src/hotel_services/routes/__pycache__/staff_shift_routes.cpython-312.pyc and b/Backend/src/hotel_services/routes/__pycache__/staff_shift_routes.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/hotel_services/schemas/__pycache__/__init__.cpython-312.pyc index ea3f7387..078c4597 100644 Binary files a/Backend/src/hotel_services/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/hotel_services/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/schemas/__pycache__/service_booking.cpython-312.pyc b/Backend/src/hotel_services/schemas/__pycache__/service_booking.cpython-312.pyc index d717c24c..631d263f 100644 Binary files a/Backend/src/hotel_services/schemas/__pycache__/service_booking.cpython-312.pyc and b/Backend/src/hotel_services/schemas/__pycache__/service_booking.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/hotel_services/services/__pycache__/__init__.cpython-312.pyc index b638c0c2..0409831a 100644 Binary files a/Backend/src/hotel_services/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/hotel_services/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/hotel_services/services/__pycache__/task_service.cpython-312.pyc b/Backend/src/hotel_services/services/__pycache__/task_service.cpython-312.pyc index 132b7c05..64820312 100644 Binary files a/Backend/src/hotel_services/services/__pycache__/task_service.cpython-312.pyc and b/Backend/src/hotel_services/services/__pycache__/task_service.cpython-312.pyc differ diff --git a/Backend/src/integrations/__pycache__/__init__.cpython-312.pyc b/Backend/src/integrations/__pycache__/__init__.cpython-312.pyc index 428681ec..59aa1195 100644 Binary files a/Backend/src/integrations/__pycache__/__init__.cpython-312.pyc and b/Backend/src/integrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/integrations/models/__pycache__/api_key.cpython-312.pyc b/Backend/src/integrations/models/__pycache__/api_key.cpython-312.pyc index f2b7e32a..9efa12db 100644 Binary files a/Backend/src/integrations/models/__pycache__/api_key.cpython-312.pyc and b/Backend/src/integrations/models/__pycache__/api_key.cpython-312.pyc differ diff --git a/Backend/src/integrations/models/__pycache__/webhook.cpython-312.pyc b/Backend/src/integrations/models/__pycache__/webhook.cpython-312.pyc index af6446d1..3843d3e9 100644 Binary files a/Backend/src/integrations/models/__pycache__/webhook.cpython-312.pyc and b/Backend/src/integrations/models/__pycache__/webhook.cpython-312.pyc differ diff --git a/Backend/src/integrations/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/integrations/routes/__pycache__/__init__.cpython-312.pyc index d547dd0b..5186b38e 100644 Binary files a/Backend/src/integrations/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/integrations/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/integrations/routes/__pycache__/api_key_routes.cpython-312.pyc b/Backend/src/integrations/routes/__pycache__/api_key_routes.cpython-312.pyc index 714e21f0..09a389d2 100644 Binary files a/Backend/src/integrations/routes/__pycache__/api_key_routes.cpython-312.pyc and b/Backend/src/integrations/routes/__pycache__/api_key_routes.cpython-312.pyc differ diff --git a/Backend/src/integrations/routes/__pycache__/webhook_routes.cpython-312.pyc b/Backend/src/integrations/routes/__pycache__/webhook_routes.cpython-312.pyc index ecbe715e..31f5a438 100644 Binary files a/Backend/src/integrations/routes/__pycache__/webhook_routes.cpython-312.pyc and b/Backend/src/integrations/routes/__pycache__/webhook_routes.cpython-312.pyc differ diff --git a/Backend/src/integrations/services/__pycache__/api_key_service.cpython-312.pyc b/Backend/src/integrations/services/__pycache__/api_key_service.cpython-312.pyc index aa8e3c26..eac63bc5 100644 Binary files a/Backend/src/integrations/services/__pycache__/api_key_service.cpython-312.pyc and b/Backend/src/integrations/services/__pycache__/api_key_service.cpython-312.pyc differ diff --git a/Backend/src/integrations/services/__pycache__/webhook_service.cpython-312.pyc b/Backend/src/integrations/services/__pycache__/webhook_service.cpython-312.pyc index 7e281af3..89253973 100644 Binary files a/Backend/src/integrations/services/__pycache__/webhook_service.cpython-312.pyc and b/Backend/src/integrations/services/__pycache__/webhook_service.cpython-312.pyc differ diff --git a/Backend/src/loyalty/__pycache__/__init__.cpython-312.pyc b/Backend/src/loyalty/__pycache__/__init__.cpython-312.pyc index ba4eab9c..c593269a 100644 Binary files a/Backend/src/loyalty/__pycache__/__init__.cpython-312.pyc and b/Backend/src/loyalty/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/__init__.cpython-312.pyc index 056da99d..c667b43e 100644 Binary files a/Backend/src/loyalty/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/loyalty_point_transaction.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/loyalty_point_transaction.cpython-312.pyc index 6a34b301..1f812dce 100644 Binary files a/Backend/src/loyalty/models/__pycache__/loyalty_point_transaction.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/loyalty_point_transaction.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/loyalty_reward.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/loyalty_reward.cpython-312.pyc index 1870f29a..56790848 100644 Binary files a/Backend/src/loyalty/models/__pycache__/loyalty_reward.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/loyalty_reward.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/loyalty_tier.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/loyalty_tier.cpython-312.pyc index 1dcc6afe..c05b6106 100644 Binary files a/Backend/src/loyalty/models/__pycache__/loyalty_tier.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/loyalty_tier.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/package.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/package.cpython-312.pyc index 4a507db1..eb8af0ac 100644 Binary files a/Backend/src/loyalty/models/__pycache__/package.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/package.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/promotion.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/promotion.cpython-312.pyc index ac43f597..e3a3ed2a 100644 Binary files a/Backend/src/loyalty/models/__pycache__/promotion.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/promotion.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/referral.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/referral.cpython-312.pyc index eaf636f3..827cc96e 100644 Binary files a/Backend/src/loyalty/models/__pycache__/referral.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/referral.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/reward_redemption.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/reward_redemption.cpython-312.pyc index 626950bf..8fc1cd1f 100644 Binary files a/Backend/src/loyalty/models/__pycache__/reward_redemption.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/reward_redemption.cpython-312.pyc differ diff --git a/Backend/src/loyalty/models/__pycache__/user_loyalty.cpython-312.pyc b/Backend/src/loyalty/models/__pycache__/user_loyalty.cpython-312.pyc index 88438bd9..dd21c493 100644 Binary files a/Backend/src/loyalty/models/__pycache__/user_loyalty.cpython-312.pyc and b/Backend/src/loyalty/models/__pycache__/user_loyalty.cpython-312.pyc differ diff --git a/Backend/src/loyalty/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/loyalty/routes/__pycache__/__init__.cpython-312.pyc index 9330ba51..eae6177e 100644 Binary files a/Backend/src/loyalty/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/loyalty/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/loyalty/routes/__pycache__/loyalty_routes.cpython-312.pyc b/Backend/src/loyalty/routes/__pycache__/loyalty_routes.cpython-312.pyc index 4df75e39..80f382cb 100644 Binary files a/Backend/src/loyalty/routes/__pycache__/loyalty_routes.cpython-312.pyc and b/Backend/src/loyalty/routes/__pycache__/loyalty_routes.cpython-312.pyc differ diff --git a/Backend/src/loyalty/routes/__pycache__/package_routes.cpython-312.pyc b/Backend/src/loyalty/routes/__pycache__/package_routes.cpython-312.pyc index d23536a8..5253b124 100644 Binary files a/Backend/src/loyalty/routes/__pycache__/package_routes.cpython-312.pyc and b/Backend/src/loyalty/routes/__pycache__/package_routes.cpython-312.pyc differ diff --git a/Backend/src/loyalty/routes/__pycache__/promotion_routes.cpython-312.pyc b/Backend/src/loyalty/routes/__pycache__/promotion_routes.cpython-312.pyc index 14412a7a..8387370f 100644 Binary files a/Backend/src/loyalty/routes/__pycache__/promotion_routes.cpython-312.pyc and b/Backend/src/loyalty/routes/__pycache__/promotion_routes.cpython-312.pyc differ diff --git a/Backend/src/loyalty/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/loyalty/schemas/__pycache__/__init__.cpython-312.pyc index cb20a0ca..accad83d 100644 Binary files a/Backend/src/loyalty/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/loyalty/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/loyalty/schemas/__pycache__/promotion.cpython-312.pyc b/Backend/src/loyalty/schemas/__pycache__/promotion.cpython-312.pyc index e160df48..88ff61ce 100644 Binary files a/Backend/src/loyalty/schemas/__pycache__/promotion.cpython-312.pyc and b/Backend/src/loyalty/schemas/__pycache__/promotion.cpython-312.pyc differ diff --git a/Backend/src/loyalty/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/loyalty/services/__pycache__/__init__.cpython-312.pyc index bceb968b..16e2015d 100644 Binary files a/Backend/src/loyalty/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/loyalty/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/loyalty/services/__pycache__/loyalty_service.cpython-312.pyc b/Backend/src/loyalty/services/__pycache__/loyalty_service.cpython-312.pyc index 7dedbdf6..2a6b2a61 100644 Binary files a/Backend/src/loyalty/services/__pycache__/loyalty_service.cpython-312.pyc and b/Backend/src/loyalty/services/__pycache__/loyalty_service.cpython-312.pyc differ diff --git a/Backend/src/main.py b/Backend/src/main.py index 3132864a..af99f453 100644 --- a/Backend/src/main.py +++ b/Backend/src/main.py @@ -304,7 +304,7 @@ from .reviews.routes import review_routes, favorite_routes from .loyalty.routes import promotion_routes, loyalty_routes, package_routes from .guest_management.routes import guest_profile_routes from .guest_management.routes.complaint_routes import router as complaint_routes -from .notifications.routes import chat_routes, notification_routes, email_campaign_routes +from .notifications.routes import chat_routes, notification_routes, email_campaign_routes, team_chat_routes from .analytics.routes import analytics_routes, report_routes, audit_routes from .security.routes import security_routes, compliance_routes from .system.routes import system_settings_routes, workflow_routes, task_routes, approval_routes, backup_routes @@ -353,6 +353,7 @@ app.include_router(cancellation_routes.router, prefix=api_prefix) app.include_router(accessibility_routes.router, prefix=api_prefix) app.include_router(faq_routes.router, prefix=api_prefix) app.include_router(chat_routes.router, prefix=api_prefix) +app.include_router(team_chat_routes.router, prefix=api_prefix) app.include_router(loyalty_routes.router, prefix=api_prefix) app.include_router(guest_profile_routes.router, prefix=api_prefix) app.include_router(complaint_routes, prefix=api_prefix) diff --git a/Backend/src/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/models/__pycache__/__init__.cpython-312.pyc index 7555c912..a15242bc 100644 Binary files a/Backend/src/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/notifications/__pycache__/__init__.cpython-312.pyc b/Backend/src/notifications/__pycache__/__init__.cpython-312.pyc index 3e3cf66b..efe089e8 100644 Binary files a/Backend/src/notifications/__pycache__/__init__.cpython-312.pyc and b/Backend/src/notifications/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/notifications/models/__init__.py b/Backend/src/notifications/models/__init__.py index e69de29b..c237c30e 100644 --- a/Backend/src/notifications/models/__init__.py +++ b/Backend/src/notifications/models/__init__.py @@ -0,0 +1,24 @@ +from .notification import Notification +from .email_campaign import Campaign as EmailCampaign +from .team_chat import ( + TeamChannel, + TeamMessage, + TeamMessageReadReceipt, + UserPresence, + ChannelType, + MessagePriority, + team_channel_members +) + +__all__ = [ + 'Notification', + 'EmailCampaign', + 'TeamChannel', + 'TeamMessage', + 'TeamMessageReadReceipt', + 'UserPresence', + 'ChannelType', + 'MessagePriority', + 'team_channel_members' +] + diff --git a/Backend/src/notifications/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/notifications/models/__pycache__/__init__.cpython-312.pyc index b3198eee..c12826a1 100644 Binary files a/Backend/src/notifications/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/notifications/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/notifications/models/__pycache__/email_campaign.cpython-312.pyc b/Backend/src/notifications/models/__pycache__/email_campaign.cpython-312.pyc index c630a475..c72afa92 100644 Binary files a/Backend/src/notifications/models/__pycache__/email_campaign.cpython-312.pyc and b/Backend/src/notifications/models/__pycache__/email_campaign.cpython-312.pyc differ diff --git a/Backend/src/notifications/models/__pycache__/notification.cpython-312.pyc b/Backend/src/notifications/models/__pycache__/notification.cpython-312.pyc index 806278da..3e05570e 100644 Binary files a/Backend/src/notifications/models/__pycache__/notification.cpython-312.pyc and b/Backend/src/notifications/models/__pycache__/notification.cpython-312.pyc differ diff --git a/Backend/src/notifications/models/__pycache__/team_chat.cpython-312.pyc b/Backend/src/notifications/models/__pycache__/team_chat.cpython-312.pyc new file mode 100644 index 00000000..8d65c18e Binary files /dev/null and b/Backend/src/notifications/models/__pycache__/team_chat.cpython-312.pyc differ diff --git a/Backend/src/notifications/models/team_chat.py b/Backend/src/notifications/models/team_chat.py new file mode 100644 index 00000000..13f23e83 --- /dev/null +++ b/Backend/src/notifications/models/team_chat.py @@ -0,0 +1,159 @@ +""" +Internal Team Chat Models for enterprise communication between staff, admin, and housekeeping. +""" +from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Enum, Boolean, Table +from sqlalchemy.orm import relationship +from datetime import datetime +import enum +from ...shared.config.database import Base + + +class ChannelType(str, enum.Enum): + """Types of chat channels""" + direct = 'direct' # 1-on-1 direct message + group = 'group' # Custom group chat + department = 'department' # Department-wide channel (housekeeping, front-desk, etc.) + announcement = 'announcement' # Admin announcements (read-only for non-admins) + + +class MessagePriority(str, enum.Enum): + """Message priority levels""" + normal = 'normal' + high = 'high' + urgent = 'urgent' + + +# Many-to-many relationship table for channel members +team_channel_members = Table( + 'team_channel_members', + Base.metadata, + Column('channel_id', Integer, ForeignKey('team_channels.id'), primary_key=True), + Column('user_id', Integer, ForeignKey('users.id'), primary_key=True), + Column('joined_at', DateTime, default=datetime.utcnow, nullable=False), + Column('is_muted', Boolean, default=False, nullable=False), + Column('last_read_at', DateTime, nullable=True) +) + + +class TeamChannel(Base): + """ + Team communication channels for internal messaging. + Supports direct messages, group chats, department channels, and announcements. + """ + __tablename__ = 'team_channels' + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + name = Column(String(100), nullable=True) # Optional for direct messages + description = Column(Text, nullable=True) + channel_type = Column(Enum(ChannelType), nullable=False, default=ChannelType.group) + + # For department channels + department = Column(String(50), nullable=True) # 'housekeeping', 'front-desk', 'management', etc. + + # Creator/owner + created_by = Column(Integer, ForeignKey('users.id'), nullable=False) + + # Channel settings + is_active = Column(Boolean, default=True, nullable=False) + is_private = Column(Boolean, default=False, nullable=False) # Private channels require invitation + + # Timestamps + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) + last_message_at = Column(DateTime, nullable=True) + + # Relationships + creator = relationship('User', foreign_keys=[created_by]) + members = relationship('User', secondary=team_channel_members, backref='team_channels') + messages = relationship('TeamMessage', back_populates='channel', cascade='all, delete-orphan', + order_by='TeamMessage.created_at') + + def __repr__(self): + return f"" + + +class TeamMessage(Base): + """ + Messages within team channels. + """ + __tablename__ = 'team_messages' + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + channel_id = Column(Integer, ForeignKey('team_channels.id'), nullable=False, index=True) + sender_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True) + + # Message content + content = Column(Text, nullable=False) + priority = Column(Enum(MessagePriority), default=MessagePriority.normal, nullable=False) + + # For replies/threads + reply_to_id = Column(Integer, ForeignKey('team_messages.id'), nullable=True) + + # Message metadata + is_edited = Column(Boolean, default=False, nullable=False) + edited_at = Column(DateTime, nullable=True) + is_deleted = Column(Boolean, default=False, nullable=False) + deleted_at = Column(DateTime, nullable=True) + + # For task/booking references + reference_type = Column(String(50), nullable=True) # 'booking', 'task', 'room', etc. + reference_id = Column(Integer, nullable=True) + + # Timestamps + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + + # Relationships + channel = relationship('TeamChannel', back_populates='messages') + sender = relationship('User', foreign_keys=[sender_id]) + reply_to = relationship('TeamMessage', remote_side=[id], backref='replies') + read_receipts = relationship('TeamMessageReadReceipt', back_populates='message', cascade='all, delete-orphan') + + def __repr__(self): + return f"" + + +class TeamMessageReadReceipt(Base): + """ + Track which users have read which messages. + """ + __tablename__ = 'team_message_read_receipts' + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + message_id = Column(Integer, ForeignKey('team_messages.id'), nullable=False, index=True) + user_id = Column(Integer, ForeignKey('users.id'), nullable=False, index=True) + read_at = Column(DateTime, default=datetime.utcnow, nullable=False) + + # Relationships + message = relationship('TeamMessage', back_populates='read_receipts') + user = relationship('User') + + def __repr__(self): + return f"" + + +class UserPresence(Base): + """ + Track user online/offline status for real-time features. + """ + __tablename__ = 'user_presence' + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + user_id = Column(Integer, ForeignKey('users.id'), unique=True, nullable=False, index=True) + + # Status + status = Column(String(20), default='offline', nullable=False) # online, away, busy, offline + custom_status = Column(String(100), nullable=True) # Custom status message + + # Activity tracking + last_seen_at = Column(DateTime, default=datetime.utcnow, nullable=False) + last_active_at = Column(DateTime, default=datetime.utcnow, nullable=False) + + # Device info + device_type = Column(String(50), nullable=True) # web, mobile, desktop + + # Relationships + user = relationship('User', backref='presence') + + def __repr__(self): + return f"" + diff --git a/Backend/src/notifications/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/notifications/routes/__pycache__/__init__.cpython-312.pyc index c42d0550..68d76c95 100644 Binary files a/Backend/src/notifications/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/notifications/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/notifications/routes/__pycache__/chat_routes.cpython-312.pyc b/Backend/src/notifications/routes/__pycache__/chat_routes.cpython-312.pyc index c4d8e4fd..ec20bb58 100644 Binary files a/Backend/src/notifications/routes/__pycache__/chat_routes.cpython-312.pyc and b/Backend/src/notifications/routes/__pycache__/chat_routes.cpython-312.pyc differ diff --git a/Backend/src/notifications/routes/__pycache__/email_campaign_routes.cpython-312.pyc b/Backend/src/notifications/routes/__pycache__/email_campaign_routes.cpython-312.pyc index 7c6e0a3b..2c7c8e82 100644 Binary files a/Backend/src/notifications/routes/__pycache__/email_campaign_routes.cpython-312.pyc and b/Backend/src/notifications/routes/__pycache__/email_campaign_routes.cpython-312.pyc differ diff --git a/Backend/src/notifications/routes/__pycache__/notification_routes.cpython-312.pyc b/Backend/src/notifications/routes/__pycache__/notification_routes.cpython-312.pyc index ba0d9248..c8cf2753 100644 Binary files a/Backend/src/notifications/routes/__pycache__/notification_routes.cpython-312.pyc and b/Backend/src/notifications/routes/__pycache__/notification_routes.cpython-312.pyc differ diff --git a/Backend/src/notifications/routes/__pycache__/team_chat_routes.cpython-312.pyc b/Backend/src/notifications/routes/__pycache__/team_chat_routes.cpython-312.pyc new file mode 100644 index 00000000..bcc6f5c6 Binary files /dev/null and b/Backend/src/notifications/routes/__pycache__/team_chat_routes.cpython-312.pyc differ diff --git a/Backend/src/notifications/routes/team_chat_routes.py b/Backend/src/notifications/routes/team_chat_routes.py new file mode 100644 index 00000000..97ef9763 --- /dev/null +++ b/Backend/src/notifications/routes/team_chat_routes.py @@ -0,0 +1,857 @@ +""" +Internal Team Chat Routes for enterprise communication. +Enables messaging between admin, staff, and housekeeping users. +""" +from fastapi import APIRouter, Depends, HTTPException, status, WebSocket, WebSocketDisconnect, Query +from sqlalchemy.orm import Session, joinedload +from sqlalchemy import and_, or_, desc, func +from typing import List, Optional +from datetime import datetime +from pydantic import BaseModel, Field +import json + +from ...shared.config.database import get_db +from ...shared.config.logging_config import get_logger +from ...security.middleware.auth import get_current_user, authorize_roles +from ...auth.models.user import User +from ...auth.models.role import Role +from ..models.team_chat import ( + TeamChannel, TeamMessage, TeamMessageReadReceipt, UserPresence, + ChannelType, MessagePriority, team_channel_members +) + +logger = get_logger(__name__) +router = APIRouter(prefix='/team-chat', tags=['team-chat']) + + +# ============== Pydantic Schemas ============== + +class ChannelCreate(BaseModel): + name: Optional[str] = None + description: Optional[str] = None + channel_type: ChannelType = ChannelType.group + department: Optional[str] = None + is_private: bool = False + member_ids: List[int] = Field(default_factory=list) + + +class ChannelUpdate(BaseModel): + name: Optional[str] = None + description: Optional[str] = None + is_private: Optional[bool] = None + + +class MessageCreate(BaseModel): + content: str = Field(..., min_length=1, max_length=5000) + priority: MessagePriority = MessagePriority.normal + reply_to_id: Optional[int] = None + reference_type: Optional[str] = None + reference_id: Optional[int] = None + + +class MessageUpdate(BaseModel): + content: str = Field(..., min_length=1, max_length=5000) + + +class DirectMessageCreate(BaseModel): + recipient_id: int + content: str = Field(..., min_length=1, max_length=5000) + priority: MessagePriority = MessagePriority.normal + + +class PresenceUpdate(BaseModel): + status: str = Field(..., pattern='^(online|away|busy|offline)$') + custom_status: Optional[str] = Field(None, max_length=100) + + +# ============== WebSocket Manager ============== + +class TeamChatManager: + """Manages WebSocket connections for team chat.""" + + def __init__(self): + # Map of channel_id -> list of (user_id, websocket) + self.channel_connections: dict[int, list[tuple[int, WebSocket]]] = {} + # Map of user_id -> websocket for direct notifications + self.user_connections: dict[int, WebSocket] = {} + + async def connect(self, websocket: WebSocket, user_id: int, channel_id: Optional[int] = None): + await websocket.accept() + self.user_connections[user_id] = websocket + if channel_id: + if channel_id not in self.channel_connections: + self.channel_connections[channel_id] = [] + self.channel_connections[channel_id].append((user_id, websocket)) + + def disconnect(self, user_id: int, channel_id: Optional[int] = None): + if user_id in self.user_connections: + del self.user_connections[user_id] + if channel_id and channel_id in self.channel_connections: + self.channel_connections[channel_id] = [ + (uid, ws) for uid, ws in self.channel_connections[channel_id] + if uid != user_id + ] + + async def broadcast_to_channel(self, channel_id: int, message: dict, exclude_user_id: Optional[int] = None): + """Send message to all users in a channel.""" + if channel_id not in self.channel_connections: + return + + disconnected = [] + for user_id, websocket in self.channel_connections[channel_id]: + if exclude_user_id and user_id == exclude_user_id: + continue + try: + await websocket.send_json(message) + except Exception as e: + logger.error(f"Error sending to user {user_id}: {e}") + disconnected.append(user_id) + + # Clean up disconnected + for uid in disconnected: + self.disconnect(uid, channel_id) + + async def send_to_user(self, user_id: int, message: dict): + """Send message directly to a user.""" + if user_id in self.user_connections: + try: + await self.user_connections[user_id].send_json(message) + except Exception as e: + logger.error(f"Error sending to user {user_id}: {e}") + self.disconnect(user_id) + + async def broadcast_to_users(self, user_ids: List[int], message: dict): + """Send message to multiple users.""" + for user_id in user_ids: + await self.send_to_user(user_id, message) + + +manager = TeamChatManager() + + +# ============== Helper Functions ============== + +def get_or_create_direct_channel(db: Session, user1_id: int, user2_id: int) -> TeamChannel: + """Get or create a direct message channel between two users.""" + # Look for existing direct channel + existing = db.query(TeamChannel).filter( + TeamChannel.channel_type == ChannelType.direct, + TeamChannel.members.any(User.id == user1_id), + TeamChannel.members.any(User.id == user2_id) + ).first() + + if existing: + return existing + + # Create new direct channel + user1 = db.query(User).get(user1_id) + user2 = db.query(User).get(user2_id) + + if not user1 or not user2: + raise HTTPException(status_code=404, detail="User not found") + + channel = TeamChannel( + channel_type=ChannelType.direct, + created_by=user1_id, + is_private=True + ) + channel.members = [user1, user2] + db.add(channel) + db.commit() + db.refresh(channel) + return channel + + +def serialize_channel(channel: TeamChannel, current_user_id: int, unread_count: int = 0) -> dict: + """Serialize channel for API response.""" + other_members = [m for m in channel.members if m.id != current_user_id] + + return { + "id": channel.id, + "name": channel.name if channel.channel_type != ChannelType.direct else ( + other_members[0].full_name if other_members else "Direct Message" + ), + "description": channel.description, + "channel_type": channel.channel_type.value, + "department": channel.department, + "is_private": channel.is_private, + "is_active": channel.is_active, + "created_at": channel.created_at.isoformat(), + "last_message_at": channel.last_message_at.isoformat() if channel.last_message_at else None, + "unread_count": unread_count, + "members": [ + { + "id": m.id, + "full_name": m.full_name, + "email": m.email, + "avatar_url": m.avatar_url, + "role": m.role.name if m.role else None + } + for m in channel.members + ], + "last_message": serialize_message(channel.messages[-1]) if channel.messages else None + } + + +def serialize_message(message: TeamMessage) -> dict: + """Serialize message for API response.""" + return { + "id": message.id, + "channel_id": message.channel_id, + "sender_id": message.sender_id, + "sender": { + "id": message.sender.id, + "full_name": message.sender.full_name, + "avatar_url": message.sender.avatar_url, + "role": message.sender.role.name if message.sender.role else None + } if message.sender else None, + "content": message.content if not message.is_deleted else "[Message deleted]", + "priority": message.priority.value, + "reply_to_id": message.reply_to_id, + "reference_type": message.reference_type, + "reference_id": message.reference_id, + "is_edited": message.is_edited, + "is_deleted": message.is_deleted, + "created_at": message.created_at.isoformat(), + "edited_at": message.edited_at.isoformat() if message.edited_at else None + } + + +# ============== Channel Endpoints ============== + +@router.get('/channels') +async def get_my_channels( + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Get all channels the current user is a member of.""" + channels = db.query(TeamChannel).options( + joinedload(TeamChannel.members), + joinedload(TeamChannel.messages).joinedload(TeamMessage.sender) + ).filter( + TeamChannel.is_active == True, + TeamChannel.members.any(User.id == current_user.id) + ).order_by(desc(TeamChannel.last_message_at)).all() + + result = [] + for channel in channels: + # Count unread messages + last_read = db.execute( + team_channel_members.select().where( + and_( + team_channel_members.c.channel_id == channel.id, + team_channel_members.c.user_id == current_user.id + ) + ) + ).first() + + unread_count = 0 + if last_read and last_read.last_read_at: + unread_count = db.query(func.count(TeamMessage.id)).filter( + TeamMessage.channel_id == channel.id, + TeamMessage.created_at > last_read.last_read_at, + TeamMessage.sender_id != current_user.id, + TeamMessage.is_deleted == False + ).scalar() + else: + unread_count = db.query(func.count(TeamMessage.id)).filter( + TeamMessage.channel_id == channel.id, + TeamMessage.sender_id != current_user.id, + TeamMessage.is_deleted == False + ).scalar() + + result.append(serialize_channel(channel, current_user.id, unread_count)) + + return {"success": True, "data": result} + + +@router.post('/channels') +async def create_channel( + channel_data: ChannelCreate, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Create a new channel.""" + # Only admins can create department or announcement channels + if channel_data.channel_type in [ChannelType.department, ChannelType.announcement]: + if current_user.role.name != 'admin': + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Only admins can create department or announcement channels" + ) + + channel = TeamChannel( + name=channel_data.name, + description=channel_data.description, + channel_type=channel_data.channel_type, + department=channel_data.department, + is_private=channel_data.is_private, + created_by=current_user.id + ) + + # Add creator as member + channel.members.append(current_user) + + # Add other members + if channel_data.member_ids: + members = db.query(User).filter(User.id.in_(channel_data.member_ids)).all() + for member in members: + if member not in channel.members: + channel.members.append(member) + + db.add(channel) + db.commit() + db.refresh(channel) + + logger.info(f"Channel created: {channel.id} by user {current_user.id}") + + return {"success": True, "data": serialize_channel(channel, current_user.id)} + + +@router.get('/channels/{channel_id}') +async def get_channel( + channel_id: int, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Get channel details.""" + channel = db.query(TeamChannel).options( + joinedload(TeamChannel.members) + ).filter( + TeamChannel.id == channel_id, + TeamChannel.is_active == True + ).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found") + + # Check membership + if current_user not in channel.members and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Not a member of this channel") + + return {"success": True, "data": serialize_channel(channel, current_user.id)} + + +@router.put('/channels/{channel_id}') +async def update_channel( + channel_id: int, + channel_data: ChannelUpdate, + current_user: User = Depends(authorize_roles('admin', 'staff')), + db: Session = Depends(get_db) +): + """Update channel settings.""" + channel = db.query(TeamChannel).filter(TeamChannel.id == channel_id).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found") + + # Only creator or admin can update + if channel.created_by != current_user.id and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Not authorized to update this channel") + + if channel_data.name is not None: + channel.name = channel_data.name + if channel_data.description is not None: + channel.description = channel_data.description + if channel_data.is_private is not None: + channel.is_private = channel_data.is_private + + db.commit() + db.refresh(channel) + + return {"success": True, "data": serialize_channel(channel, current_user.id)} + + +@router.post('/channels/{channel_id}/members') +async def add_channel_members( + channel_id: int, + member_ids: List[int], + current_user: User = Depends(authorize_roles('admin', 'staff')), + db: Session = Depends(get_db) +): + """Add members to a channel.""" + channel = db.query(TeamChannel).options( + joinedload(TeamChannel.members) + ).filter(TeamChannel.id == channel_id).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found") + + # Check authorization + if channel.created_by != current_user.id and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Not authorized") + + new_members = db.query(User).filter(User.id.in_(member_ids)).all() + for member in new_members: + if member not in channel.members: + channel.members.append(member) + + db.commit() + + return {"success": True, "message": f"Added {len(new_members)} members"} + + +@router.delete('/channels/{channel_id}/members/{user_id}') +async def remove_channel_member( + channel_id: int, + user_id: int, + current_user: User = Depends(authorize_roles('admin', 'staff')), + db: Session = Depends(get_db) +): + """Remove a member from a channel.""" + channel = db.query(TeamChannel).options( + joinedload(TeamChannel.members) + ).filter(TeamChannel.id == channel_id).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found") + + # Check authorization + if channel.created_by != current_user.id and current_user.role.name != 'admin': + if user_id != current_user.id: # Users can leave themselves + raise HTTPException(status_code=403, detail="Not authorized") + + member_to_remove = next((m for m in channel.members if m.id == user_id), None) + if member_to_remove: + channel.members.remove(member_to_remove) + db.commit() + + return {"success": True, "message": "Member removed"} + + +# ============== Message Endpoints ============== + +@router.get('/channels/{channel_id}/messages') +async def get_channel_messages( + channel_id: int, + limit: int = Query(50, ge=1, le=100), + before_id: Optional[int] = None, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Get messages from a channel.""" + channel = db.query(TeamChannel).filter( + TeamChannel.id == channel_id, + TeamChannel.members.any(User.id == current_user.id) + ).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found or not a member") + + query = db.query(TeamMessage).options( + joinedload(TeamMessage.sender) + ).filter(TeamMessage.channel_id == channel_id) + + if before_id: + query = query.filter(TeamMessage.id < before_id) + + messages = query.order_by(desc(TeamMessage.created_at)).limit(limit).all() + + # Mark as read + db.execute( + team_channel_members.update().where( + and_( + team_channel_members.c.channel_id == channel_id, + team_channel_members.c.user_id == current_user.id + ) + ).values(last_read_at=datetime.utcnow()) + ) + db.commit() + + return { + "success": True, + "data": [serialize_message(m) for m in reversed(messages)] + } + + +@router.post('/channels/{channel_id}/messages') +async def send_message( + channel_id: int, + message_data: MessageCreate, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Send a message to a channel.""" + channel = db.query(TeamChannel).options( + joinedload(TeamChannel.members) + ).filter( + TeamChannel.id == channel_id, + TeamChannel.members.any(User.id == current_user.id) + ).first() + + if not channel: + raise HTTPException(status_code=404, detail="Channel not found or not a member") + + # Check if announcement channel (only admins can post) + if channel.channel_type == ChannelType.announcement and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Only admins can post in announcement channels") + + message = TeamMessage( + channel_id=channel_id, + sender_id=current_user.id, + content=message_data.content, + priority=message_data.priority, + reply_to_id=message_data.reply_to_id, + reference_type=message_data.reference_type, + reference_id=message_data.reference_id + ) + + db.add(message) + channel.last_message_at = datetime.utcnow() + db.commit() + db.refresh(message) + + # Broadcast to channel members via WebSocket + serialized = serialize_message(message) + await manager.broadcast_to_channel(channel_id, { + "type": "new_message", + "data": serialized + }, exclude_user_id=current_user.id) + + # Also notify users not connected to channel + member_ids = [m.id for m in channel.members if m.id != current_user.id] + await manager.broadcast_to_users(member_ids, { + "type": "new_message_notification", + "data": { + "channel_id": channel_id, + "channel_name": channel.name, + "message": serialized + } + }) + + return {"success": True, "data": serialized} + + +@router.put('/messages/{message_id}') +async def edit_message( + message_id: int, + message_data: MessageUpdate, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Edit a message.""" + message = db.query(TeamMessage).filter(TeamMessage.id == message_id).first() + + if not message: + raise HTTPException(status_code=404, detail="Message not found") + + if message.sender_id != current_user.id and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Not authorized to edit this message") + + message.content = message_data.content + message.is_edited = True + message.edited_at = datetime.utcnow() + + db.commit() + db.refresh(message) + + # Broadcast update + await manager.broadcast_to_channel(message.channel_id, { + "type": "message_edited", + "data": serialize_message(message) + }) + + return {"success": True, "data": serialize_message(message)} + + +@router.delete('/messages/{message_id}') +async def delete_message( + message_id: int, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Delete a message (soft delete).""" + message = db.query(TeamMessage).filter(TeamMessage.id == message_id).first() + + if not message: + raise HTTPException(status_code=404, detail="Message not found") + + if message.sender_id != current_user.id and current_user.role.name != 'admin': + raise HTTPException(status_code=403, detail="Not authorized to delete this message") + + message.is_deleted = True + message.deleted_at = datetime.utcnow() + + db.commit() + + # Broadcast deletion + await manager.broadcast_to_channel(message.channel_id, { + "type": "message_deleted", + "data": {"id": message_id, "channel_id": message.channel_id} + }) + + return {"success": True, "message": "Message deleted"} + + +# ============== Direct Message Endpoints ============== + +@router.post('/direct') +async def send_direct_message( + dm_data: DirectMessageCreate, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Send a direct message to another user.""" + # Verify recipient exists and is staff/admin/housekeeping + recipient = db.query(User).options( + joinedload(User.role) + ).filter(User.id == dm_data.recipient_id).first() + + if not recipient: + raise HTTPException(status_code=404, detail="Recipient not found") + + if recipient.role.name not in ['admin', 'staff', 'housekeeping']: + raise HTTPException(status_code=400, detail="Can only message staff members") + + # Get or create direct channel + channel = get_or_create_direct_channel(db, current_user.id, dm_data.recipient_id) + + # Send message + message = TeamMessage( + channel_id=channel.id, + sender_id=current_user.id, + content=dm_data.content, + priority=dm_data.priority + ) + + db.add(message) + channel.last_message_at = datetime.utcnow() + db.commit() + db.refresh(message) + + serialized = serialize_message(message) + + # Notify recipient + await manager.send_to_user(dm_data.recipient_id, { + "type": "new_direct_message", + "data": { + "channel_id": channel.id, + "sender": { + "id": current_user.id, + "full_name": current_user.full_name, + "avatar_url": current_user.avatar_url + }, + "message": serialized + } + }) + + return {"success": True, "data": {"channel_id": channel.id, "message": serialized}} + + +# ============== User/Presence Endpoints ============== + +@router.get('/users') +async def get_team_users( + role: Optional[str] = None, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Get all team users (admin, staff, housekeeping) for messaging.""" + query = db.query(User).options( + joinedload(User.role), + joinedload(User.presence) + ).join(Role).filter( + Role.name.in_(['admin', 'staff', 'housekeeping']), + User.is_active == True, + User.id != current_user.id + ) + + if role: + query = query.filter(Role.name == role) + + users = query.order_by(User.full_name).all() + + return { + "success": True, + "data": [ + { + "id": u.id, + "full_name": u.full_name, + "email": u.email, + "avatar_url": u.avatar_url, + "role": u.role.name if u.role else None, + "status": u.presence.status if u.presence else 'offline', + "last_seen": u.presence.last_seen_at.isoformat() if u.presence else None + } + for u in users + ] + } + + +@router.put('/presence') +async def update_presence( + presence_data: PresenceUpdate, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Update user presence status.""" + presence = db.query(UserPresence).filter(UserPresence.user_id == current_user.id).first() + + if not presence: + presence = UserPresence(user_id=current_user.id) + db.add(presence) + + presence.status = presence_data.status + presence.custom_status = presence_data.custom_status + presence.last_seen_at = datetime.utcnow() + presence.last_active_at = datetime.utcnow() + + db.commit() + + return {"success": True, "message": "Presence updated"} + + +# ============== Department Channels ============== + +@router.post('/departments/init') +async def initialize_department_channels( + current_user: User = Depends(authorize_roles('admin')), + db: Session = Depends(get_db) +): + """Initialize default department channels (admin only).""" + departments = [ + {"name": "Housekeeping Team", "department": "housekeeping", "description": "Channel for housekeeping coordination"}, + {"name": "Front Desk", "department": "front-desk", "description": "Front desk staff communication"}, + {"name": "Management", "department": "management", "description": "Management team discussions"}, + {"name": "Announcements", "department": "all", "description": "Hotel-wide announcements"} + ] + + created = [] + for dept in departments: + existing = db.query(TeamChannel).filter( + TeamChannel.department == dept["department"], + TeamChannel.channel_type == ChannelType.department + ).first() + + if not existing: + channel = TeamChannel( + name=dept["name"], + description=dept["description"], + channel_type=ChannelType.department if dept["department"] != "all" else ChannelType.announcement, + department=dept["department"], + created_by=current_user.id, + is_private=False + ) + db.add(channel) + created.append(dept["name"]) + + db.commit() + + return {"success": True, "message": f"Created channels: {', '.join(created)}" if created else "All channels already exist"} + + +@router.post('/departments/{department}/join') +async def join_department_channel( + department: str, + current_user: User = Depends(authorize_roles('admin', 'staff', 'housekeeping')), + db: Session = Depends(get_db) +): + """Join a department channel.""" + channel = db.query(TeamChannel).options( + joinedload(TeamChannel.members) + ).filter( + TeamChannel.department == department, + TeamChannel.channel_type.in_([ChannelType.department, ChannelType.announcement]) + ).first() + + if not channel: + raise HTTPException(status_code=404, detail="Department channel not found") + + if current_user not in channel.members: + channel.members.append(current_user) + db.commit() + + return {"success": True, "data": serialize_channel(channel, current_user.id)} + + +# ============== WebSocket Endpoint ============== + +@router.websocket('/ws') +async def websocket_endpoint( + websocket: WebSocket, + db: Session = Depends(get_db) +): + """WebSocket endpoint for real-time team chat.""" + user_id = None + try: + await websocket.accept() + + # Wait for authentication message + auth_data = await websocket.receive_json() + if auth_data.get("type") != "auth": + await websocket.close(code=4001, reason="Authentication required") + return + + # In production, verify JWT token here + user_id = auth_data.get("user_id") + if not user_id: + await websocket.close(code=4001, reason="Invalid authentication") + return + + # Verify user exists and has correct role + user = db.query(User).options(joinedload(User.role)).filter(User.id == user_id).first() + if not user or user.role.name not in ['admin', 'staff', 'housekeeping']: + await websocket.close(code=4003, reason="Unauthorized") + return + + # Update presence + presence = db.query(UserPresence).filter(UserPresence.user_id == user_id).first() + if not presence: + presence = UserPresence(user_id=user_id, status='online') + db.add(presence) + else: + presence.status = 'online' + presence.last_seen_at = datetime.utcnow() + db.commit() + + # Store connection + manager.user_connections[user_id] = websocket + + await websocket.send_json({"type": "connected", "user_id": user_id}) + + # Listen for messages + while True: + data = await websocket.receive_json() + msg_type = data.get("type") + + if msg_type == "join_channel": + channel_id = data.get("channel_id") + if channel_id not in manager.channel_connections: + manager.channel_connections[channel_id] = [] + manager.channel_connections[channel_id].append((user_id, websocket)) + await websocket.send_json({"type": "joined_channel", "channel_id": channel_id}) + + elif msg_type == "leave_channel": + channel_id = data.get("channel_id") + manager.disconnect(user_id, channel_id) + await websocket.send_json({"type": "left_channel", "channel_id": channel_id}) + + elif msg_type == "typing": + channel_id = data.get("channel_id") + await manager.broadcast_to_channel(channel_id, { + "type": "user_typing", + "data": {"user_id": user_id, "channel_id": channel_id} + }, exclude_user_id=user_id) + + elif msg_type == "ping": + await websocket.send_json({"type": "pong"}) + # Update presence + presence = db.query(UserPresence).filter(UserPresence.user_id == user_id).first() + if presence: + presence.last_active_at = datetime.utcnow() + db.commit() + + except WebSocketDisconnect: + logger.info(f"User {user_id} disconnected from team chat") + except Exception as e: + logger.error(f"WebSocket error: {e}") + finally: + if user_id: + manager.disconnect(user_id) + # Update presence to offline + presence = db.query(UserPresence).filter(UserPresence.user_id == user_id).first() + if presence: + presence.status = 'offline' + presence.last_seen_at = datetime.utcnow() + db.commit() + diff --git a/Backend/src/notifications/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/notifications/services/__pycache__/__init__.cpython-312.pyc index ad2ab781..857b918d 100644 Binary files a/Backend/src/notifications/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/notifications/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/notifications/services/__pycache__/email_campaign_service.cpython-312.pyc b/Backend/src/notifications/services/__pycache__/email_campaign_service.cpython-312.pyc index f4582c0b..fc03af49 100644 Binary files a/Backend/src/notifications/services/__pycache__/email_campaign_service.cpython-312.pyc and b/Backend/src/notifications/services/__pycache__/email_campaign_service.cpython-312.pyc differ diff --git a/Backend/src/notifications/services/__pycache__/notification_service.cpython-312.pyc b/Backend/src/notifications/services/__pycache__/notification_service.cpython-312.pyc index f1ce042f..5cd0ffc3 100644 Binary files a/Backend/src/notifications/services/__pycache__/notification_service.cpython-312.pyc and b/Backend/src/notifications/services/__pycache__/notification_service.cpython-312.pyc differ diff --git a/Backend/src/payments/__pycache__/__init__.cpython-312.pyc b/Backend/src/payments/__pycache__/__init__.cpython-312.pyc index b93d10e4..0bbff8ff 100644 Binary files a/Backend/src/payments/__pycache__/__init__.cpython-312.pyc and b/Backend/src/payments/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/payments/models/__pycache__/__init__.cpython-312.pyc index 7184086e..91027db0 100644 Binary files a/Backend/src/payments/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/accountant_session.cpython-312.pyc b/Backend/src/payments/models/__pycache__/accountant_session.cpython-312.pyc index 4cacae50..b5887f5c 100644 Binary files a/Backend/src/payments/models/__pycache__/accountant_session.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/accountant_session.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/chart_of_accounts.cpython-312.pyc b/Backend/src/payments/models/__pycache__/chart_of_accounts.cpython-312.pyc index 8badf297..784fd0bb 100644 Binary files a/Backend/src/payments/models/__pycache__/chart_of_accounts.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/chart_of_accounts.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/financial_approval.cpython-312.pyc b/Backend/src/payments/models/__pycache__/financial_approval.cpython-312.pyc index 72164690..1d94df36 100644 Binary files a/Backend/src/payments/models/__pycache__/financial_approval.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/financial_approval.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/financial_audit_trail.cpython-312.pyc b/Backend/src/payments/models/__pycache__/financial_audit_trail.cpython-312.pyc index da17d16a..bdc12a58 100644 Binary files a/Backend/src/payments/models/__pycache__/financial_audit_trail.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/financial_audit_trail.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/fiscal_period.cpython-312.pyc b/Backend/src/payments/models/__pycache__/fiscal_period.cpython-312.pyc index f1363ced..b084c8d8 100644 Binary files a/Backend/src/payments/models/__pycache__/fiscal_period.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/fiscal_period.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/invoice.cpython-312.pyc b/Backend/src/payments/models/__pycache__/invoice.cpython-312.pyc index ff06df3e..c17a2e87 100644 Binary files a/Backend/src/payments/models/__pycache__/invoice.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/invoice.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/journal_entry.cpython-312.pyc b/Backend/src/payments/models/__pycache__/journal_entry.cpython-312.pyc index c37bb68c..768a5d6d 100644 Binary files a/Backend/src/payments/models/__pycache__/journal_entry.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/journal_entry.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/payment.cpython-312.pyc b/Backend/src/payments/models/__pycache__/payment.cpython-312.pyc index 29da6ff2..898ec896 100644 Binary files a/Backend/src/payments/models/__pycache__/payment.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/payment.cpython-312.pyc differ diff --git a/Backend/src/payments/models/__pycache__/reconciliation_exception.cpython-312.pyc b/Backend/src/payments/models/__pycache__/reconciliation_exception.cpython-312.pyc index 4b7440f5..e33e106a 100644 Binary files a/Backend/src/payments/models/__pycache__/reconciliation_exception.cpython-312.pyc and b/Backend/src/payments/models/__pycache__/reconciliation_exception.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/__init__.cpython-312.pyc index c0332186..42486647 100644 Binary files a/Backend/src/payments/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/accountant_security_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/accountant_security_routes.cpython-312.pyc index df825680..e27141bd 100644 Binary files a/Backend/src/payments/routes/__pycache__/accountant_security_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/accountant_security_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/approval_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/approval_routes.cpython-312.pyc index 4ea69c23..df516460 100644 Binary files a/Backend/src/payments/routes/__pycache__/approval_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/approval_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/audit_trail_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/audit_trail_routes.cpython-312.pyc index 495abc10..7d670356 100644 Binary files a/Backend/src/payments/routes/__pycache__/audit_trail_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/audit_trail_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/financial_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/financial_routes.cpython-312.pyc index d67e744b..4d70eb5a 100644 Binary files a/Backend/src/payments/routes/__pycache__/financial_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/financial_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/gl_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/gl_routes.cpython-312.pyc index 5befd714..10c86410 100644 Binary files a/Backend/src/payments/routes/__pycache__/gl_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/gl_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/invoice_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/invoice_routes.cpython-312.pyc index 42f9777b..c6bf8c58 100644 Binary files a/Backend/src/payments/routes/__pycache__/invoice_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/invoice_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/payment_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/payment_routes.cpython-312.pyc index 2010991c..0cf102af 100644 Binary files a/Backend/src/payments/routes/__pycache__/payment_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/payment_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/__pycache__/reconciliation_routes.cpython-312.pyc b/Backend/src/payments/routes/__pycache__/reconciliation_routes.cpython-312.pyc index 8d834036..1118222e 100644 Binary files a/Backend/src/payments/routes/__pycache__/reconciliation_routes.cpython-312.pyc and b/Backend/src/payments/routes/__pycache__/reconciliation_routes.cpython-312.pyc differ diff --git a/Backend/src/payments/routes/invoice_routes.py b/Backend/src/payments/routes/invoice_routes.py index 2a27a8a2..181367b7 100644 --- a/Backend/src/payments/routes/invoice_routes.py +++ b/Backend/src/payments/routes/invoice_routes.py @@ -15,6 +15,7 @@ from ...shared.utils.response_helpers import success_response from ...shared.utils.request_helpers import get_request_id from ..services.financial_audit_service import financial_audit_service from ..models.financial_audit_trail import FinancialActionType +from ...analytics.services.audit_service import audit_service from ..schemas.invoice import ( CreateInvoiceRequest, UpdateInvoiceRequest, diff --git a/Backend/src/payments/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/payments/schemas/__pycache__/__init__.cpython-312.pyc index d9ad208f..ea3c2405 100644 Binary files a/Backend/src/payments/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/payments/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/payments/schemas/__pycache__/invoice.cpython-312.pyc b/Backend/src/payments/schemas/__pycache__/invoice.cpython-312.pyc index 6522444e..798b0085 100644 Binary files a/Backend/src/payments/schemas/__pycache__/invoice.cpython-312.pyc and b/Backend/src/payments/schemas/__pycache__/invoice.cpython-312.pyc differ diff --git a/Backend/src/payments/schemas/__pycache__/payment.cpython-312.pyc b/Backend/src/payments/schemas/__pycache__/payment.cpython-312.pyc index 027be558..2a6f15d0 100644 Binary files a/Backend/src/payments/schemas/__pycache__/payment.cpython-312.pyc and b/Backend/src/payments/schemas/__pycache__/payment.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/payments/services/__pycache__/__init__.cpython-312.pyc index e4adb5ce..6156ef21 100644 Binary files a/Backend/src/payments/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/accountant_security_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/accountant_security_service.cpython-312.pyc index 02916289..4fc18af6 100644 Binary files a/Backend/src/payments/services/__pycache__/accountant_security_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/accountant_security_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/approval_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/approval_service.cpython-312.pyc index c3105668..95ae79f7 100644 Binary files a/Backend/src/payments/services/__pycache__/approval_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/approval_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/borica_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/borica_service.cpython-312.pyc index 6a758d4e..7882102f 100644 Binary files a/Backend/src/payments/services/__pycache__/borica_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/borica_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/financial_audit_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/financial_audit_service.cpython-312.pyc index 012dfe6c..46da3dc2 100644 Binary files a/Backend/src/payments/services/__pycache__/financial_audit_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/financial_audit_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/gl_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/gl_service.cpython-312.pyc index 0a2ad716..5165c7ff 100644 Binary files a/Backend/src/payments/services/__pycache__/gl_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/gl_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/invoice_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/invoice_service.cpython-312.pyc index 4b948d44..b538847f 100644 Binary files a/Backend/src/payments/services/__pycache__/invoice_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/invoice_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/paypal_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/paypal_service.cpython-312.pyc index 77d2f938..5a1f8366 100644 Binary files a/Backend/src/payments/services/__pycache__/paypal_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/paypal_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/reconciliation_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/reconciliation_service.cpython-312.pyc index 7a30bd92..289e709e 100644 Binary files a/Backend/src/payments/services/__pycache__/reconciliation_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/reconciliation_service.cpython-312.pyc differ diff --git a/Backend/src/payments/services/__pycache__/stripe_service.cpython-312.pyc b/Backend/src/payments/services/__pycache__/stripe_service.cpython-312.pyc index 72273307..1432001c 100644 Binary files a/Backend/src/payments/services/__pycache__/stripe_service.cpython-312.pyc and b/Backend/src/payments/services/__pycache__/stripe_service.cpython-312.pyc differ diff --git a/Backend/src/reviews/__pycache__/__init__.cpython-312.pyc b/Backend/src/reviews/__pycache__/__init__.cpython-312.pyc index 8fa33815..18b87e96 100644 Binary files a/Backend/src/reviews/__pycache__/__init__.cpython-312.pyc and b/Backend/src/reviews/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/reviews/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/reviews/models/__pycache__/__init__.cpython-312.pyc index 4ad77b12..52202424 100644 Binary files a/Backend/src/reviews/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/reviews/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/reviews/models/__pycache__/favorite.cpython-312.pyc b/Backend/src/reviews/models/__pycache__/favorite.cpython-312.pyc index e218dbde..22a12f8b 100644 Binary files a/Backend/src/reviews/models/__pycache__/favorite.cpython-312.pyc and b/Backend/src/reviews/models/__pycache__/favorite.cpython-312.pyc differ diff --git a/Backend/src/reviews/models/__pycache__/review.cpython-312.pyc b/Backend/src/reviews/models/__pycache__/review.cpython-312.pyc index 77402580..7cf14071 100644 Binary files a/Backend/src/reviews/models/__pycache__/review.cpython-312.pyc and b/Backend/src/reviews/models/__pycache__/review.cpython-312.pyc differ diff --git a/Backend/src/reviews/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/reviews/routes/__pycache__/__init__.cpython-312.pyc index 3b2ae01b..7bc03c77 100644 Binary files a/Backend/src/reviews/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/reviews/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/reviews/routes/__pycache__/favorite_routes.cpython-312.pyc b/Backend/src/reviews/routes/__pycache__/favorite_routes.cpython-312.pyc index 4d4706a8..e9151ae0 100644 Binary files a/Backend/src/reviews/routes/__pycache__/favorite_routes.cpython-312.pyc and b/Backend/src/reviews/routes/__pycache__/favorite_routes.cpython-312.pyc differ diff --git a/Backend/src/reviews/routes/__pycache__/review_routes.cpython-312.pyc b/Backend/src/reviews/routes/__pycache__/review_routes.cpython-312.pyc index 67fe5357..76e93d5f 100644 Binary files a/Backend/src/reviews/routes/__pycache__/review_routes.cpython-312.pyc and b/Backend/src/reviews/routes/__pycache__/review_routes.cpython-312.pyc differ diff --git a/Backend/src/reviews/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/reviews/schemas/__pycache__/__init__.cpython-312.pyc index 9dcbbe5c..e895e6fc 100644 Binary files a/Backend/src/reviews/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/reviews/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/reviews/schemas/__pycache__/review.cpython-312.pyc b/Backend/src/reviews/schemas/__pycache__/review.cpython-312.pyc index 620f7b67..fa2856ba 100644 Binary files a/Backend/src/reviews/schemas/__pycache__/review.cpython-312.pyc and b/Backend/src/reviews/schemas/__pycache__/review.cpython-312.pyc differ diff --git a/Backend/src/rooms/__pycache__/__init__.cpython-312.pyc b/Backend/src/rooms/__pycache__/__init__.cpython-312.pyc index 705966d3..c9d71fce 100644 Binary files a/Backend/src/rooms/__pycache__/__init__.cpython-312.pyc and b/Backend/src/rooms/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/__init__.cpython-312.pyc index cadfed9e..2689690b 100644 Binary files a/Backend/src/rooms/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/rate_plan.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/rate_plan.cpython-312.pyc index d1d813c0..3ab2983e 100644 Binary files a/Backend/src/rooms/models/__pycache__/rate_plan.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/rate_plan.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/room.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/room.cpython-312.pyc index 6fbf789f..69adb46c 100644 Binary files a/Backend/src/rooms/models/__pycache__/room.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/room.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/room_attribute.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/room_attribute.cpython-312.pyc index 96582ddc..4299011e 100644 Binary files a/Backend/src/rooms/models/__pycache__/room_attribute.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/room_attribute.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/room_inspection.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/room_inspection.cpython-312.pyc index 1c15dba0..eb5bcc75 100644 Binary files a/Backend/src/rooms/models/__pycache__/room_inspection.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/room_inspection.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/room_maintenance.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/room_maintenance.cpython-312.pyc index 75335f56..02c33d80 100644 Binary files a/Backend/src/rooms/models/__pycache__/room_maintenance.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/room_maintenance.cpython-312.pyc differ diff --git a/Backend/src/rooms/models/__pycache__/room_type.cpython-312.pyc b/Backend/src/rooms/models/__pycache__/room_type.cpython-312.pyc index 717372ac..0e722a52 100644 Binary files a/Backend/src/rooms/models/__pycache__/room_type.cpython-312.pyc and b/Backend/src/rooms/models/__pycache__/room_type.cpython-312.pyc differ diff --git a/Backend/src/rooms/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/rooms/routes/__pycache__/__init__.cpython-312.pyc index 0496561a..1802ba08 100644 Binary files a/Backend/src/rooms/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/rooms/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/rooms/routes/__pycache__/advanced_room_routes.cpython-312.pyc b/Backend/src/rooms/routes/__pycache__/advanced_room_routes.cpython-312.pyc index 01667954..d48560af 100644 Binary files a/Backend/src/rooms/routes/__pycache__/advanced_room_routes.cpython-312.pyc and b/Backend/src/rooms/routes/__pycache__/advanced_room_routes.cpython-312.pyc differ diff --git a/Backend/src/rooms/routes/__pycache__/rate_plan_routes.cpython-312.pyc b/Backend/src/rooms/routes/__pycache__/rate_plan_routes.cpython-312.pyc index f4cb5eea..724a3406 100644 Binary files a/Backend/src/rooms/routes/__pycache__/rate_plan_routes.cpython-312.pyc and b/Backend/src/rooms/routes/__pycache__/rate_plan_routes.cpython-312.pyc differ diff --git a/Backend/src/rooms/routes/__pycache__/room_routes.cpython-312.pyc b/Backend/src/rooms/routes/__pycache__/room_routes.cpython-312.pyc index c235dbd1..1fc99c0d 100644 Binary files a/Backend/src/rooms/routes/__pycache__/room_routes.cpython-312.pyc and b/Backend/src/rooms/routes/__pycache__/room_routes.cpython-312.pyc differ diff --git a/Backend/src/rooms/routes/room_routes.py b/Backend/src/rooms/routes/room_routes.py index 0343f983..14173a5b 100644 --- a/Backend/src/rooms/routes/room_routes.py +++ b/Backend/src/rooms/routes/room_routes.py @@ -11,7 +11,7 @@ from ...auth.models.user import User from ..models.room import Room, RoomStatus from ...analytics.services.audit_service import audit_service from ..models.room_type import RoomType -from ..schemas.room import CreateRoomRequest, UpdateRoomRequest, BulkDeleteRoomsRequest, UpdateAmenityRequest +from ..schemas.room import CreateRoomRequest, UpdateRoomRequest, BulkDeleteRoomsRequest, UpdateAmenityRequest, CreateRoomTypeRequest, UpdateRoomTypeRequest from ...shared.utils.response_helpers import success_response from ...reviews.models.review import Review, ReviewStatus from ...bookings.models.booking import Booking, BookingStatus @@ -192,6 +192,7 @@ async def get_room_types(db: Session=Depends(get_db)): 'description': rt.description, 'base_price': float(rt.base_price) if rt.base_price else 0.0, 'capacity': rt.capacity, + 'amenities': rt.amenities if rt.amenities else [], } for rt in room_types ] @@ -200,6 +201,221 @@ async def get_room_types(db: Session=Depends(get_db)): logger.error(f'Error fetching room types: {str(e)}', exc_info=True) raise HTTPException(status_code=500, detail=str(e)) + +@router.get('/room-types/{id}') +async def get_room_type(id: int, db: Session=Depends(get_db)): + """Get a specific room type by ID.""" + try: + room_type = db.query(RoomType).filter(RoomType.id == id).first() + if not room_type: + raise HTTPException(status_code=404, detail='Room type not found') + + room_type_dict = { + 'id': room_type.id, + 'name': room_type.name, + 'description': room_type.description, + 'base_price': float(room_type.base_price) if room_type.base_price else 0.0, + 'capacity': room_type.capacity, + 'amenities': room_type.amenities if room_type.amenities else [], + 'created_at': room_type.created_at.isoformat() if room_type.created_at else None, + 'updated_at': room_type.updated_at.isoformat() if room_type.updated_at else None, + } + return success_response(data={'room_type': room_type_dict}, message='Room type retrieved successfully') + except HTTPException: + raise + except Exception as e: + logger.error(f'Error fetching room type: {str(e)}', exc_info=True) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post('/room-types', dependencies=[Depends(authorize_roles('admin'))]) +async def create_room_type( + room_type_data: CreateRoomTypeRequest, + request: Request, + current_user: User=Depends(authorize_roles('admin')), + db: Session=Depends(get_db) +): + """Create a new room type.""" + try: + # Check for duplicate name + existing = db.query(RoomType).filter(RoomType.name == room_type_data.name).first() + if existing: + raise HTTPException(status_code=400, detail='Room type with this name already exists') + + room_type = RoomType( + name=room_type_data.name, + description=room_type_data.description, + base_price=room_type_data.base_price, + capacity=room_type_data.capacity, + amenities=room_type_data.amenities or [] + ) + + db.add(room_type) + db.commit() + db.refresh(room_type) + + # Audit log + audit_service.log_action( + db=db, + user_id=current_user.id, + action='create', + resource_type='room_type', + resource_id=room_type.id, + details={'name': room_type.name, 'base_price': float(room_type.base_price)} + ) + + room_type_dict = { + 'id': room_type.id, + 'name': room_type.name, + 'description': room_type.description, + 'base_price': float(room_type.base_price) if room_type.base_price else 0.0, + 'capacity': room_type.capacity, + 'amenities': room_type.amenities if room_type.amenities else [], + 'created_at': room_type.created_at.isoformat() if room_type.created_at else None, + 'updated_at': room_type.updated_at.isoformat() if room_type.updated_at else None, + } + + return success_response(data={'room_type': room_type_dict}, message='Room type created successfully') + except HTTPException: + db.rollback() + raise + except IntegrityError as e: + db.rollback() + logger.error(f'Database integrity error during room type creation: {str(e)}') + raise HTTPException(status_code=409, detail='Room type conflict detected. Please check room type name.') + except Exception as e: + db.rollback() + logger.error(f'Error creating room type: {str(e)}', exc_info=True) + raise HTTPException(status_code=500, detail='An error occurred while creating the room type') + + +@router.put('/room-types/{id}', dependencies=[Depends(authorize_roles('admin'))]) +async def update_room_type( + id: int, + room_type_data: UpdateRoomTypeRequest, + request: Request, + current_user: User=Depends(authorize_roles('admin')), + db: Session=Depends(get_db) +): + """Update a room type.""" + try: + room_type = db.query(RoomType).filter(RoomType.id == id).first() + if not room_type: + raise HTTPException(status_code=404, detail='Room type not found') + + # Check for duplicate name if name is being updated + if room_type_data.name and room_type_data.name != room_type.name: + existing = db.query(RoomType).filter(RoomType.name == room_type_data.name).first() + if existing: + raise HTTPException(status_code=400, detail='Room type with this name already exists') + + # Update fields + update_data = room_type_data.model_dump(exclude_unset=True) + for key, value in update_data.items(): + setattr(room_type, key, value) + + db.commit() + db.refresh(room_type) + + # Audit log + audit_service.log_action( + db=db, + user_id=current_user.id, + action='update', + resource_type='room_type', + resource_id=room_type.id, + details=update_data + ) + + room_type_dict = { + 'id': room_type.id, + 'name': room_type.name, + 'description': room_type.description, + 'base_price': float(room_type.base_price) if room_type.base_price else 0.0, + 'capacity': room_type.capacity, + 'amenities': room_type.amenities if room_type.amenities else [], + 'created_at': room_type.created_at.isoformat() if room_type.created_at else None, + 'updated_at': room_type.updated_at.isoformat() if room_type.updated_at else None, + } + + return success_response(data={'room_type': room_type_dict}, message='Room type updated successfully') + except HTTPException: + db.rollback() + raise + except IntegrityError as e: + db.rollback() + logger.error(f'Database integrity error during room type update: {str(e)}') + raise HTTPException(status_code=409, detail='Room type conflict detected. Please check room type name.') + except Exception as e: + db.rollback() + logger.error(f'Error updating room type: {str(e)}', exc_info=True) + raise HTTPException(status_code=500, detail='An error occurred while updating the room type') + + +@router.delete('/room-types/{id}', dependencies=[Depends(authorize_roles('admin'))]) +async def delete_room_type( + id: int, + request: Request, + current_user: User=Depends(authorize_roles('admin')), + db: Session=Depends(get_db) +): + """Delete a room type.""" + try: + room_type = db.query(RoomType).filter(RoomType.id == id).first() + if not room_type: + raise HTTPException(status_code=404, detail='Room type not found') + + # Check if room type is being used by any rooms + rooms_count = db.query(Room).filter(Room.room_type_id == id).count() + if rooms_count > 0: + raise HTTPException( + status_code=400, + detail=f'Cannot delete room type. It is being used by {rooms_count} room(s). Please remove or reassign rooms first.' + ) + + # Check if room type is being used by any rate plans + from ..models.rate_plan import RatePlan + rate_plans_count = db.query(RatePlan).filter(RatePlan.room_type_id == id).count() + if rate_plans_count > 0: + raise HTTPException( + status_code=400, + detail=f'Cannot delete room type. It is being used by {rate_plans_count} rate plan(s). Please remove or reassign rate plans first.' + ) + + # Check if room type is being used by any packages + from ...loyalty.models.package import Package + packages_count = db.query(Package).filter(Package.room_type_id == id).count() + if packages_count > 0: + raise HTTPException( + status_code=400, + detail=f'Cannot delete room type. It is being used by {packages_count} package(s). Please remove or reassign packages first.' + ) + + # Store name for audit log + room_type_name = room_type.name + + db.delete(room_type) + db.commit() + + # Audit log + audit_service.log_action( + db=db, + user_id=current_user.id, + action='delete', + resource_type='room_type', + resource_id=id, + details={'name': room_type_name} + ) + + return success_response(message='Room type deleted successfully') + except HTTPException: + db.rollback() + raise + except Exception as e: + db.rollback() + logger.error(f'Error deleting room type: {str(e)}', exc_info=True) + raise HTTPException(status_code=500, detail='An error occurred while deleting the room type') + @router.get('/available') async def search_available_rooms(request: Request, from_date: str=Query(..., alias='from'), to_date: str=Query(..., alias='to'), roomId: Optional[int]=Query(None, alias='roomId'), type: Optional[str]=Query(None), capacity: Optional[int]=Query(None), page: int=Query(1, ge=1), limit: int=Query(12, ge=1, le=100), db: Session=Depends(get_db)): try: @@ -359,6 +575,19 @@ async def create_room(room_data: CreateRoomRequest, request: Request, current_us # Use price from request or default to room type base price room_price = room_data.price if room_data.price is not None else float(room_type.base_price) if room_type.base_price else 0.0 + # Inherit amenities from room type if not provided or empty + room_amenities = room_data.amenities if room_data.amenities else [] + if not room_amenities and room_type.amenities: + # Convert room type amenities to list if needed + if isinstance(room_type.amenities, list): + room_amenities = room_type.amenities + elif isinstance(room_type.amenities, str): + try: + import json + room_amenities = json.loads(room_type.amenities) + except: + room_amenities = [s.strip() for s in room_type.amenities.split(',') if s.strip()] + room = Room( room_type_id=room_data.room_type_id, room_number=room_data.room_number, @@ -370,7 +599,7 @@ async def create_room(room_data: CreateRoomRequest, request: Request, current_us capacity=room_data.capacity, room_size=room_data.room_size, view=room_data.view, - amenities=room_data.amenities or [] + amenities=room_amenities ) db.add(room) db.flush() @@ -415,6 +644,29 @@ async def update_room(id: int, room_data: UpdateRoomRequest, request: Request, c db.rollback() raise HTTPException(status_code=404, detail='Room type not found') room.room_type_id = room_data.room_type_id + + # Inherit amenities from room type if amenities not provided in update + if not room_data.amenities and room_type.amenities: + # Convert room type amenities to list if needed + if isinstance(room_type.amenities, list): + room.amenities = room_type.amenities + elif isinstance(room_type.amenities, str): + try: + import json + room.amenities = json.loads(room_type.amenities) + except: + room.amenities = [s.strip() for s in room_type.amenities.split(',') if s.strip()] + elif not room_data.amenities: + # If room type not changed but amenities not provided, inherit from current room type + if room.room_type and room.room_type.amenities: + if isinstance(room.room_type.amenities, list): + room.amenities = room.room_type.amenities + elif isinstance(room.room_type.amenities, str): + try: + import json + room.amenities = json.loads(room.room_type.amenities) + except: + room.amenities = [s.strip() for s in room.room_type.amenities.split(',') if s.strip()] if room_data.room_number is not None: # Check for duplicate room number diff --git a/Backend/src/rooms/schemas/__pycache__/__init__.cpython-312.pyc b/Backend/src/rooms/schemas/__pycache__/__init__.cpython-312.pyc index a1376572..11681ab6 100644 Binary files a/Backend/src/rooms/schemas/__pycache__/__init__.cpython-312.pyc and b/Backend/src/rooms/schemas/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/rooms/schemas/__pycache__/room.cpython-312.pyc b/Backend/src/rooms/schemas/__pycache__/room.cpython-312.pyc index c1e78f7c..af7cabc0 100644 Binary files a/Backend/src/rooms/schemas/__pycache__/room.cpython-312.pyc and b/Backend/src/rooms/schemas/__pycache__/room.cpython-312.pyc differ diff --git a/Backend/src/rooms/schemas/room.py b/Backend/src/rooms/schemas/room.py index de0c54d1..2a30c6a7 100644 --- a/Backend/src/rooms/schemas/room.py +++ b/Backend/src/rooms/schemas/room.py @@ -110,3 +110,51 @@ class UpdateAmenityRequest(BaseModel): """Schema for updating/renaming an amenity.""" new_name: str = Field(..., min_length=1, max_length=100, description="New name for the amenity") + +class CreateRoomTypeRequest(BaseModel): + """Schema for creating a room type.""" + name: str = Field(..., min_length=1, max_length=100, description="Room type name") + description: Optional[str] = Field(None, max_length=2000, description="Room type description") + base_price: float = Field(..., ge=0, description="Base price for this room type") + capacity: int = Field(..., ge=1, le=20, description="Room capacity") + amenities: Optional[List[str]] = Field(default_factory=list, description="List of amenities") + + @field_validator('amenities') + @classmethod + def validate_amenities(cls, v: Optional[List[str]]) -> List[str]: + """Ensure amenities is a list.""" + if v is None: + return [] + if not isinstance(v, list): + return [] + return v + + model_config = { + "json_schema_extra": { + "example": { + "name": "Deluxe Suite", + "description": "Spacious suite with premium amenities", + "base_price": 250.00, + "capacity": 2, + "amenities": ["WiFi", "TV", "AC", "Mini Bar"] + } + } + } + + +class UpdateRoomTypeRequest(BaseModel): + """Schema for updating a room type.""" + name: Optional[str] = Field(None, min_length=1, max_length=100, description="Room type name") + description: Optional[str] = Field(None, max_length=2000, description="Room type description") + base_price: Optional[float] = Field(None, ge=0, description="Base price for this room type") + capacity: Optional[int] = Field(None, ge=1, le=20, description="Room capacity") + amenities: Optional[List[str]] = Field(None, description="List of amenities") + + @field_validator('amenities') + @classmethod + def validate_amenities(cls, v: Optional[List[str]]) -> Optional[List[str]]: + """Ensure amenities is a list.""" + if v is not None and not isinstance(v, list): + return [] + return v + diff --git a/Backend/src/rooms/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/rooms/services/__pycache__/__init__.cpython-312.pyc index b71fe869..cf292b7e 100644 Binary files a/Backend/src/rooms/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/rooms/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/rooms/services/__pycache__/room_assignment_service.cpython-312.pyc b/Backend/src/rooms/services/__pycache__/room_assignment_service.cpython-312.pyc index c6f90e81..53e2e0a4 100644 Binary files a/Backend/src/rooms/services/__pycache__/room_assignment_service.cpython-312.pyc and b/Backend/src/rooms/services/__pycache__/room_assignment_service.cpython-312.pyc differ diff --git a/Backend/src/rooms/services/__pycache__/room_service.cpython-312.pyc b/Backend/src/rooms/services/__pycache__/room_service.cpython-312.pyc index 325aba5f..e2f7d3b4 100644 Binary files a/Backend/src/rooms/services/__pycache__/room_service.cpython-312.pyc and b/Backend/src/rooms/services/__pycache__/room_service.cpython-312.pyc differ diff --git a/Backend/src/security/__pycache__/__init__.cpython-312.pyc b/Backend/src/security/__pycache__/__init__.cpython-312.pyc index 96d43175..4bcd828d 100644 Binary files a/Backend/src/security/__pycache__/__init__.cpython-312.pyc and b/Backend/src/security/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/__init__.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/__init__.cpython-312.pyc index 641b541d..d26b74c7 100644 Binary files a/Backend/src/security/middleware/__pycache__/__init__.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/admin_ip_whitelist.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/admin_ip_whitelist.cpython-312.pyc index 2b3e2694..a6f45808 100644 Binary files a/Backend/src/security/middleware/__pycache__/admin_ip_whitelist.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/admin_ip_whitelist.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/auth.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/auth.cpython-312.pyc index 5bf12138..89d047de 100644 Binary files a/Backend/src/security/middleware/__pycache__/auth.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/csrf.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/csrf.cpython-312.pyc index 550fe4e1..f55f90fe 100644 Binary files a/Backend/src/security/middleware/__pycache__/csrf.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/csrf.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/role_based_rate_limit.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/role_based_rate_limit.cpython-312.pyc index ca7b886e..79638d67 100644 Binary files a/Backend/src/security/middleware/__pycache__/role_based_rate_limit.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/role_based_rate_limit.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/security.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/security.cpython-312.pyc index 3ae81547..7f9b77fe 100644 Binary files a/Backend/src/security/middleware/__pycache__/security.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/security.cpython-312.pyc differ diff --git a/Backend/src/security/middleware/__pycache__/step_up_auth.cpython-312.pyc b/Backend/src/security/middleware/__pycache__/step_up_auth.cpython-312.pyc index 0865b790..5fc67d97 100644 Binary files a/Backend/src/security/middleware/__pycache__/step_up_auth.cpython-312.pyc and b/Backend/src/security/middleware/__pycache__/step_up_auth.cpython-312.pyc differ diff --git a/Backend/src/security/models/__pycache__/__init__.cpython-312.pyc b/Backend/src/security/models/__pycache__/__init__.cpython-312.pyc index a8a7d59b..c68ebfc4 100644 Binary files a/Backend/src/security/models/__pycache__/__init__.cpython-312.pyc and b/Backend/src/security/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/security/models/__pycache__/gdpr_compliance.cpython-312.pyc b/Backend/src/security/models/__pycache__/gdpr_compliance.cpython-312.pyc index fd002145..f70e7219 100644 Binary files a/Backend/src/security/models/__pycache__/gdpr_compliance.cpython-312.pyc and b/Backend/src/security/models/__pycache__/gdpr_compliance.cpython-312.pyc differ diff --git a/Backend/src/security/models/__pycache__/security_event.cpython-312.pyc b/Backend/src/security/models/__pycache__/security_event.cpython-312.pyc index b0fe234e..f2a68d8f 100644 Binary files a/Backend/src/security/models/__pycache__/security_event.cpython-312.pyc and b/Backend/src/security/models/__pycache__/security_event.cpython-312.pyc differ diff --git a/Backend/src/security/models/gdpr_compliance.py b/Backend/src/security/models/gdpr_compliance.py index c0875b3b..51ffc65d 100644 --- a/Backend/src/security/models/gdpr_compliance.py +++ b/Backend/src/security/models/gdpr_compliance.py @@ -51,7 +51,7 @@ class DataSubjectRequest(Base): updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) # Relationships - user = Column(Integer, ForeignKey('users.id'), nullable=True) + user = relationship('User', foreign_keys=[user_id]) assignee = relationship('User', foreign_keys=[assigned_to]) completer = relationship('User', foreign_keys=[completed_by]) diff --git a/Backend/src/security/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/security/routes/__pycache__/__init__.cpython-312.pyc index 478af519..482959b8 100644 Binary files a/Backend/src/security/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/security/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/security/routes/__pycache__/compliance_routes.cpython-312.pyc b/Backend/src/security/routes/__pycache__/compliance_routes.cpython-312.pyc index bc147be9..8a8dea89 100644 Binary files a/Backend/src/security/routes/__pycache__/compliance_routes.cpython-312.pyc and b/Backend/src/security/routes/__pycache__/compliance_routes.cpython-312.pyc differ diff --git a/Backend/src/security/routes/__pycache__/security_routes.cpython-312.pyc b/Backend/src/security/routes/__pycache__/security_routes.cpython-312.pyc index bbe3e3d1..43113bfd 100644 Binary files a/Backend/src/security/routes/__pycache__/security_routes.cpython-312.pyc and b/Backend/src/security/routes/__pycache__/security_routes.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/__init__.cpython-312.pyc b/Backend/src/security/services/__pycache__/__init__.cpython-312.pyc index 726bec27..a44d7da0 100644 Binary files a/Backend/src/security/services/__pycache__/__init__.cpython-312.pyc and b/Backend/src/security/services/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/encryption_service.cpython-312.pyc b/Backend/src/security/services/__pycache__/encryption_service.cpython-312.pyc index 6739aa2b..20d41b4e 100644 Binary files a/Backend/src/security/services/__pycache__/encryption_service.cpython-312.pyc and b/Backend/src/security/services/__pycache__/encryption_service.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/gdpr_service.cpython-312.pyc b/Backend/src/security/services/__pycache__/gdpr_service.cpython-312.pyc index 88fafe04..6e0ebe81 100644 Binary files a/Backend/src/security/services/__pycache__/gdpr_service.cpython-312.pyc and b/Backend/src/security/services/__pycache__/gdpr_service.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/privacy_admin_service.cpython-312.pyc b/Backend/src/security/services/__pycache__/privacy_admin_service.cpython-312.pyc index b2f7dc3d..03b7e8f6 100644 Binary files a/Backend/src/security/services/__pycache__/privacy_admin_service.cpython-312.pyc and b/Backend/src/security/services/__pycache__/privacy_admin_service.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/security_monitoring_service.cpython-312.pyc b/Backend/src/security/services/__pycache__/security_monitoring_service.cpython-312.pyc index 91a369f1..746ebf72 100644 Binary files a/Backend/src/security/services/__pycache__/security_monitoring_service.cpython-312.pyc and b/Backend/src/security/services/__pycache__/security_monitoring_service.cpython-312.pyc differ diff --git a/Backend/src/security/services/__pycache__/security_scan_service.cpython-312.pyc b/Backend/src/security/services/__pycache__/security_scan_service.cpython-312.pyc index 53cb46d0..91e3db40 100644 Binary files a/Backend/src/security/services/__pycache__/security_scan_service.cpython-312.pyc and b/Backend/src/security/services/__pycache__/security_scan_service.cpython-312.pyc differ diff --git a/Backend/src/shared/__pycache__/__init__.cpython-312.pyc b/Backend/src/shared/__pycache__/__init__.cpython-312.pyc index 20bee488..eb31c31c 100644 Binary files a/Backend/src/shared/__pycache__/__init__.cpython-312.pyc and b/Backend/src/shared/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/shared/config/__pycache__/__init__.cpython-312.pyc b/Backend/src/shared/config/__pycache__/__init__.cpython-312.pyc index 24835475..865a2bf8 100644 Binary files a/Backend/src/shared/config/__pycache__/__init__.cpython-312.pyc and b/Backend/src/shared/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/shared/config/__pycache__/database.cpython-312.pyc b/Backend/src/shared/config/__pycache__/database.cpython-312.pyc index 9f479635..49a2776c 100644 Binary files a/Backend/src/shared/config/__pycache__/database.cpython-312.pyc and b/Backend/src/shared/config/__pycache__/database.cpython-312.pyc differ diff --git a/Backend/src/shared/config/__pycache__/logging_config.cpython-312.pyc b/Backend/src/shared/config/__pycache__/logging_config.cpython-312.pyc index a606bb5f..2c93c447 100644 Binary files a/Backend/src/shared/config/__pycache__/logging_config.cpython-312.pyc and b/Backend/src/shared/config/__pycache__/logging_config.cpython-312.pyc differ diff --git a/Backend/src/shared/config/__pycache__/settings.cpython-312.pyc b/Backend/src/shared/config/__pycache__/settings.cpython-312.pyc index 1d4c8e0c..a490aedf 100644 Binary files a/Backend/src/shared/config/__pycache__/settings.cpython-312.pyc and b/Backend/src/shared/config/__pycache__/settings.cpython-312.pyc differ diff --git a/Backend/src/shared/config/settings.py b/Backend/src/shared/config/settings.py index 3b2b739a..f2df89bb 100644 --- a/Backend/src/shared/config/settings.py +++ b/Backend/src/shared/config/settings.py @@ -23,6 +23,9 @@ class Settings(BaseSettings): JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = Field(default=3, description='JWT refresh token expiration in days (reduced from 7 for better security)') MAX_LOGIN_ATTEMPTS: int = Field(default=5, description='Maximum failed login attempts before account lockout') ACCOUNT_LOCKOUT_DURATION_MINUTES: int = Field(default=30, description='Account lockout duration in minutes after max failed attempts') + PASSWORD_EXPIRY_DAYS: int = Field(default=0, description='Password expiry in days (0 = no expiry, 90 = 90 days)') + PASSWORD_HISTORY_COUNT: int = Field(default=5, description='Number of previous passwords to prevent reuse (0 = disabled)') + PASSWORD_MIN_AGE_DAYS: int = Field(default=1, description='Minimum days before password can be changed again (0 = no minimum)') ENCRYPTION_KEY: str = Field(default='', description='Base64-encoded encryption key for data encryption at rest') CLIENT_URL: str = Field(default='http://localhost:5173', description='Frontend client URL') CORS_ORIGINS: List[str] = Field(default_factory=lambda: ['http://localhost:5173', 'http://localhost:3000', 'http://127.0.0.1:5173'], description='Allowed CORS origins') diff --git a/Backend/src/shared/middleware/__pycache__/__init__.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/__init__.cpython-312.pyc index 5d59f3c0..cf43e0d9 100644 Binary files a/Backend/src/shared/middleware/__pycache__/__init__.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/api_versioning.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/api_versioning.cpython-312.pyc index aba68c1f..624fdcc8 100644 Binary files a/Backend/src/shared/middleware/__pycache__/api_versioning.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/api_versioning.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/cookie_consent.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/cookie_consent.cpython-312.pyc index 4339a78b..1f8f596e 100644 Binary files a/Backend/src/shared/middleware/__pycache__/cookie_consent.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/cookie_consent.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/error_handler.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/error_handler.cpython-312.pyc index 37f29969..f49123c3 100644 Binary files a/Backend/src/shared/middleware/__pycache__/error_handler.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/error_handler.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/request_id.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/request_id.cpython-312.pyc index 25e02fec..2107c111 100644 Binary files a/Backend/src/shared/middleware/__pycache__/request_id.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/request_id.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/request_size_limit.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/request_size_limit.cpython-312.pyc index 178ce627..10fb20c1 100644 Binary files a/Backend/src/shared/middleware/__pycache__/request_size_limit.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/request_size_limit.cpython-312.pyc differ diff --git a/Backend/src/shared/middleware/__pycache__/timeout.cpython-312.pyc b/Backend/src/shared/middleware/__pycache__/timeout.cpython-312.pyc index d9805638..4e542960 100644 Binary files a/Backend/src/shared/middleware/__pycache__/timeout.cpython-312.pyc and b/Backend/src/shared/middleware/__pycache__/timeout.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/__init__.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/__init__.cpython-312.pyc index b3ad74e6..d33c9467 100644 Binary files a/Backend/src/shared/utils/__pycache__/__init__.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/currency_helpers.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/currency_helpers.cpython-312.pyc index 4110e8aa..5c2a4968 100644 Binary files a/Backend/src/shared/utils/__pycache__/currency_helpers.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/currency_helpers.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/email_templates.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/email_templates.cpython-312.pyc index 3d19df00..b8cb2a3f 100644 Binary files a/Backend/src/shared/utils/__pycache__/email_templates.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/email_templates.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/file_validation.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/file_validation.cpython-312.pyc index 72cdb827..7be59c7d 100644 Binary files a/Backend/src/shared/utils/__pycache__/file_validation.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/file_validation.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/image_optimization.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/image_optimization.cpython-312.pyc index 5f0a77ac..2c2b0378 100644 Binary files a/Backend/src/shared/utils/__pycache__/image_optimization.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/image_optimization.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/mailer.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/mailer.cpython-312.pyc index d301d46f..db50c40d 100644 Binary files a/Backend/src/shared/utils/__pycache__/mailer.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/mailer.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/request_helpers.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/request_helpers.cpython-312.pyc index 66fd6248..4a49ccb0 100644 Binary files a/Backend/src/shared/utils/__pycache__/request_helpers.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/request_helpers.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/response_helpers.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/response_helpers.cpython-312.pyc index 33fc08a0..b5231bab 100644 Binary files a/Backend/src/shared/utils/__pycache__/response_helpers.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/response_helpers.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/role_helpers.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/role_helpers.cpython-312.pyc index e3be2ac2..fa11ae41 100644 Binary files a/Backend/src/shared/utils/__pycache__/role_helpers.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/role_helpers.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/__pycache__/sanitization.cpython-312.pyc b/Backend/src/shared/utils/__pycache__/sanitization.cpython-312.pyc index c44bf741..ecc43e75 100644 Binary files a/Backend/src/shared/utils/__pycache__/sanitization.cpython-312.pyc and b/Backend/src/shared/utils/__pycache__/sanitization.cpython-312.pyc differ diff --git a/Backend/src/shared/utils/email_templates.py b/Backend/src/shared/utils/email_templates.py index 5850cd8f..47617d3f 100644 --- a/Backend/src/shared/utils/email_templates.py +++ b/Backend/src/shared/utils/email_templates.py @@ -169,6 +169,68 @@ def password_reset_email_template(reset_url: str) -> str: company_name = company_settings.get("company_name") or "Hotel Booking" return get_base_template(content, f"Password Reset - {company_name}", reset_url.split('/reset-password')[0] if '/reset-password' in reset_url else "http://localhost:5173") +def email_verification_template(verification_url: str, name: str) -> str: + content = f""" +
+

Verify Your Email Address

+

+ Hi {name}, +

+

+ Thank you for registering! Please verify your email address by clicking the button below. +

+ +

+ If the button doesn't work, copy and paste this link into your browser:
+ {verification_url} +

+

+ This link will expire in 24 hours for security reasons. +

+

+ If you did not create an account, please ignore this email. +

+
+ """ + company_settings = _get_company_settings() + company_name = company_settings.get("company_name") or "Hotel Booking" + return get_base_template(content, f"Verify Your Email - {company_name}", verification_url.split('/verify-email')[0] if '/verify-email' in verification_url else "http://localhost:5173") + +def email_changed_verification_template(verification_url: str, name: str, new_email: str) -> str: + content = f""" +
+

Verify Your New Email Address

+

+ Hi {name}, +

+

+ You have requested to change your email address to {new_email}. Please verify this new email address by clicking the button below. +

+ +

+ If the button doesn't work, copy and paste this link into your browser:
+ {verification_url} +

+

+ This link will expire in 24 hours for security reasons. +

+

+ If you did not request this change, please contact us immediately to secure your account. +

+
+ """ + company_settings = _get_company_settings() + company_name = company_settings.get("company_name") or "Hotel Booking" + return get_base_template(content, f"Verify Your New Email - {company_name}", verification_url.split('/verify-email')[0] if '/verify-email' in verification_url else "http://localhost:5173") + def password_changed_email_template(email: str) -> str: content = f"""
diff --git a/Backend/src/shared/utils/role_helpers.py b/Backend/src/shared/utils/role_helpers.py index c0d7bc3a..29a29c92 100644 --- a/Backend/src/shared/utils/role_helpers.py +++ b/Backend/src/shared/utils/role_helpers.py @@ -210,10 +210,10 @@ def user_has_permissions(user: User, db: Session, permissions: list[str]) -> boo def can_access_all_payments(user: User, db: Session) -> bool: """ Check if user can see all payments. - - Admins and any accountant family role may view all. + - Admins, staff, and any accountant family role may view all. """ role_name = get_user_role_name(user, db) - if role_name == "admin": + if role_name in {"admin", "staff"}: return True if is_accountant(user, db): return True diff --git a/Backend/src/system/models/__pycache__/approval_workflow.cpython-312.pyc b/Backend/src/system/models/__pycache__/approval_workflow.cpython-312.pyc index b21fca35..56a3ef79 100644 Binary files a/Backend/src/system/models/__pycache__/approval_workflow.cpython-312.pyc and b/Backend/src/system/models/__pycache__/approval_workflow.cpython-312.pyc differ diff --git a/Backend/src/system/models/__pycache__/system_settings.cpython-312.pyc b/Backend/src/system/models/__pycache__/system_settings.cpython-312.pyc index b29ab2f8..44b350dc 100644 Binary files a/Backend/src/system/models/__pycache__/system_settings.cpython-312.pyc and b/Backend/src/system/models/__pycache__/system_settings.cpython-312.pyc differ diff --git a/Backend/src/system/models/__pycache__/workflow.cpython-312.pyc b/Backend/src/system/models/__pycache__/workflow.cpython-312.pyc index e670a2e5..e9a9ce6c 100644 Binary files a/Backend/src/system/models/__pycache__/workflow.cpython-312.pyc and b/Backend/src/system/models/__pycache__/workflow.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/__init__.cpython-312.pyc b/Backend/src/system/routes/__pycache__/__init__.cpython-312.pyc index e4fd42fc..e3ee9706 100644 Binary files a/Backend/src/system/routes/__pycache__/__init__.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/approval_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/approval_routes.cpython-312.pyc index fb3c18ec..4dab27e6 100644 Binary files a/Backend/src/system/routes/__pycache__/approval_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/approval_routes.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/backup_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/backup_routes.cpython-312.pyc index 797975e3..3604b2bd 100644 Binary files a/Backend/src/system/routes/__pycache__/backup_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/backup_routes.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc index 9837915f..8bc55002 100644 Binary files a/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/system_settings_routes.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/task_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/task_routes.cpython-312.pyc index 0a2ad1f5..3e6ac637 100644 Binary files a/Backend/src/system/routes/__pycache__/task_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/task_routes.cpython-312.pyc differ diff --git a/Backend/src/system/routes/__pycache__/workflow_routes.cpython-312.pyc b/Backend/src/system/routes/__pycache__/workflow_routes.cpython-312.pyc index f5c44587..22661157 100644 Binary files a/Backend/src/system/routes/__pycache__/workflow_routes.cpython-312.pyc and b/Backend/src/system/routes/__pycache__/workflow_routes.cpython-312.pyc differ diff --git a/Backend/src/system/services/__pycache__/approval_service.cpython-312.pyc b/Backend/src/system/services/__pycache__/approval_service.cpython-312.pyc index 73dafca4..be562bdb 100644 Binary files a/Backend/src/system/services/__pycache__/approval_service.cpython-312.pyc and b/Backend/src/system/services/__pycache__/approval_service.cpython-312.pyc differ diff --git a/Backend/src/system/services/__pycache__/backup_service.cpython-312.pyc b/Backend/src/system/services/__pycache__/backup_service.cpython-312.pyc index de4faaf9..bda64e1e 100644 Binary files a/Backend/src/system/services/__pycache__/backup_service.cpython-312.pyc and b/Backend/src/system/services/__pycache__/backup_service.cpython-312.pyc differ diff --git a/Backend/src/system/services/__pycache__/workflow_service.cpython-312.pyc b/Backend/src/system/services/__pycache__/workflow_service.cpython-312.pyc index dfdd7ddc..fb62c736 100644 Binary files a/Backend/src/system/services/__pycache__/workflow_service.cpython-312.pyc and b/Backend/src/system/services/__pycache__/workflow_service.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc index e49fbd26..ce365951 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc index 62784cda..a2f7bcb2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-312.pyc index 706bf52c..31b0f682 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc index 264188ce..2e212a4d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc index ba10f26b..fe06af0d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/CurImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-312.pyc index 211910ab..dfa1054d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-312.pyc index bc24199d..3e94aa8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc index 5b7fa1fa..94997bc7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ExifTags.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ExifTags.cpython-312.pyc index 5b4a88c2..738487e4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ExifTags.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ExifTags.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc index c97a4cbf..7c09c75d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc index 2bdeffe0..7f6e9f6a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FliImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-312.pyc index 2ff1f758..544a746f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-312.pyc index 0940ce30..75d2d2c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc index 44790ac8..16d32dba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc index 0904ccb5..70e5b8e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GifImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc index 730b8437..b1804356 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpGradientFile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-312.pyc index 1a9f2e57..4d8b9bd6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc index 86a4fe60..2adfa1b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc index 0aea0206..30a7ace6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc index 19cf49c6..c9cf4273 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc index 6f78951e..0965e7cc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImImagePlugin.cpython-312.pyc index cfbc175d..190732de 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Image.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Image.cpython-312.pyc index 1327a106..3b725ace 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Image.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Image.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageChops.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageChops.cpython-312.pyc index ff72a54f..9c7cbf8e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageChops.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageChops.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc index a5ad1aad..281951c1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageColor.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc index 0966f409..4d56c7a6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageDraw.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFile.cpython-312.pyc index 9564f464..fa530f24 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFont.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFont.cpython-312.pyc index 087ceae1..ba3ae349 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFont.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageFont.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMath.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMath.cpython-312.pyc index 0a651be1..131747db 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMath.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMath.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc index de57bf62..0f98b98b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageMode.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageOps.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageOps.cpython-312.pyc index 0502a130..5ae7c81f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageOps.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageOps.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc index f9cae486..c651b28d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImagePalette.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc index 3b4495ce..b944143b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageSequence.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageText.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageText.cpython-312.pyc index dcdfd7a1..36cd80c7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageText.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImageText.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc index 98959057..a607797a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-312.pyc index 1cd71b21..b829d954 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc index 5560e516..3ef31ddf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-312.pyc index edf0e0ad..57691e42 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc index 8483f3c7..894d285a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/JpegPresets.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc index ad0f8622..3354a710 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc index 1dbf0ec2..702084aa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MicImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-312.pyc index 857986ef..2de36971 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc index 8bc20935..1078ed3e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc index c021194e..72a3914f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/MspImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc index 8f4e9e65..a58e62f0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PaletteFile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc index 94a0d1da..a39d2e0d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc index a2ea2ab3..a2c5ec12 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc index dee4a389..db4a5467 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc index 6c46add3..f66fb264 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfParser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfParser.cpython-312.pyc index eb983a9e..bb55fd67 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfParser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PdfParser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-312.pyc index 49b5b499..7bed5440 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc index 63fe188e..08cd5791 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PngImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-312.pyc index a0fce9a9..affe314d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-312.pyc index ecf6769b..d700d7b6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-312.pyc index 2dde70f0..690a27e4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-312.pyc index 9bdbaa1b..05421d50 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-312.pyc index 26eefcfe..4bf0e81e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SunImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SunImagePlugin.cpython-312.pyc index 5baa51a1..2d245e84 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SunImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/SunImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-312.pyc index c1c29a47..63e63341 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-312.pyc index 95052453..43e0b40a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffTags.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffTags.cpython-312.pyc index b31fe02e..fa622dc7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffTags.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/TiffTags.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-312.pyc index 1b4732d9..ead312ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-312.pyc index 174bd298..e58c493f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-312.pyc index ffaab72c..dcab552e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-312.pyc index 64d84569..cddfe7c1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-312.pyc index b32ce92c..b19a92fe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/__init__.cpython-312.pyc index f3772970..d1deb24a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_binary.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_binary.cpython-312.pyc index b0d684f5..782fbdba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_binary.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_binary.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_deprecate.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_deprecate.cpython-312.pyc index 8cba9a1f..35893ef5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_deprecate.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_deprecate.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_typing.cpython-312.pyc index 5c807037..2caffa1d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_util.cpython-312.pyc index a3eb7298..daaa1ed6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_version.cpython-312.pyc index 4ff633c6..3ae58b9a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/features.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/features.cpython-312.pyc index 5d581fe2..37f2cb8e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/features.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/PIL/__pycache__/features.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc index d8f25b2a..67c1fc5d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/__pycache__/png.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/__pycache__/py.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/__pycache__/py.cpython-312.pyc new file mode 100644 index 00000000..3cd8236c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/__pycache__/py.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc index 6d8c79c5..4770f58d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc index 3a31168a..72293ced 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/__init__.py index 8eb8ec96..8a406c5c 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/__init__.py @@ -1,13 +1,9 @@ -from __future__ import annotations - - __all__ = ["__version__", "version_tuple"] try: - from ._version import version as __version__ - from ._version import version_tuple + from ._version import version as __version__, version_tuple except ImportError: # pragma: no cover # broken installation, we don't even try # unknown only works because we do poor mans version compare __version__ = "unknown" - version_tuple = (0, 0, "unknown") + version_tuple = (0, 0, "unknown") # type:ignore[assignment] diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..c72766bc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_argcomplete.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_argcomplete.cpython-312.pyc new file mode 100644 index 00000000..568acb83 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_argcomplete.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_version.cpython-312.pyc new file mode 100644 index 00000000..8c486203 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc new file mode 100644 index 00000000..90f836a9 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/cacheprovider.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/capture.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/capture.cpython-312.pyc new file mode 100644 index 00000000..fb6cb784 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/capture.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..4344cb29 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc new file mode 100644 index 00000000..feab5412 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/debugging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc new file mode 100644 index 00000000..12add6be Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/deprecated.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/doctest.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/doctest.cpython-312.pyc new file mode 100644 index 00000000..a1c03841 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/doctest.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc new file mode 100644 index 00000000..a1aaa761 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/faulthandler.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc new file mode 100644 index 00000000..7fb620a5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/fixtures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc new file mode 100644 index 00000000..7bcd7d8f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/freeze_support.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc new file mode 100644 index 00000000..647317a2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/helpconfig.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/hookspec.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/hookspec.cpython-312.pyc new file mode 100644 index 00000000..dda888a0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/hookspec.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/junitxml.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/junitxml.cpython-312.pyc new file mode 100644 index 00000000..f93dd4a5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/junitxml.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/legacypath.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/legacypath.cpython-312.pyc new file mode 100644 index 00000000..ef32be47 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/legacypath.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/logging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/logging.cpython-312.pyc new file mode 100644 index 00000000..c5d1d115 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/logging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/main.cpython-312.pyc new file mode 100644 index 00000000..2bff3156 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc new file mode 100644 index 00000000..956211db Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/monkeypatch.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nodes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nodes.cpython-312.pyc new file mode 100644 index 00000000..9165e739 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nodes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nose.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nose.cpython-312.pyc new file mode 100644 index 00000000..ac4ceedd Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/nose.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc new file mode 100644 index 00000000..5945dfd0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/outcomes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc new file mode 100644 index 00000000..d7211a9e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pastebin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pathlib.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pathlib.cpython-312.pyc new file mode 100644 index 00000000..28f04e33 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pathlib.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester.cpython-312.pyc new file mode 100644 index 00000000..872bc433 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc new file mode 100644 index 00000000..d0549ebc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/pytester_assertions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python.cpython-312.pyc new file mode 100644 index 00000000..f5d35b15 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_api.cpython-312.pyc new file mode 100644 index 00000000..eade7ca2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_path.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_path.cpython-312.pyc new file mode 100644 index 00000000..28968e71 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/python_path.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc new file mode 100644 index 00000000..4c5939dc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/recwarn.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/reports.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/reports.cpython-312.pyc new file mode 100644 index 00000000..d50c54ec Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/reports.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/runner.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/runner.cpython-312.pyc new file mode 100644 index 00000000..5d93f2bc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/runner.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/scope.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/scope.cpython-312.pyc new file mode 100644 index 00000000..89b1fd85 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/scope.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setuponly.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setuponly.cpython-312.pyc new file mode 100644 index 00000000..10176923 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setuponly.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc new file mode 100644 index 00000000..30b7db92 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/setupplan.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc new file mode 100644 index 00000000..e2716d2e Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/skipping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stash.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stash.cpython-312.pyc new file mode 100644 index 00000000..845b8c5f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stash.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stepwise.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stepwise.cpython-312.pyc new file mode 100644 index 00000000..78fa76f7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/stepwise.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc new file mode 100644 index 00000000..23ea2204 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/terminal.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc new file mode 100644 index 00000000..450a2227 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/threadexception.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/timing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/timing.cpython-312.pyc new file mode 100644 index 00000000..5d2b6021 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/timing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/tmpdir.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/tmpdir.cpython-312.pyc new file mode 100644 index 00000000..206131f6 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/tmpdir.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc new file mode 100644 index 00000000..9210cb0b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unittest.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc new file mode 100644 index 00000000..175711d3 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/unraisableexception.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc new file mode 100644 index 00000000..80cf4ea1 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warning_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warnings.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warnings.cpython-312.pyc new file mode 100644 index 00000000..de7bd254 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/__pycache__/warnings.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_argcomplete.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_argcomplete.py index 59426ef9..6a808377 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_argcomplete.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_argcomplete.py @@ -61,14 +61,13 @@ If things do not work right away: which should throw a KeyError: 'COMPLINE' (which is properly set by the global argcomplete script). """ - -from __future__ import annotations - import argparse -from glob import glob import os import sys +from glob import glob from typing import Any +from typing import List +from typing import Optional class FastFilesCompleter: @@ -77,7 +76,7 @@ class FastFilesCompleter: def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix: str, **kwargs: Any) -> list[str]: + def __call__(self, prefix: str, **kwargs: Any) -> List[str]: # Only called on non option completions. if os.sep in prefix[1:]: prefix_dir = len(os.path.dirname(prefix) + os.sep) @@ -104,7 +103,7 @@ if os.environ.get("_ARGCOMPLETE"): import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter: FastFilesCompleter | None = FastFilesCompleter() + filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter() def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__init__.py index 7f67a2e3..511d0dde 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__init__.py @@ -1,7 +1,4 @@ """Python inspection/code generation API.""" - -from __future__ import annotations - from .code import Code from .code import ExceptionInfo from .code import filter_traceback @@ -12,15 +9,14 @@ from .code import TracebackEntry from .source import getrawcode from .source import Source - __all__ = [ "Code", "ExceptionInfo", - "Frame", - "Source", - "Traceback", - "TracebackEntry", "filter_traceback", + "Frame", "getfslineno", "getrawcode", + "Traceback", + "TracebackEntry", + "Source", ] diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..3d2abcdd Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/code.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/code.cpython-312.pyc new file mode 100644 index 00000000..4fadc4cc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/code.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/source.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/source.cpython-312.pyc new file mode 100644 index 00000000..f280bac0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/__pycache__/source.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/code.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/code.py index add2a493..9b051332 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/code.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/code.py @@ -1,37 +1,36 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - import ast -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Mapping -from collections.abc import Sequence import dataclasses import inspect +import os +import re +import sys +import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS from io import StringIO -import os from pathlib import Path -import re -import sys -from traceback import extract_tb -from traceback import format_exception from traceback import format_exception_only -from traceback import FrameSummary from types import CodeType from types import FrameType from types import TracebackType from typing import Any +from typing import Callable from typing import ClassVar -from typing import Final -from typing import final +from typing import Dict from typing import Generic -from typing import Literal +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional from typing import overload -from typing import SupportsIndex -from typing import TypeAlias +from typing import Pattern +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING from typing import TypeVar +from typing import Union import pluggy @@ -43,19 +42,22 @@ from _pytest._code.source import Source from _pytest._io import TerminalWriter from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr +from _pytest.compat import final from _pytest.compat import get_real_func from _pytest.deprecated import check_ispytest from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath +if TYPE_CHECKING: + from typing_extensions import Final + from typing_extensions import Literal + from typing_extensions import SupportsIndex -if sys.version_info < (3, 11): + _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] + +if sys.version_info[:2] < (3, 11): from exceptiongroup import BaseExceptionGroup -TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] - -EXCEPTION_OR_MORE = type[BaseException] | tuple[type[BaseException], ...] - class Code: """Wrapper around Python code objects.""" @@ -66,7 +68,7 @@ class Code: self.raw = obj @classmethod - def from_function(cls, obj: object) -> Code: + def from_function(cls, obj: object) -> "Code": return cls(getrawcode(obj)) def __eq__(self, other): @@ -84,7 +86,7 @@ class Code: return self.raw.co_name @property - def path(self) -> Path | str: + def path(self) -> Union[Path, str]: """Return a path object pointing to source code, or an ``str`` in case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: @@ -101,17 +103,17 @@ class Code: return self.raw.co_filename @property - def fullsource(self) -> Source | None: + def fullsource(self) -> Optional["Source"]: """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) return full - def source(self) -> Source: + def source(self) -> "Source": """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code return Source(self.raw) - def getargs(self, var: bool = False) -> tuple[str, ...]: + def getargs(self, var: bool = False) -> Tuple[str, ...]: """Return a tuple with the argument names for the code object. If 'var' is set True also return the names of the variable and @@ -140,11 +142,11 @@ class Frame: return self.raw.f_lineno - 1 @property - def f_globals(self) -> dict[str, Any]: + def f_globals(self) -> Dict[str, Any]: return self.raw.f_globals @property - def f_locals(self) -> dict[str, Any]: + def f_locals(self) -> Dict[str, Any]: return self.raw.f_locals @property @@ -152,7 +154,7 @@ class Frame: return Code(self.raw.f_code) @property - def statement(self) -> Source: + def statement(self) -> "Source": """Statement this frame is at.""" if self.code.fullsource is None: return Source("") @@ -196,59 +198,20 @@ class TracebackEntry: def __init__( self, rawentry: TracebackType, - repr_style: Literal["short", "long"] | None = None, + repr_style: Optional['Literal["short", "long"]'] = None, ) -> None: - self._rawentry: Final = rawentry - self._repr_style: Final = repr_style + self._rawentry: "Final" = rawentry + self._repr_style: "Final" = repr_style def with_repr_style( - self, repr_style: Literal["short", "long"] | None - ) -> TracebackEntry: + self, repr_style: Optional['Literal["short", "long"]'] + ) -> "TracebackEntry": return TracebackEntry(self._rawentry, repr_style) @property def lineno(self) -> int: return self._rawentry.tb_lineno - 1 - def get_python_framesummary(self) -> FrameSummary: - # Python's built-in traceback module implements all the nitty gritty - # details to get column numbers of out frames. - stack_summary = extract_tb(self._rawentry, limit=1) - return stack_summary[0] - - # Column and end line numbers introduced in python 3.11 - if sys.version_info < (3, 11): - - @property - def end_lineno_relative(self) -> int | None: - return None - - @property - def colno(self) -> int | None: - return None - - @property - def end_colno(self) -> int | None: - return None - else: - - @property - def end_lineno_relative(self) -> int | None: - frame_summary = self.get_python_framesummary() - if frame_summary.end_lineno is None: # pragma: no cover - return None - return frame_summary.end_lineno - 1 - self.frame.code.firstlineno - - @property - def colno(self) -> int | None: - """Starting byte offset of the expression in the traceback entry.""" - return self.get_python_framesummary().colno - - @property - def end_colno(self) -> int | None: - """Ending byte offset of the expression in the traceback entry.""" - return self.get_python_framesummary().end_colno - @property def frame(self) -> Frame: return Frame(self._rawentry.tb_frame) @@ -258,22 +221,22 @@ class TracebackEntry: return self.lineno - self.frame.code.firstlineno def __repr__(self) -> str: - return f"" + return "" % (self.frame.code.path, self.lineno + 1) @property - def statement(self) -> Source: + def statement(self) -> "Source": """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource assert source is not None return source.getstatement(self.lineno) @property - def path(self) -> Path | str: + def path(self) -> Union[Path, str]: """Path to the source code.""" return self.frame.code.path @property - def locals(self) -> dict[str, Any]: + def locals(self) -> Dict[str, Any]: """Locals of underlying frame.""" return self.frame.f_locals @@ -281,8 +244,8 @@ class TracebackEntry: return self.frame.code.firstlineno def getsource( - self, astcache: dict[str | Path, ast.AST] | None = None - ) -> Source | None: + self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None + ) -> Optional["Source"]: """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing @@ -308,7 +271,7 @@ class TracebackEntry: source = property(getsource) - def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: + def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -317,7 +280,9 @@ class TracebackEntry: Mostly for internal use. """ - tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False + tbh: Union[ + bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool] + ] = False for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): # in normal cases, f_locals and f_globals are dictionaries # however via `exec(...)` / `eval(...)` they can be other types @@ -344,7 +309,12 @@ class TracebackEntry: # This output does not quite match Python's repr for traceback entries, # but changing it to do so would break certain plugins. See # https://github.com/pytest-dev/pytest/pull/7535/ for details. - return f" File '{self.path}':{self.lineno + 1} in {name}\n {line}\n" + return " File %r:%d in %s\n %s\n" % ( + str(self.path), + self.lineno + 1, + name, + line, + ) @property def name(self) -> str: @@ -352,18 +322,18 @@ class TracebackEntry: return self.frame.code.raw.co_name -class Traceback(list[TracebackEntry]): +class Traceback(List[TracebackEntry]): """Traceback objects encapsulate and offer higher level access to Traceback entries.""" def __init__( self, - tb: TracebackType | Iterable[TracebackEntry], + tb: Union[TracebackType, Iterable[TracebackEntry]], ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: TracebackType | None = cur + cur_: Optional[TracebackType] = cur while cur_ is not None: yield TracebackEntry(cur_) cur_ = cur_.tb_next @@ -374,11 +344,11 @@ class Traceback(list[TracebackEntry]): def cut( self, - path: os.PathLike[str] | str | None = None, - lineno: int | None = None, - firstlineno: int | None = None, - excludepath: os.PathLike[str] | None = None, - ) -> Traceback: + path: Optional[Union["os.PathLike[str]", str]] = None, + lineno: Optional[int] = None, + firstlineno: Optional[int] = None, + excludepath: Optional["os.PathLike[str]"] = None, + ) -> "Traceback": """Return a Traceback instance wrapping part of this Traceback. By providing any combination of path, lineno and firstlineno, the @@ -409,12 +379,16 @@ class Traceback(list[TracebackEntry]): return self @overload - def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... + def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: + ... @overload - def __getitem__(self, key: slice) -> Traceback: ... + def __getitem__(self, key: slice) -> "Traceback": + ... - def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: + def __getitem__( + self, key: Union["SupportsIndex", slice] + ) -> Union[TracebackEntry, "Traceback"]: if isinstance(key, slice): return self.__class__(super().__getitem__(key)) else: @@ -422,9 +396,12 @@ class Traceback(list[TracebackEntry]): def filter( self, - excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], - /, - ) -> Traceback: + # TODO(py38): change to positional only. + _excinfo_or_fn: Union[ + "ExceptionInfo[BaseException]", + Callable[[TracebackEntry], bool], + ], + ) -> "Traceback": """Return a Traceback instance with certain items removed. If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s @@ -434,60 +411,34 @@ class Traceback(list[TracebackEntry]): ``TracebackEntry`` instance, and should return True when the item should be added to the ``Traceback``, False when not. """ - if isinstance(excinfo_or_fn, ExceptionInfo): - fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731 + if isinstance(_excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(_excinfo_or_fn) # noqa: E731 else: - fn = excinfo_or_fn + fn = _excinfo_or_fn return Traceback(filter(fn, self)) - def recursionindex(self) -> int | None: + def recursionindex(self) -> Optional[int]: """Return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred.""" - cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} + cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi # which generates code objects that have hash/value equality # XXX needs a test key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno + # print "checking for recursion at", key values = cache.setdefault(key, []) - # Since Python 3.13 f_locals is a proxy, freeze it. - loc = dict(entry.frame.f_locals) if values: + f = entry.frame + loc = f.f_locals for otherloc in values: if otherloc == loc: return i - values.append(loc) + values.append(entry.frame.f_locals) return None -def stringify_exception( - exc: BaseException, include_subexception_msg: bool = True -) -> str: - try: - notes = getattr(exc, "__notes__", []) - except KeyError: - # Workaround for https://github.com/python/cpython/issues/98778 on - # some 3.10 and 3.11 patch versions. - HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) - if sys.version_info < (3, 12) and isinstance(exc, HTTPError): - notes = [] - else: # pragma: no cover - # exception not related to above bug, reraise - raise - if not include_subexception_msg and isinstance(exc, BaseExceptionGroup): - message = exc.message - else: - message = str(exc) - - return "\n".join( - [ - message, - *notes, - ] - ) - - E = TypeVar("E", bound=BaseException, covariant=True) @@ -498,15 +449,15 @@ class ExceptionInfo(Generic[E]): _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo: tuple[type[E], E, TracebackType] | None + _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] _striptext: str - _traceback: Traceback | None + _traceback: Optional[Traceback] def __init__( self, - excinfo: tuple[type[E], E, TracebackType] | None, + excinfo: Optional[Tuple[Type["E"], "E", TracebackType]], striptext: str = "", - traceback: Traceback | None = None, + traceback: Optional[Traceback] = None, *, _ispytest: bool = False, ) -> None: @@ -522,8 +473,8 @@ class ExceptionInfo(Generic[E]): # This is OK to ignore because this class is (conceptually) readonly. # See https://github.com/python/mypy/issues/7049. exception: E, # type: ignore[misc] - exprinfo: str | None = None, - ) -> ExceptionInfo[E]: + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[E]": """Return an ExceptionInfo for an existing exception. The exception must have a non-``None`` ``__traceback__`` attribute, @@ -538,19 +489,18 @@ class ExceptionInfo(Generic[E]): .. versionadded:: 7.4 """ - assert exception.__traceback__, ( - "Exceptions passed to ExcInfo.from_exception(...)" - " must have a non-None __traceback__." - ) + assert ( + exception.__traceback__ + ), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__." exc_info = (type(exception), exception, exception.__traceback__) return cls.from_exc_info(exc_info, exprinfo) @classmethod def from_exc_info( cls, - exc_info: tuple[type[E], E, TracebackType], - exprinfo: str | None = None, - ) -> ExceptionInfo[E]: + exc_info: Tuple[Type[E], E, TracebackType], + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[E]": """Like :func:`from_exception`, but using old-style exc_info tuple.""" _striptext = "" if exprinfo is None and isinstance(exc_info[1], AssertionError): @@ -563,7 +513,9 @@ class ExceptionInfo(Generic[E]): return cls(exc_info, _striptext, _ispytest=True) @classmethod - def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: + def from_current( + cls, exprinfo: Optional[str] = None + ) -> "ExceptionInfo[BaseException]": """Return an ExceptionInfo matching the current traceback. .. warning:: @@ -583,45 +535,45 @@ class ExceptionInfo(Generic[E]): return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod - def for_later(cls) -> ExceptionInfo[E]: + def for_later(cls) -> "ExceptionInfo[E]": """Return an unfilled ExceptionInfo.""" return cls(None, _ispytest=True) - def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: + def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None: """Fill an unfilled ExceptionInfo created with ``for_later()``.""" assert self._excinfo is None, "ExceptionInfo was already filled" self._excinfo = exc_info @property - def type(self) -> type[E]: + def type(self) -> Type[E]: """The exception class.""" - assert self._excinfo is not None, ( - ".type can only be used after the context manager exits" - ) + assert ( + self._excinfo is not None + ), ".type can only be used after the context manager exits" return self._excinfo[0] @property def value(self) -> E: """The exception value.""" - assert self._excinfo is not None, ( - ".value can only be used after the context manager exits" - ) + assert ( + self._excinfo is not None + ), ".value can only be used after the context manager exits" return self._excinfo[1] @property def tb(self) -> TracebackType: """The exception raw traceback.""" - assert self._excinfo is not None, ( - ".tb can only be used after the context manager exits" - ) + assert ( + self._excinfo is not None + ), ".tb can only be used after the context manager exits" return self._excinfo[2] @property def typename(self) -> str: """The type name of the exception.""" - assert self._excinfo is not None, ( - ".typename can only be used after the context manager exits" - ) + assert ( + self._excinfo is not None + ), ".typename can only be used after the context manager exits" return self.type.__name__ @property @@ -638,7 +590,9 @@ class ExceptionInfo(Generic[E]): def __repr__(self) -> str: if self._excinfo is None: return "" - return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>" + return "<{} {} tblen={}>".format( + self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) + ) def exconly(self, tryshort: bool = False) -> str: """Return the exception as a string. @@ -648,23 +602,6 @@ class ExceptionInfo(Generic[E]): representation is returned (so 'AssertionError: ' is removed from the beginning). """ - - def _get_single_subexc( - eg: BaseExceptionGroup[BaseException], - ) -> BaseException | None: - if len(eg.exceptions) != 1: - return None - if isinstance(e := eg.exceptions[0], BaseExceptionGroup): - return _get_single_subexc(e) - return e - - if ( - tryshort - and isinstance(self.value, BaseExceptionGroup) - and (subexc := _get_single_subexc(self.value)) is not None - ): - return f"{subexc!r} [single exception in {type(self.value).__name__}]" - lines = format_exception_only(self.type, self.value) text = "".join(lines) text = text.rstrip() @@ -673,14 +610,16 @@ class ExceptionInfo(Generic[E]): text = text[len(self._striptext) :] return text - def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: + def errisinstance( + self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] + ) -> bool: """Return True if the exception is an instance of exc. Consider using ``isinstance(excinfo.value, exc)`` instead. """ return isinstance(self.value, exc) - def _getreprcrash(self) -> ReprFileLocation | None: + def _getreprcrash(self) -> Optional["ReprFileLocation"]: # Find last non-hidden traceback entry that led to the exception of the # traceback, or None if all hidden. for i in range(-1, -len(self.traceback) - 1, -1): @@ -694,14 +633,15 @@ class ExceptionInfo(Generic[E]): def getrepr( self, showlocals: bool = False, - style: TracebackStyle = "long", + style: "_TracebackStyle" = "long", abspath: bool = False, - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, + tbfilter: Union[ + bool, Callable[["ExceptionInfo[BaseException]"], Traceback] + ] = True, funcargs: bool = False, truncate_locals: bool = True, - truncate_args: bool = True, chain: bool = True, - ) -> ReprExceptionInfo | ExceptionChainRepr: + ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: """Return str()able representation of this exception info. :param bool showlocals: @@ -730,9 +670,6 @@ class ExceptionInfo(Generic[E]): :param bool truncate_locals: With ``showlocals==True``, make sure locals can be safely represented as strings. - :param bool truncate_args: - With ``showargs==True``, make sure args can be safely represented as strings. - :param bool chain: If chained exceptions in Python 3 should be shown. @@ -743,7 +680,7 @@ class ExceptionInfo(Generic[E]): if style == "native": return ReprExceptionInfo( reprtraceback=ReprTracebackNative( - format_exception( + traceback.format_exception( self.type, self.value, self.traceback[0]._rawentry if self.traceback else None, @@ -759,108 +696,25 @@ class ExceptionInfo(Generic[E]): tbfilter=tbfilter, funcargs=funcargs, truncate_locals=truncate_locals, - truncate_args=truncate_args, chain=chain, ) return fmt.repr_excinfo(self) - def match(self, regexp: str | re.Pattern[str]) -> Literal[True]: + def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - value = stringify_exception(self.value) - msg = ( - f"Regex pattern did not match.\n" - f" Expected regex: {regexp!r}\n" - f" Actual message: {value!r}" - ) + value = str(self.value) + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" if regexp == value: msg += "\n Did you mean to `re.escape()` the regex?" assert re.search(regexp, value), msg # Return True to allow for "assert excinfo.match()". return True - def _group_contains( - self, - exc_group: BaseExceptionGroup[BaseException], - expected_exception: EXCEPTION_OR_MORE, - match: str | re.Pattern[str] | None, - target_depth: int | None = None, - current_depth: int = 1, - ) -> bool: - """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" - if (target_depth is not None) and (current_depth > target_depth): - # already descended past the target depth - return False - for exc in exc_group.exceptions: - if isinstance(exc, BaseExceptionGroup): - if self._group_contains( - exc, expected_exception, match, target_depth, current_depth + 1 - ): - return True - if (target_depth is not None) and (current_depth != target_depth): - # not at the target depth, no match - continue - if not isinstance(exc, expected_exception): - continue - if match is not None: - value = stringify_exception(exc) - if not re.search(match, value): - continue - return True - return False - - def group_contains( - self, - expected_exception: EXCEPTION_OR_MORE, - *, - match: str | re.Pattern[str] | None = None, - depth: int | None = None, - ) -> bool: - """Check whether a captured exception group contains a matching exception. - - :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception: - The expected exception type, or a tuple if one of multiple possible - exception types are expected. - - :param str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception and its `PEP-678 ` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - :param Optional[int] depth: - If `None`, will search for a matching exception at any nesting depth. - If >= 1, will only match an exception if it's at the specified depth (depth = 1 being - the exceptions contained within the topmost exception group). - - .. versionadded:: 8.0 - - .. warning:: - This helper makes it easy to check for the presence of specific exceptions, - but it is very bad for checking that the group does *not* contain - *any other exceptions*. - You should instead consider using :class:`pytest.RaisesGroup` - - """ - msg = "Captured exception is not an instance of `BaseExceptionGroup`" - assert isinstance(self.value, BaseExceptionGroup), msg - msg = "`depth` must be >= 1 if specified" - assert (depth is None) or (depth >= 1), msg - return self._group_contains(self.value, expected_exception, match, depth) - - -# Type alias for the `tbfilter` setting: -# bool: If True, it should be filtered using Traceback.filter() -# callable: A callable that takes an ExceptionInfo and returns the filtered traceback. -TracebackFilter: TypeAlias = bool | Callable[[ExceptionInfo[BaseException]], Traceback] - @dataclasses.dataclass class FormattedExcinfo: @@ -871,18 +725,17 @@ class FormattedExcinfo: fail_marker: ClassVar = "E" showlocals: bool = False - style: TracebackStyle = "long" + style: "_TracebackStyle" = "long" abspath: bool = True - tbfilter: TracebackFilter = True + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True funcargs: bool = False truncate_locals: bool = True - truncate_args: bool = True chain: bool = True - astcache: dict[str | Path, ast.AST] = dataclasses.field( + astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False ) - def _getindent(self, source: Source) -> int: + def _getindent(self, source: "Source") -> int: # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) @@ -897,34 +750,27 @@ class FormattedExcinfo: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Source | None: + def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: + def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): - if self.truncate_args: - str_repr = saferepr(argvalue) - else: - str_repr = saferepr(argvalue, maxsize=None) - args.append((argname, str_repr)) + args.append((argname, saferepr(argvalue))) return ReprFuncArgs(args) return None def get_source( self, - source: Source | None, + source: Optional["Source"], line_index: int = -1, - excinfo: ExceptionInfo[BaseException] | None = None, + excinfo: Optional[ExceptionInfo[BaseException]] = None, short: bool = False, - end_line_index: int | None = None, - colno: int | None = None, - end_colno: int | None = None, - ) -> list[str]: + ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] if source is not None and line_index < 0: @@ -937,30 +783,10 @@ class FormattedExcinfo: space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) - lines.extend( - self.get_highlight_arrows_for_line( - raw_line=source.raw_lines[line_index], - line=source.lines[line_index].strip(), - lineno=line_index, - end_lineno=end_line_index, - colno=colno, - end_colno=end_colno, - ) - ) else: for line in source.lines[:line_index]: lines.append(space_prefix + line) lines.append(self.flow_marker + " " + source.lines[line_index]) - lines.extend( - self.get_highlight_arrows_for_line( - raw_line=source.raw_lines[line_index], - line=source.lines[line_index], - lineno=line_index, - end_lineno=end_line_index, - colno=colno, - end_colno=end_colno, - ) - ) for line in source.lines[line_index + 1 :]: lines.append(space_prefix + line) if excinfo is not None: @@ -968,49 +794,12 @@ class FormattedExcinfo: lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) return lines - def get_highlight_arrows_for_line( - self, - line: str, - raw_line: str, - lineno: int | None, - end_lineno: int | None, - colno: int | None, - end_colno: int | None, - ) -> list[str]: - """Return characters highlighting a source line. - - Example with colno and end_colno pointing to the bar expression: - "foo() + bar()" - returns " ^^^^^" - """ - if lineno != end_lineno: - # Don't handle expressions that span multiple lines. - return [] - if colno is None or end_colno is None: - # Can't do anything without column information. - return [] - - num_stripped_chars = len(raw_line) - len(line) - - start_char_offset = _byte_offset_to_character_offset(raw_line, colno) - end_char_offset = _byte_offset_to_character_offset(raw_line, end_colno) - num_carets = end_char_offset - start_char_offset - # If the highlight would span the whole line, it is redundant, don't - # show it. - if num_carets >= len(line.strip()): - return [] - - highlights = " " - highlights += " " * (start_char_offset - num_stripped_chars + 1) - highlights += "^" * num_carets - return [highlights] - def get_exconly( self, excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, - ) -> list[str]: + ) -> List[str]: lines = [] indentstr = " " * indent # Get the real exception information out. @@ -1022,7 +811,7 @@ class FormattedExcinfo: failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: + def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -1050,10 +839,10 @@ class FormattedExcinfo: def repr_traceback_entry( self, - entry: TracebackEntry | None, - excinfo: ExceptionInfo[BaseException] | None = None, - ) -> ReprEntry: - lines: list[str] = [] + entry: Optional[TracebackEntry], + excinfo: Optional[ExceptionInfo[BaseException]] = None, + ) -> "ReprEntry": + lines: List[str] = [] style = ( entry._repr_style if entry is not None and entry._repr_style is not None @@ -1064,28 +853,16 @@ class FormattedExcinfo: if source is None: source = Source("???") line_index = 0 - end_line_index, colno, end_colno = None, None, None else: - line_index = entry.relline - end_line_index = entry.end_lineno_relative - colno = entry.colno - end_colno = entry.end_colno + line_index = entry.lineno - entry.getfirstlinesource() short = style == "short" reprargs = self.repr_args(entry) if not short else None - s = self.get_source( - source=source, - line_index=line_index, - excinfo=excinfo, - short=short, - end_line_index=end_line_index, - colno=colno, - end_colno=end_colno, - ) + s = self.get_source(source, line_index, excinfo, short=short) lines.extend(s) if short: - message = f"in {entry.name}" + message = "in %s" % (entry.name) else: - message = (excinfo and excinfo.typename) or "" + message = excinfo and excinfo.typename or "" entry_path = entry.path path = self._makepath(entry_path) reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) @@ -1100,7 +877,7 @@ class FormattedExcinfo: lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path: Path | str) -> str: + def _makepath(self, path: Union[Path, str]) -> str: if not self.abspath and isinstance(path, Path): try: np = bestrelpath(Path.cwd(), path) @@ -1110,8 +887,12 @@ class FormattedExcinfo: return np return str(path) - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: - traceback = filter_excinfo_traceback(self.tbfilter, excinfo) + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": + traceback = excinfo.traceback + if callable(self.tbfilter): + traceback = self.tbfilter(excinfo) + elif self.tbfilter: + traceback = traceback.filter(excinfo) if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) @@ -1137,7 +918,7 @@ class FormattedExcinfo: def _truncate_recursive_traceback( self, traceback: Traceback - ) -> tuple[Traceback, str | None]: + ) -> Tuple[Traceback, Optional[str]]: """Truncate the given recursive traceback trying to find the starting point of the recursion. @@ -1154,11 +935,16 @@ class FormattedExcinfo: recursionindex = traceback.recursionindex() except Exception as e: max_frames = 10 - extraline: str | None = ( + extraline: Optional[str] = ( "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" " The following exception happened when comparing locals in the stack frame:\n" - f" {type(e).__name__}: {e!s}\n" - f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." + " {exc_type}: {exc_msg}\n" + " Displaying first and last {max_frames} stack frames out of {total}." + ).format( + exc_type=type(e).__name__, + exc_msg=str(e), + max_frames=max_frames, + total=len(traceback), ) # Type ignored because adding two instances of a List subtype # currently incorrectly has type List instead of the subtype. @@ -1172,12 +958,16 @@ class FormattedExcinfo: return traceback, extraline - def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: - repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] - e: BaseException | None = excinfo.value - excinfo_: ExceptionInfo[BaseException] | None = excinfo + def repr_excinfo( + self, excinfo: ExceptionInfo[BaseException] + ) -> "ExceptionChainRepr": + repr_chain: List[ + Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] + ] = [] + e: Optional[BaseException] = excinfo.value + excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo descr = None - seen: set[int] = set() + seen: Set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) @@ -1185,15 +975,14 @@ class FormattedExcinfo: # Fall back to native traceback as a temporary workaround until # full support for exception groups added to ExceptionInfo. # See https://github.com/pytest-dev/pytest/issues/9159 - reprtraceback: ReprTraceback | ReprTracebackNative if isinstance(e, BaseExceptionGroup): - # don't filter any sub-exceptions since they shouldn't have any internal frames - traceback = filter_excinfo_traceback(self.tbfilter, excinfo) - reprtraceback = ReprTracebackNative( - format_exception( - type(excinfo.value), - excinfo.value, - traceback[0]._rawentry, + reprtraceback: Union[ + ReprTracebackNative, ReprTraceback + ] = ReprTracebackNative( + traceback.format_exception( + type(excinfo_.value), + excinfo_.value, + excinfo_.traceback[0]._rawentry, ) ) else: @@ -1202,7 +991,9 @@ class FormattedExcinfo: else: # Fallback to native repr if the exception doesn't have a traceback: # ExceptionInfo objects require a full traceback to work. - reprtraceback = ReprTracebackNative(format_exception(type(e), e, None)) + reprtraceback = ReprTracebackNative( + traceback.format_exception(type(e), e, None) + ) reprcrash = None repr_chain += [(reprtraceback, reprcrash, descr)] @@ -1243,9 +1034,9 @@ class TerminalRepr: @dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprtraceback: ReprTraceback - reprcrash: ReprFileLocation | None - sections: list[tuple[str, str, str]] = dataclasses.field( + reprtraceback: "ReprTraceback" + reprcrash: Optional["ReprFileLocation"] + sections: List[Tuple[str, str, str]] = dataclasses.field( init=False, default_factory=list ) @@ -1260,11 +1051,13 @@ class ExceptionRepr(TerminalRepr): @dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] + chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] def __init__( self, - chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], + chain: Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ], ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. @@ -1285,8 +1078,8 @@ class ExceptionChainRepr(ExceptionRepr): @dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback: ReprTraceback - reprcrash: ReprFileLocation | None + reprtraceback: "ReprTraceback" + reprcrash: Optional["ReprFileLocation"] def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1295,9 +1088,9 @@ class ReprExceptionInfo(ExceptionRepr): @dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): - reprentries: Sequence[ReprEntry | ReprEntryNative] - extraline: str | None - style: TracebackStyle + reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] + extraline: Optional[str] + style: "_TracebackStyle" entrysep: ClassVar = "_ " @@ -1309,8 +1102,10 @@ class ReprTraceback(TerminalRepr): entry.toterminal(tw) if i < len(self.reprentries) - 1: next_entry = self.reprentries[i + 1] - if entry.style == "long" or ( - entry.style == "short" and next_entry.style == "long" + if ( + entry.style == "long" + or entry.style == "short" + and next_entry.style == "long" ): tw.sep(self.entrysep) @@ -1329,7 +1124,7 @@ class ReprTracebackNative(ReprTraceback): class ReprEntryNative(TerminalRepr): lines: Sequence[str] - style: ClassVar[TracebackStyle] = "native" + style: ClassVar["_TracebackStyle"] = "native" def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) @@ -1338,10 +1133,10 @@ class ReprEntryNative(TerminalRepr): @dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] - reprfuncargs: ReprFuncArgs | None - reprlocals: ReprLocals | None - reprfileloc: ReprFileLocation | None - style: TracebackStyle + reprfuncargs: Optional["ReprFuncArgs"] + reprlocals: Optional["ReprLocals"] + reprfileloc: Optional["ReprFileLocation"] + style: "_TracebackStyle" def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. @@ -1356,16 +1151,8 @@ class ReprEntry(TerminalRepr): the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" character, as doing so might break line continuations. """ - if not self.lines: - return - if self.style == "value": - # Using tw.write instead of tw.line for testing purposes due to TWMock implementation; - # lines written with TWMock.line and TWMock._write_source cannot be distinguished - # from each other, whereas lines written with TWMock.write are marked with TWMock.WRITE - for line in self.lines: - tw.write(line) - tw.write("\n") + if not self.lines: return # separate indents and source lines that are not failures: we want to @@ -1373,9 +1160,9 @@ class ReprEntry(TerminalRepr): # such as "> assert 0" fail_marker = f"{FormattedExcinfo.fail_marker} " indent_size = len(fail_marker) - indents: list[str] = [] - source_lines: list[str] = [] - failure_lines: list[str] = [] + indents: List[str] = [] + source_lines: List[str] = [] + failure_lines: List[str] = [] for index, line in enumerate(self.lines): is_failure_line = line.startswith(fail_marker) if is_failure_line: @@ -1383,8 +1170,11 @@ class ReprEntry(TerminalRepr): failure_lines.extend(self.lines[index:]) break else: - indents.append(line[:indent_size]) - source_lines.append(line[indent_size:]) + if self.style == "value": + source_lines.append(line) + else: + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) tw._write_source(source_lines, indents) @@ -1451,7 +1241,7 @@ class ReprLocals(TerminalRepr): @dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): - args: Sequence[tuple[str, object]] + args: Sequence[Tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: @@ -1472,7 +1262,7 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getfslineno(obj: object) -> tuple[str | Path, int]: +def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: """Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1). @@ -1484,7 +1274,7 @@ def getfslineno(obj: object) -> tuple[str | Path, int]: # in 6ec13a2b9. It ("place_as") appears to be something very custom. obj = get_real_func(obj) if hasattr(obj, "place_as"): - obj = obj.place_as + obj = obj.place_as # type: ignore[attr-defined] try: code = Code.from_function(obj) @@ -1494,7 +1284,7 @@ def getfslineno(obj: object) -> tuple[str | Path, int]: except TypeError: return "", -1 - fspath = (fn and absolutepath(fn)) or "" + fspath = fn and absolutepath(fn) or "" lineno = -1 if fspath: try: @@ -1506,12 +1296,6 @@ def getfslineno(obj: object) -> tuple[str | Path, int]: return code.path, code.firstlineno -def _byte_offset_to_character_offset(str, offset): - """Converts a byte based offset in a string to a code-point.""" - as_utf8 = str.encode("utf-8") - return len(as_utf8[:offset].decode("utf-8", errors="replace")) - - # Relative paths that we use to filter traceback entries from appearing to the user; # see filter_traceback. # note: if we need to add more paths than what we have now we should probably use a list @@ -1551,15 +1335,3 @@ def filter_traceback(entry: TracebackEntry) -> bool: return False return True - - -def filter_excinfo_traceback( - tbfilter: TracebackFilter, excinfo: ExceptionInfo[BaseException] -) -> Traceback: - """Filter the exception traceback in ``excinfo`` according to ``tbfilter``.""" - if callable(tbfilter): - return tbfilter(excinfo) - elif tbfilter: - return excinfo.traceback.filter(excinfo) - else: - return excinfo.traceback diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/source.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/source.py index 99c242dd..208cfb80 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_code/source.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_code/source.py @@ -1,16 +1,17 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - import ast -from bisect import bisect_right -from collections.abc import Iterable -from collections.abc import Iterator import inspect import textwrap import tokenize import types -from typing import overload import warnings +from bisect import bisect_right +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import overload +from typing import Tuple +from typing import Union class Source: @@ -21,17 +22,13 @@ class Source: def __init__(self, obj: object = None) -> None: if not obj: - self.lines: list[str] = [] - self.raw_lines: list[str] = [] + self.lines: List[str] = [] elif isinstance(obj, Source): self.lines = obj.lines - self.raw_lines = obj.raw_lines - elif isinstance(obj, tuple | list): + elif isinstance(obj, (tuple, list)): self.lines = deindent(x.rstrip("\n") for x in obj) - self.raw_lines = list(x.rstrip("\n") for x in obj) elif isinstance(obj, str): self.lines = deindent(obj.split("\n")) - self.raw_lines = obj.split("\n") else: try: rawcode = getrawcode(obj) @@ -39,7 +36,6 @@ class Source: except TypeError: src = inspect.getsource(obj) # type: ignore[arg-type] self.lines = deindent(src.split("\n")) - self.raw_lines = src.split("\n") def __eq__(self, other: object) -> bool: if not isinstance(other, Source): @@ -50,12 +46,14 @@ class Source: __hash__ = None # type: ignore @overload - def __getitem__(self, key: int) -> str: ... + def __getitem__(self, key: int) -> str: + ... @overload - def __getitem__(self, key: slice) -> Source: ... + def __getitem__(self, key: slice) -> "Source": + ... - def __getitem__(self, key: int | slice) -> str | Source: + def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: if isinstance(key, int): return self.lines[key] else: @@ -63,7 +61,6 @@ class Source: raise IndexError("cannot slice a Source with a step") newsource = Source() newsource.lines = self.lines[key.start : key.stop] - newsource.raw_lines = self.raw_lines[key.start : key.stop] return newsource def __iter__(self) -> Iterator[str]: @@ -72,7 +69,7 @@ class Source: def __len__(self) -> int: return len(self.lines) - def strip(self) -> Source: + def strip(self) -> "Source": """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): @@ -80,37 +77,34 @@ class Source: while end > start and not self.lines[end - 1].strip(): end -= 1 source = Source() - source.raw_lines = self.raw_lines source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> Source: + def indent(self, indent: str = " " * 4) -> "Source": """Return a copy of the source object with all lines indented by the given indent-string.""" newsource = Source() - newsource.raw_lines = self.raw_lines newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> Source: + def getstatement(self, lineno: int) -> "Source": """Return Source statement which contains the given linenumber (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno: int) -> tuple[int, int]: + def getstatementrange(self, lineno: int) -> Tuple[int, int]: """Return (start, end) tuple which spans the minimal statement region which containing the given lineno.""" if not (0 <= lineno < len(self)): raise IndexError("lineno out of range") - _ast, start, end = getstatementrange_ast(lineno, self) + ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> Source: + def deindent(self) -> "Source": """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) - newsource.raw_lines = self.raw_lines return newsource def __str__(self) -> str: @@ -122,14 +116,13 @@ class Source: # -def findsource(obj) -> tuple[Source | None, int]: +def findsource(obj) -> Tuple[Optional[Source], int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: return None, -1 source = Source() source.lines = [line.rstrip() for line in sourcelines] - source.raw_lines = sourcelines return source, lineno @@ -146,23 +139,24 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> list[str]: +def deindent(lines: Iterable[str]) -> List[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: +def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: # Flatten all statements and except handlers into one lineno-list. # AST's line numbers start indexing at 1. - values: list[int] = [] + values: List[int] = [] for x in ast.walk(node): - if isinstance(x, ast.stmt | ast.ExceptHandler): - # The lineno points to the class/def, so need to include the decorators. - if isinstance(x, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef): + if isinstance(x, (ast.stmt, ast.ExceptHandler)): + # Before Python 3.8, the lineno of a decorated class or function pointed at the decorator. + # Since Python 3.8, the lineno points to the class/def, so need to include the decorators. + if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): for d in x.decorator_list: values.append(d.lineno - 1) values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val: list[ast.stmt] | None = getattr(x, name, None) + val: Optional[List[ast.stmt]] = getattr(x, name, None) if val: # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) @@ -180,8 +174,8 @@ def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, - astnode: ast.AST | None = None, -) -> tuple[ast.AST, int, int]: + astnode: Optional[ast.AST] = None, +) -> Tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: @@ -203,9 +197,7 @@ def getstatementrange_ast( # by using the BlockFinder helper used which inspect.getsource() uses itself. block_finder = inspect.BlockFinder() # If we start with an indented line, put blockfinder to "started" mode. - block_finder.started = ( - bool(source.lines[start]) and source.lines[start][0].isspace() - ) + block_finder.started = source.lines[start][0].isspace() it = ((x + "\n") for x in source.lines[start:end]) try: for tok in tokenize.generate_tokens(lambda: next(it)): diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__init__.py index b0155b18..db001e91 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__init__.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from .terminalwriter import get_terminal_width from .terminalwriter import TerminalWriter diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..84016161 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc new file mode 100644 index 00000000..1684c216 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/saferepr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc new file mode 100644 index 00000000..1357d70f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc new file mode 100644 index 00000000..876576b0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/__pycache__/wcwidth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/pprint.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/pprint.py deleted file mode 100644 index 28f06909..00000000 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/pprint.py +++ /dev/null @@ -1,673 +0,0 @@ -# mypy: allow-untyped-defs -# This module was imported from the cpython standard library -# (https://github.com/python/cpython/) at commit -# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12). -# -# -# Original Author: Fred L. Drake, Jr. -# fdrake@acm.org -# -# This is a simple little module I wrote to make life easier. I didn't -# see anything quite like it in the library, though I may have overlooked -# something. I wrote this when I was trying to read some heavily nested -# tuples with fairly non-descriptive content. This is modeled very much -# after Lisp/Scheme - style pretty-printing of lists. If you find it -# useful, thank small children who sleep at night. -from __future__ import annotations - -import collections as _collections -from collections.abc import Callable -from collections.abc import Iterator -import dataclasses as _dataclasses -from io import StringIO as _StringIO -import re -import types as _types -from typing import Any -from typing import IO - - -class _safe_key: - """Helper function for key functions when sorting unorderable objects. - - The wrapped-object will fallback to a Py2.x style comparison for - unorderable types (sorting first comparing the type name and then by - the obj ids). Does not work recursively, so dict.items() must have - _safe_key applied to both the key and the value. - - """ - - __slots__ = ["obj"] - - def __init__(self, obj): - self.obj = obj - - def __lt__(self, other): - try: - return self.obj < other.obj - except TypeError: - return (str(type(self.obj)), id(self.obj)) < ( - str(type(other.obj)), - id(other.obj), - ) - - -def _safe_tuple(t): - """Helper function for comparing 2-tuples""" - return _safe_key(t[0]), _safe_key(t[1]) - - -class PrettyPrinter: - def __init__( - self, - indent: int = 4, - width: int = 80, - depth: int | None = None, - ) -> None: - """Handle pretty printing operations onto a stream using a set of - configured parameters. - - indent - Number of spaces to indent for each level of nesting. - - width - Attempted maximum number of columns in the output. - - depth - The maximum depth to print out nested structures. - - """ - if indent < 0: - raise ValueError("indent must be >= 0") - if depth is not None and depth <= 0: - raise ValueError("depth must be > 0") - if not width: - raise ValueError("width must be != 0") - self._depth = depth - self._indent_per_level = indent - self._width = width - - def pformat(self, object: Any) -> str: - sio = _StringIO() - self._format(object, sio, 0, 0, set(), 0) - return sio.getvalue() - - def _format( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - objid = id(object) - if objid in context: - stream.write(_recursion(object)) - return - - p = self._dispatch.get(type(object).__repr__, None) - if p is not None: - context.add(objid) - p(self, object, stream, indent, allowance, context, level + 1) - context.remove(objid) - elif ( - _dataclasses.is_dataclass(object) - and not isinstance(object, type) - and object.__dataclass_params__.repr # type:ignore[attr-defined] - and - # Check dataclass has generated repr method. - hasattr(object.__repr__, "__wrapped__") - and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ - ): - context.add(objid) - self._pprint_dataclass( - object, stream, indent, allowance, context, level + 1 - ) - context.remove(objid) - else: - stream.write(self._repr(object, context, level)) - - def _pprint_dataclass( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - cls_name = object.__class__.__name__ - items = [ - (f.name, getattr(object, f.name)) - for f in _dataclasses.fields(object) - if f.repr - ] - stream.write(cls_name + "(") - self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch: dict[ - Callable[..., str], - Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], - ] = {} - - def _pprint_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - write("{") - items = sorted(object.items(), key=_safe_tuple) - self._format_dict_items(items, stream, indent, allowance, context, level) - write("}") - - _dispatch[dict.__repr__] = _pprint_dict - - def _pprint_ordered_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "(") - self._pprint_dict(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict - - def _pprint_list( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("[") - self._format_items(object, stream, indent, allowance, context, level) - stream.write("]") - - _dispatch[list.__repr__] = _pprint_list - - def _pprint_tuple( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("(") - self._format_items(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[tuple.__repr__] = _pprint_tuple - - def _pprint_set( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object): - stream.write(repr(object)) - return - typ = object.__class__ - if typ is set: - stream.write("{") - endchar = "}" - else: - stream.write(typ.__name__ + "({") - endchar = "})" - object = sorted(object, key=_safe_key) - self._format_items(object, stream, indent, allowance, context, level) - stream.write(endchar) - - _dispatch[set.__repr__] = _pprint_set - _dispatch[frozenset.__repr__] = _pprint_set - - def _pprint_str( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - if not len(object): - write(repr(object)) - return - chunks = [] - lines = object.splitlines(True) - if level == 1: - indent += 1 - allowance += 1 - max_width1 = max_width = self._width - indent - for i, line in enumerate(lines): - rep = repr(line) - if i == len(lines) - 1: - max_width1 -= allowance - if len(rep) <= max_width1: - chunks.append(rep) - else: - # A list of alternating (non-space, space) strings - parts = re.findall(r"\S*\s*", line) - assert parts - assert not parts[-1] - parts.pop() # drop empty last part - max_width2 = max_width - current = "" - for j, part in enumerate(parts): - candidate = current + part - if j == len(parts) - 1 and i == len(lines) - 1: - max_width2 -= allowance - if len(repr(candidate)) > max_width2: - if current: - chunks.append(repr(current)) - current = part - else: - current = candidate - if current: - chunks.append(repr(current)) - if len(chunks) == 1: - write(rep) - return - if level == 1: - write("(") - for i, rep in enumerate(chunks): - if i > 0: - write("\n" + " " * indent) - write(rep) - if level == 1: - write(")") - - _dispatch[str.__repr__] = _pprint_str - - def _pprint_bytes( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - if len(object) <= 4: - write(repr(object)) - return - parens = level == 1 - if parens: - indent += 1 - allowance += 1 - write("(") - delim = "" - for rep in _wrap_bytes_repr(object, self._width - indent, allowance): - write(delim) - write(rep) - if not delim: - delim = "\n" + " " * indent - if parens: - write(")") - - _dispatch[bytes.__repr__] = _pprint_bytes - - def _pprint_bytearray( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - write = stream.write - write("bytearray(") - self._pprint_bytes( - bytes(object), stream, indent + 10, allowance + 1, context, level + 1 - ) - write(")") - - _dispatch[bytearray.__repr__] = _pprint_bytearray - - def _pprint_mappingproxy( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write("mappingproxy(") - self._format(object.copy(), stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy - - def _pprint_simplenamespace( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if type(object) is _types.SimpleNamespace: - # The SimpleNamespace repr is "namespace" instead of the class - # name, so we do the same here. For subclasses; use the class name. - cls_name = "namespace" - else: - cls_name = object.__class__.__name__ - items = object.__dict__.items() - stream.write(cls_name + "(") - self._format_namespace_items(items, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace - - def _format_dict_items( - self, - items: list[tuple[Any, Any]], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - for key, ent in items: - write(delimnl) - write(self._repr(key, context, level)) - write(": ") - self._format(ent, stream, item_indent, 1, context, level) - write(",") - - write("\n" + " " * indent) - - def _format_namespace_items( - self, - items: list[tuple[Any, Any]], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - for key, ent in items: - write(delimnl) - write(key) - write("=") - if id(ent) in context: - # Special-case representation of recursion to match standard - # recursive dataclass repr. - write("...") - else: - self._format( - ent, - stream, - item_indent + len(key) + 1, - 1, - context, - level, - ) - - write(",") - - write("\n" + " " * indent) - - def _format_items( - self, - items: list[Any], - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not items: - return - - write = stream.write - item_indent = indent + self._indent_per_level - delimnl = "\n" + " " * item_indent - - for item in items: - write(delimnl) - self._format(item, stream, item_indent, 1, context, level) - write(",") - - write("\n" + " " * indent) - - def _repr(self, object: Any, context: set[int], level: int) -> str: - return self._safe_repr(object, context.copy(), self._depth, level) - - def _pprint_default_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - rdf = self._repr(object.default_factory, context, level) - stream.write(f"{object.__class__.__name__}({rdf}, ") - self._pprint_dict(object, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict - - def _pprint_counter( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write(object.__class__.__name__ + "(") - - if object: - stream.write("{") - items = object.most_common() - self._format_dict_items(items, stream, indent, allowance, context, level) - stream.write("}") - - stream.write(")") - - _dispatch[_collections.Counter.__repr__] = _pprint_counter - - def _pprint_chain_map( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): - stream.write(repr(object)) - return - - stream.write(object.__class__.__name__ + "(") - self._format_items(object.maps, stream, indent, allowance, context, level) - stream.write(")") - - _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map - - def _pprint_deque( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - stream.write(object.__class__.__name__ + "(") - if object.maxlen is not None: - stream.write(f"maxlen={object.maxlen}, ") - stream.write("[") - - self._format_items(object, stream, indent, allowance + 1, context, level) - stream.write("])") - - _dispatch[_collections.deque.__repr__] = _pprint_deque - - def _pprint_user_dict( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict - - def _pprint_user_list( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserList.__repr__] = _pprint_user_list - - def _pprint_user_string( - self, - object: Any, - stream: IO[str], - indent: int, - allowance: int, - context: set[int], - level: int, - ) -> None: - self._format(object.data, stream, indent, allowance, context, level - 1) - - _dispatch[_collections.UserString.__repr__] = _pprint_user_string - - def _safe_repr( - self, object: Any, context: set[int], maxlevels: int | None, level: int - ) -> str: - typ = type(object) - if typ in _builtin_scalars: - return repr(object) - - r = getattr(typ, "__repr__", None) - - if issubclass(typ, dict) and r is dict.__repr__: - if not object: - return "{}" - objid = id(object) - if maxlevels and level >= maxlevels: - return "{...}" - if objid in context: - return _recursion(object) - context.add(objid) - components: list[str] = [] - append = components.append - level += 1 - for k, v in sorted(object.items(), key=_safe_tuple): - krepr = self._safe_repr(k, context, maxlevels, level) - vrepr = self._safe_repr(v, context, maxlevels, level) - append(f"{krepr}: {vrepr}") - context.remove(objid) - return "{{{}}}".format(", ".join(components)) - - if (issubclass(typ, list) and r is list.__repr__) or ( - issubclass(typ, tuple) and r is tuple.__repr__ - ): - if issubclass(typ, list): - if not object: - return "[]" - format = "[%s]" - elif len(object) == 1: - format = "(%s,)" - else: - if not object: - return "()" - format = "(%s)" - objid = id(object) - if maxlevels and level >= maxlevels: - return format % "..." - if objid in context: - return _recursion(object) - context.add(objid) - components = [] - append = components.append - level += 1 - for o in object: - orepr = self._safe_repr(o, context, maxlevels, level) - append(orepr) - context.remove(objid) - return format % ", ".join(components) - - return repr(object) - - -_builtin_scalars = frozenset( - {str, bytes, bytearray, float, complex, bool, type(None), int} -) - - -def _recursion(object: Any) -> str: - return f"" - - -def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]: - current = b"" - last = len(object) // 4 * 4 - for i in range(0, len(object), 4): - part = object[i : i + 4] - candidate = current + part - if i == last: - width -= allowance - if len(repr(candidate)) > width: - if current: - yield repr(current) - current = part - else: - current = candidate - if current: - yield repr(current) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/saferepr.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/saferepr.py index cee70e33..c7018722 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/saferepr.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/saferepr.py @@ -1,7 +1,9 @@ -from __future__ import annotations - import pprint import reprlib +from typing import Any +from typing import Dict +from typing import IO +from typing import Optional def _try_repr_or_str(obj: object) -> str: @@ -18,10 +20,10 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str: exc_info = _try_repr_or_str(exc) except (KeyboardInterrupt, SystemExit): raise - except BaseException as inner_exc: - exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})" - return ( - f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" + except BaseException as exc: + exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})" + return "<[{} raised in repr()] {} object at 0x{:x}>".format( + exc_info, type(obj).__name__, id(obj) ) @@ -39,7 +41,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: + def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -60,6 +62,7 @@ class SafeRepr(reprlib.Repr): s = ascii(x) else: s = super().repr(x) + except (KeyboardInterrupt, SystemExit): raise except BaseException as exc: @@ -97,7 +100,7 @@ DEFAULT_REPR_MAX_SIZE = 240 def saferepr( - obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False ) -> str: """Return a size-limited safe repr-string for the given object. @@ -108,6 +111,7 @@ def saferepr( This function is a wrapper around the Repr/reprlib functionality of the stdlib. """ + return SafeRepr(maxsize, use_ascii).repr(obj) @@ -128,3 +132,49 @@ def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str: return repr(obj) except Exception as exc: return _format_repr_exception(exc, obj) + + +class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): + """PrettyPrinter that always dispatches (regardless of width).""" + + def _format( + self, + object: object, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, Any], + level: int, + ) -> None: + # Type ignored because _dispatch is private. + p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] + + objid = id(object) + if objid in context or p is None: + # Type ignored because _format is private. + super()._format( # type: ignore[misc] + object, + stream, + indent, + allowance, + context, + level, + ) + return + + context[objid] = 1 + p(self, object, stream, indent, allowance, context, level + 1) + del context[objid] + + +def _pformat_dispatch( + object: object, + indent: int = 1, + width: int = 80, + depth: Optional[int] = None, + *, + compact: bool = False, +) -> str: + return AlwaysDispatchingPrettyPrinter( + indent=indent, width=width, depth=depth, compact=compact + ).pformat(object) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/terminalwriter.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/terminalwriter.py index 9191b4ed..379035d8 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/terminalwriter.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/terminalwriter.py @@ -1,23 +1,13 @@ """Helper functions for writing to terminals and files.""" - -from __future__ import annotations - -from collections.abc import Sequence import os import shutil import sys -from typing import final -from typing import Literal +from typing import Optional +from typing import Sequence from typing import TextIO -import pygments -from pygments.formatters.terminal import TerminalFormatter -from pygments.lexer import Lexer -from pygments.lexers.diff import DiffLexer -from pygments.lexers.python import PythonLexer - -from ..compat import assert_never from .wcwidth import wcswidth +from _pytest.compat import final # This code was initially copied from py 1.8.1, file _io/terminalwriter.py. @@ -38,9 +28,9 @@ def should_do_markup(file: TextIO) -> bool: return True if os.environ.get("PY_COLORS") == "0": return False - if os.environ.get("NO_COLOR"): + if "NO_COLOR" in os.environ: return False - if os.environ.get("FORCE_COLOR"): + if "FORCE_COLOR" in os.environ: return True return ( hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" @@ -72,7 +62,7 @@ class TerminalWriter: invert=7, ) - def __init__(self, file: TextIO | None = None) -> None: + def __init__(self, file: Optional[TextIO] = None) -> None: if file is None: file = sys.stdout if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": @@ -86,7 +76,7 @@ class TerminalWriter: self._file = file self.hasmarkup = should_do_markup(file) self._current_line = "" - self._terminal_width: int | None = None + self._terminal_width: Optional[int] = None self.code_highlight = True @property @@ -111,14 +101,14 @@ class TerminalWriter: if self.hasmarkup: esc = [self._esctable[name] for name, on in markup.items() if on] if esc: - text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" + text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m" return text def sep( self, sepchar: str, - title: str | None = None, - fullwidth: int | None = None, + title: Optional[str] = None, + fullwidth: Optional[int] = None, **markup: bool, ) -> None: if fullwidth is None: @@ -161,23 +151,20 @@ class TerminalWriter: msg = self.markup(msg, **markup) - self.write_raw(msg, flush=flush) + try: + self._file.write(msg) + except UnicodeEncodeError: + # Some environments don't support printing general Unicode + # strings, due to misconfiguration or otherwise; in that case, + # print the string escaped to ASCII. + # When the Unicode situation improves we should consider + # letting the error propagate instead of masking it (see #7475 + # for one brief attempt). + msg = msg.encode("unicode-escape").decode("ascii") + self._file.write(msg) - def write_raw(self, msg: str, *, flush: bool = False) -> None: - try: - self._file.write(msg) - except UnicodeEncodeError: - # Some environments don't support printing general Unicode - # strings, due to misconfiguration or otherwise; in that case, - # print the string escaped to ASCII. - # When the Unicode situation improves we should consider - # letting the error propagate instead of masking it (see #7475 - # for one brief attempt). - msg = msg.encode("unicode-escape").decode("ascii") - self._file.write(msg) - - if flush: - self.flush() + if flush: + self.flush() def line(self, s: str = "", **markup: bool) -> None: self.write(s, **markup) @@ -195,64 +182,52 @@ class TerminalWriter: """ if indents and len(indents) != len(lines): raise ValueError( - f"indents size ({len(indents)}) should have same size as lines ({len(lines)})" + "indents size ({}) should have same size as lines ({})".format( + len(indents), len(lines) + ) ) if not indents: indents = [""] * len(lines) source = "\n".join(lines) new_lines = self._highlight(source).splitlines() - # Would be better to strict=True but that fails some CI jobs. - for indent, new_line in zip(indents, new_lines, strict=False): + for indent, new_line in zip(indents, new_lines): self.line(indent + new_line) - def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer: - if lexer == "python": - return PythonLexer() - elif lexer == "diff": - return DiffLexer() - else: - assert_never(lexer) - - def _get_pygments_formatter(self) -> TerminalFormatter: + def _highlight(self, source: str) -> str: + """Highlight the given source code if we have markup support.""" from _pytest.config.exceptions import UsageError - theme = os.getenv("PYTEST_THEME") - theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") - - try: - return TerminalFormatter(bg=theme_mode, style=theme) - except pygments.util.ClassNotFound as e: - raise UsageError( - f"PYTEST_THEME environment variable has an invalid value: '{theme}'. " - "Hint: See available pygments styles with `pygmentize -L styles`." - ) from e - except pygments.util.OptionError as e: - raise UsageError( - f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. " - "The allowed values are 'dark' (default) and 'light'." - ) from e - - def _highlight( - self, source: str, lexer: Literal["diff", "python"] = "python" - ) -> str: - """Highlight the given source if we have markup support.""" - if not source or not self.hasmarkup or not self.code_highlight: + if not self.hasmarkup or not self.code_highlight: return source - - pygments_lexer = self._get_pygments_lexer(lexer) - pygments_formatter = self._get_pygments_formatter() - - highlighted: str = pygments.highlight( - source, pygments_lexer, pygments_formatter - ) - # pygments terminal formatter may add a newline when there wasn't one. - # We don't want this, remove. - if highlighted[-1] == "\n" and source[-1] != "\n": - highlighted = highlighted[:-1] - - # Some lexers will not set the initial color explicitly - # which may lead to the previous color being propagated to the - # start of the expression, so reset first. - highlighted = "\x1b[0m" + highlighted - - return highlighted + try: + from pygments.formatters.terminal import TerminalFormatter + from pygments.lexers.python import PythonLexer + from pygments import highlight + import pygments.util + except ImportError: + return source + else: + try: + highlighted: str = highlight( + source, + PythonLexer(), + TerminalFormatter( + bg=os.getenv("PYTEST_THEME_MODE", "dark"), + style=os.getenv("PYTEST_THEME"), + ), + ) + return highlighted + except pygments.util.ClassNotFound: + raise UsageError( + "PYTEST_THEME environment variable had an invalid value: '{}'. " + "Only valid pygment styles are allowed.".format( + os.getenv("PYTEST_THEME") + ) + ) + except pygments.util.OptionError: + raise UsageError( + "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " + "The only allowed values are 'dark' and 'light'.".format( + os.getenv("PYTEST_THEME_MODE") + ) + ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/wcwidth.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/wcwidth.py index 23886ff1..e5c7bf4d 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_io/wcwidth.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_io/wcwidth.py @@ -1,7 +1,5 @@ -from __future__ import annotations - -from functools import lru_cache import unicodedata +from functools import lru_cache @lru_cache(100) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..fb60b739 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/error.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/error.cpython-312.pyc new file mode 100644 index 00000000..e0109df9 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/error.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/path.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/path.cpython-312.pyc new file mode 100644 index 00000000..5acd0c34 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/__pycache__/path.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/error.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/error.py index dace2376..0b8f2d53 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/error.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/error.py @@ -1,15 +1,13 @@ """create errno-specific classes for IO or os calls.""" - from __future__ import annotations -from collections.abc import Callable import errno import os import sys +from typing import Callable from typing import TYPE_CHECKING from typing import TypeVar - if TYPE_CHECKING: from typing_extensions import ParamSpec @@ -41,7 +39,7 @@ _winerrnomap = { 3: errno.ENOENT, 17: errno.EEXIST, 18: errno.EXDEV, - 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable 22: errno.ENOTDIR, 20: errno.ENOTDIR, 267: errno.ENOTDIR, @@ -69,7 +67,7 @@ class ErrorMaker: try: return self._errno2class[eno] except KeyError: - clsname = errno.errorcode.get(eno, f"UnknownErrno{eno}") + clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,)) errorcls = type( clsname, (Error,), @@ -90,23 +88,15 @@ class ErrorMaker: except OSError as value: if not hasattr(value, "errno"): raise + errno = value.errno if sys.platform == "win32": try: - # error: Invalid index type "Optional[int]" for "dict[int, int]"; expected type "int" [index] - # OK to ignore because we catch the KeyError below. - cls = self._geterrnoclass(_winerrnomap[value.errno]) # type:ignore[index] + cls = self._geterrnoclass(_winerrnomap[errno]) except KeyError: raise value else: # we are not on Windows, or we got a proper OSError - if value.errno is None: - cls = type( - "UnknownErrnoNone", - (Error,), - {"__module__": "py.error", "__doc__": None}, - ) - else: - cls = self._geterrnoclass(value.errno) + cls = self._geterrnoclass(errno) raise cls(f"{func.__name__}{args!r}") diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/path.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/path.py index b7131b08..73a070d1 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_py/path.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_py/path.py @@ -1,15 +1,16 @@ -# mypy: allow-untyped-defs """local path implementation.""" - from __future__ import annotations import atexit -from collections.abc import Callable -from contextlib import contextmanager import fnmatch import importlib.util import io import os +import posixpath +import sys +import uuid +import warnings +from contextlib import contextmanager from os.path import abspath from os.path import dirname from os.path import exists @@ -18,21 +19,19 @@ from os.path import isdir from os.path import isfile from os.path import islink from os.path import normpath -import posixpath from stat import S_ISDIR from stat import S_ISLNK from stat import S_ISREG -import sys from typing import Any +from typing import Callable from typing import cast -from typing import Literal from typing import overload from typing import TYPE_CHECKING -import uuid -import warnings from . import error +if TYPE_CHECKING: + from typing import Literal # Moved from local.py. iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") @@ -161,13 +160,15 @@ class Visitor: ) if not self.breadthfirst: for subdir in dirs: - yield from self.gen(subdir) + for p in self.gen(subdir): + yield p for p in self.optsort(entries): if self.fil is None or self.fil(p): yield p if self.breadthfirst: for subdir in dirs: - yield from self.gen(subdir) + for p in self.gen(subdir): + yield p class FNMatcher: @@ -204,10 +205,12 @@ class Stat: if TYPE_CHECKING: @property - def size(self) -> int: ... + def size(self) -> int: + ... @property - def mtime(self) -> float: ... + def mtime(self) -> float: + ... def __getattr__(self, name: str) -> Any: return getattr(self._osstatresult, "st_" + name) @@ -222,7 +225,7 @@ class Stat: raise NotImplementedError("XXX win32") import pwd - entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined,unused-ignore] + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined] return entry[0] @property @@ -232,7 +235,7 @@ class Stat: raise NotImplementedError("XXX win32") import grp - entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined,unused-ignore] + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined] return entry[0] def isdir(self): @@ -250,7 +253,7 @@ def getuserid(user): import pwd if not isinstance(user, int): - user = pwd.getpwnam(user)[2] # type:ignore[attr-defined,unused-ignore] + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined] return user @@ -258,7 +261,7 @@ def getgroupid(group): import grp if not isinstance(group, int): - group = grp.getgrnam(group)[2] # type:ignore[attr-defined,unused-ignore] + group = grp.getgrnam(group)[2] # type:ignore[attr-defined] return group @@ -315,7 +318,7 @@ class LocalPath: def readlink(self) -> str: """Return value of a symbolic link.""" # https://github.com/python/mypy/issues/12278 - return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value,unused-ignore] + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value] def mklinkto(self, oldname): """Posix style hard link to another name.""" @@ -432,7 +435,7 @@ class LocalPath: """Return a string which is the relative part of the path to the given 'relpath'. """ - if not isinstance(relpath, str | LocalPath): + if not isinstance(relpath, (str, LocalPath)): raise TypeError(f"{relpath!r}: not a string or path object") strrelpath = str(relpath) if strrelpath and strrelpath[-1] != self.sep: @@ -449,7 +452,7 @@ class LocalPath: def ensure_dir(self, *args): """Ensure the path joined with args is a directory.""" - return self.ensure(*args, dir=True) + return self.ensure(*args, **{"dir": True}) def bestrelpath(self, dest): """Return a string which is a relative path from self @@ -652,12 +655,12 @@ class LocalPath: if not kw: obj.strpath = self.strpath return obj - drive, dirname, _basename, purebasename, ext = self._getbyspec( + drive, dirname, basename, purebasename, ext = self._getbyspec( "drive,dirname,basename,purebasename,ext" ) if "basename" in kw: if "purebasename" in kw or "ext" in kw: - raise ValueError(f"invalid specification {kw!r}") + raise ValueError("invalid specification %r" % kw) else: pb = kw.setdefault("purebasename", purebasename) try: @@ -674,7 +677,7 @@ class LocalPath: else: kw.setdefault("dirname", dirname) kw.setdefault("sep", self.sep) - obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw)) + obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw) return obj def _getbyspec(self, spec: str) -> list[str]: @@ -703,7 +706,7 @@ class LocalPath: elif name == "ext": res.append(ext) else: - raise ValueError(f"invalid part specification {name!r}") + raise ValueError("invalid part specification %r" % name) return res def dirpath(self, *args, **kwargs): @@ -754,12 +757,7 @@ class LocalPath: if ensure: self.dirpath().ensure(dir=1) if encoding: - return error.checked_call( - io.open, - self.strpath, - mode, - encoding=encoding, - ) + return error.checked_call(io.open, self.strpath, mode, encoding=encoding) return error.checked_call(open, self.strpath, mode) def _fastjoin(self, name): @@ -777,11 +775,11 @@ class LocalPath: valid checkers:: - file = 1 # is a file - file = 0 # is not a file (may not even exist) - dir = 1 # is a dir - link = 1 # is a link - exists = 1 # exists + file=1 # is a file + file=0 # is not a file (may not even exist) + dir=1 # is a dir + link=1 # is a link + exists=1 # exists You can specify multiple checker definitions, for example:: @@ -834,7 +832,7 @@ class LocalPath: def copy(self, target, mode=False, stat=False): """Copy path to target. - If mode is True, will copy permission from path to target. + If mode is True, will copy copy permission from path to target. If stat is True, copy permission, last modification time, last access time, and flags from path to target. """ @@ -959,10 +957,12 @@ class LocalPath: return p @overload - def stat(self, raising: Literal[True] = ...) -> Stat: ... + def stat(self, raising: Literal[True] = ...) -> Stat: + ... @overload - def stat(self, raising: Literal[False]) -> Stat | None: ... + def stat(self, raising: Literal[False]) -> Stat | None: + ... def stat(self, raising: bool = True) -> Stat | None: """Return an os.stat() tuple.""" @@ -1024,7 +1024,7 @@ class LocalPath: return self.stat().atime def __repr__(self): - return f"local({self.strpath!r})" + return "local(%r)" % self.strpath def __str__(self): """Return string representation of the Path.""" @@ -1045,7 +1045,7 @@ class LocalPath: def pypkgpath(self): """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. - Return None if a pkgpath cannot be determined. + Return None if a pkgpath can not be determined. """ pkgpath = None for parent in self.parts(reverse=True): @@ -1096,7 +1096,9 @@ class LocalPath: modname = self.purebasename spec = importlib.util.spec_from_file_location(modname, str(self)) if spec is None or spec.loader is None: - raise ImportError(f"Can't find module {modname} at location {self!s}") + raise ImportError( + f"Can't find module {modname} at location {str(self)}" + ) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod @@ -1161,8 +1163,7 @@ class LocalPath: where the 'self' path points to executable. The process is directly invoked and not through a system shell. """ - from subprocess import PIPE - from subprocess import Popen + from subprocess import Popen, PIPE popen_opts.pop("stdout", None) popen_opts.pop("stderr", None) @@ -1262,14 +1263,13 @@ class LocalPath: @classmethod def mkdtemp(cls, rootdir=None): """Return a Path object pointing to a fresh new temporary directory - (which we created ourselves). + (which we created ourself). """ import tempfile if rootdir is None: rootdir = cls.get_temproot() - path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir)) - return cls(path) + return cls(error.checked_call(tempfile.mkdtemp, dir=str(rootdir))) @classmethod def make_numbered_dir( diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/_version.py b/Backend/venv/lib/python3.12/site-packages/_pytest/_version.py index 25a26b42..d530ef48 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/_version.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/_version.py @@ -1,34 +1,16 @@ -# file generated by setuptools-scm +# file generated by setuptools_scm # don't change, don't track in version control - -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", - "__commit_id__", - "commit_id", -] - TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Tuple - from typing import Union - + from typing import Tuple, Union VERSION_TUPLE = Tuple[Union[int, str], ...] - COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object - COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -commit_id: COMMIT_ID -__commit_id__: COMMIT_ID -__version__ = version = '9.0.1' -__version_tuple__ = version_tuple = (9, 0, 1) - -__commit_id__ = commit_id = None +__version__ = version = '7.4.3' +__version_tuple__ = version_tuple = (7, 4, 3) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py index 22f3ca8e..a46e5813 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__init__.py @@ -1,12 +1,9 @@ -# mypy: allow-untyped-defs """Support for presenting detailed information in failing assertions.""" - -from __future__ import annotations - -from collections.abc import Generator import sys from typing import Any -from typing import Protocol +from typing import Generator +from typing import List +from typing import Optional from typing import TYPE_CHECKING from _pytest.assertion import rewrite @@ -18,7 +15,6 @@ from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.nodes import Item - if TYPE_CHECKING: from _pytest.main import Session @@ -47,26 +43,6 @@ def pytest_addoption(parser: Parser) -> None: "Make sure to delete any previously generated pyc cache files.", ) - parser.addini( - "truncation_limit_lines", - default=None, - help="Set threshold of LINES after which truncation will take effect", - ) - parser.addini( - "truncation_limit_chars", - default=None, - help=("Set threshold of CHARS after which truncation will take effect"), - ) - - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_ASSERTIONS, - help=( - "Specify a verbosity level for assertions, overriding the main level. " - "Higher levels will provide more detailed explanation when an assertion fails." - ), - ) - def register_assert_rewrite(*names: str) -> None: """Register one or more module names to be rewritten on import. @@ -83,18 +59,15 @@ def register_assert_rewrite(*names: str) -> None: if not isinstance(name, str): msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] raise TypeError(msg.format(repr(names))) - rewrite_hook: RewriteHook for hook in sys.meta_path: if isinstance(hook, rewrite.AssertionRewritingHook): - rewrite_hook = hook + importhook = hook break else: - rewrite_hook = DummyRewriteHook() - rewrite_hook.mark_rewrite(*names) - - -class RewriteHook(Protocol): - def mark_rewrite(self, *names: str) -> None: ... + # TODO(typing): Add a protocol for mark_rewrite() and use it + # for importhook and for PytestPluginManager.rewrite_hook. + importhook = DummyRewriteHook() # type: ignore + importhook.mark_rewrite(*names) class DummyRewriteHook: @@ -110,7 +83,7 @@ class AssertionState: def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook: rewrite.AssertionRewritingHook | None = None + self.hook: Optional[rewrite.AssertionRewritingHook] = None def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: @@ -129,7 +102,7 @@ def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: return hook -def pytest_collection(session: Session) -> None: +def pytest_collection(session: "Session") -> None: # This hook is only called when test modules are collected # so for example not in the managing process of pytest-xdist # (which does not collect test modules). @@ -139,17 +112,18 @@ def pytest_collection(session: Session) -> None: assertstate.hook.set_session(session) -@hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: +@hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. The rewrite module will use util._reprcompare if it exists to use custom reporting via the pytest_assertrepr_compare hook. This sets up this custom comparison for the test. """ + ihook = item.ihook - def callbinrepr(op, left: object, right: object) -> str | None: + def callbinrepr(op, left: object, right: object) -> Optional[str]: """Call the pytest_assertrepr_compare hook and prepare the result. This uses the first result from the hook and then ensures the @@ -188,14 +162,13 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: util._assertion_pass = call_assertion_pass_hook - try: - return (yield) - finally: - util._reprcompare, util._assertion_pass = saved_assert_hooks - util._config = None + yield + + util._reprcompare, util._assertion_pass = saved_assert_hooks + util._config = None -def pytest_sessionfinish(session: Session) -> None: +def pytest_sessionfinish(session: "Session") -> None: assertstate = session.config.stash.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: @@ -204,5 +177,5 @@ def pytest_sessionfinish(session: Session) -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any -) -> list[str] | None: +) -> Optional[List[str]]: return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..1d5f6f8a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc new file mode 100644 index 00000000..029c4590 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/rewrite.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/truncate.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/truncate.cpython-312.pyc new file mode 100644 index 00000000..35cc8108 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/truncate.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/util.cpython-312.pyc new file mode 100644 index 00000000..b9308856 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py index 566549d6..fd235529 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py @@ -1,13 +1,5 @@ """Rewrite assertion AST to produce nice error messages.""" - -from __future__ import annotations - import ast -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Sequence import errno import functools import importlib.abc @@ -17,46 +9,53 @@ import io import itertools import marshal import os -from pathlib import Path -from pathlib import PurePath import struct import sys import tokenize import types +from collections import defaultdict +from pathlib import Path +from pathlib import PurePath +from typing import Callable +from typing import Dict from typing import IO +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple from typing import TYPE_CHECKING - - -if sys.version_info >= (3, 12): - from importlib.resources.abc import TraversableResources -else: - from importlib.abc import TraversableResources -if sys.version_info < (3, 11): - from importlib.readers import FileReader -else: - from importlib.resources.readers import FileReader - +from typing import Union from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr -from _pytest._io.saferepr import saferepr_unlimited from _pytest._version import version from _pytest.assertion import util +from _pytest.assertion.util import ( # noqa: F401 + format_explanation as _format_explanation, +) from _pytest.config import Config -from _pytest.fixtures import FixtureFunctionDefinition from _pytest.main import Session from _pytest.pathlib import absolutepath from _pytest.pathlib import fnmatch_ex from _pytest.stash import StashKey - -# fmt: off -from _pytest.assertion.util import format_explanation as _format_explanation # noqa:F401, isort:skip -# fmt:on - if TYPE_CHECKING: from _pytest.assertion import AssertionState +if sys.version_info >= (3, 8): + namedExpr = ast.NamedExpr + astNameConstant = ast.Constant + astStr = ast.Constant + astNum = ast.Constant +else: + namedExpr = ast.Expr + astNameConstant = ast.NameConstant + astStr = ast.Str + astNum = ast.Num + class Sentinel: pass @@ -66,7 +65,7 @@ assertstate_key = StashKey["AssertionState"]() # pytest caches rewritten pycs in pycache dirs PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" -PYC_EXT = ".py" + ((__debug__ and "c") or "o") +PYC_EXT = ".py" + (__debug__ and "c" or "o") PYC_TAIL = "." + PYTEST_TAG + PYC_EXT # Special marker that denotes we have just left a scope definition @@ -82,17 +81,17 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session: Session | None = None - self._rewritten_names: dict[str, Path] = {} - self._must_rewrite: set[str] = set() + self.session: Optional[Session] = None + self._rewritten_names: Dict[str, Path] = {} + self._must_rewrite: Set[str] = set() # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache: dict[str, bool] = {} + self._marked_for_rewrite_cache: Dict[str, bool] = {} self._session_paths_checked = False - def set_session(self, session: Session | None) -> None: + def set_session(self, session: Optional[Session]) -> None: self.session = session self._session_paths_checked = False @@ -102,28 +101,18 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) def find_spec( self, name: str, - path: Sequence[str | bytes] | None = None, - target: types.ModuleType | None = None, - ) -> importlib.machinery.ModuleSpec | None: + path: Optional[Sequence[Union[str, bytes]]] = None, + target: Optional[types.ModuleType] = None, + ) -> Optional[importlib.machinery.ModuleSpec]: if self._writing_pyc: return None state = self.config.stash[assertstate_key] if self._early_rewrite_bailout(name, state): return None - state.trace(f"find_module called for: {name}") + state.trace("find_module called for: %s" % name) # Type ignored because mypy is confused about the `self` binding here. spec = self._find_spec(name, path) # type: ignore - - if spec is None and path is not None: - # With --import-mode=importlib, PathFinder cannot find spec without modifying `sys.path`, - # causing inability to assert rewriting (#12659). - # At this point, try using the file path to find the module spec. - for _path_str in path: - spec = importlib.util.spec_from_file_location(name, _path_str) - if spec is not None: - break - if ( # the import machinery could not find a file to import spec is None @@ -151,7 +140,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) def create_module( self, spec: importlib.machinery.ModuleSpec - ) -> types.ModuleType | None: + ) -> Optional[types.ModuleType]: return None # default behaviour is fine def exec_module(self, module: types.ModuleType) -> None: @@ -196,7 +185,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) state.trace(f"found cached rewritten pyc for {fn}") exec(co, module.__dict__) - def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: + def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: """A fast way to get out of rewriting modules. Profiling has shown that the call to PathFinder.find_spec (inside of @@ -235,7 +224,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) state.trace(f"early skip of rewriting module: {name}") return True - def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: + def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: # always rewrite conftest files if os.path.basename(fn) == "conftest.py": state.trace(f"rewriting conftest file: {fn!r}") @@ -256,7 +245,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: + def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -292,18 +281,31 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) self.config.issue_config_time_warning( PytestAssertRewriteWarning( - f"Module already imported so cannot be rewritten; {name}" + "Module already imported so cannot be rewritten: %s" % name ), stacklevel=5, ) - def get_data(self, pathname: str | bytes) -> bytes: + def get_data(self, pathname: Union[str, bytes]) -> bytes: """Optional PEP302 get_data API.""" with open(pathname, "rb") as f: return f.read() - def get_resource_reader(self, name: str) -> TraversableResources: - return FileReader(types.SimpleNamespace(path=self._rewritten_names[name])) # type: ignore[arg-type] + if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources + + def get_resource_reader(self, name: str) -> TraversableResources: # type: ignore + if sys.version_info < (3, 11): + from importlib.readers import FileReader + else: + from importlib.resources.readers import FileReader + + return FileReader( # type:ignore[no-any-return] + types.SimpleNamespace(path=self._rewritten_names[name]) + ) def _write_pyc_fp( @@ -325,7 +327,7 @@ def _write_pyc_fp( def _write_pyc( - state: AssertionState, + state: "AssertionState", co: types.CodeType, source_stat: os.stat_result, pyc: Path, @@ -349,7 +351,7 @@ def _write_pyc( return True -def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: +def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: """Read and rewrite *fn* and return the code object.""" stat = os.stat(fn) source = fn.read_bytes() @@ -362,7 +364,7 @@ def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeT def _read_pyc( source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> types.CodeType | None: +) -> Optional[types.CodeType]: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. @@ -382,21 +384,21 @@ def _read_pyc( return None # Check for invalid or out of date pyc file. if len(data) != (16): - trace(f"_read_pyc({source}): invalid pyc (too short)") + trace("_read_pyc(%s): invalid pyc (too short)" % source) return None if data[:4] != importlib.util.MAGIC_NUMBER: - trace(f"_read_pyc({source}): invalid pyc (bad magic number)") + trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) return None if data[4:8] != b"\x00\x00\x00\x00": - trace(f"_read_pyc({source}): invalid pyc (unsupported flags)") + trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) return None mtime_data = data[8:12] if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: - trace(f"_read_pyc({source}): out of date") + trace("_read_pyc(%s): out of date" % source) return None size_data = data[12:16] if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: - trace(f"_read_pyc({source}): invalid pyc (incorrect size)") + trace("_read_pyc(%s): invalid pyc (incorrect size)" % source) return None try: co = marshal.load(fp) @@ -404,7 +406,7 @@ def _read_pyc( trace(f"_read_pyc({source}): marshal.load error {e}") return None if not isinstance(co, types.CodeType): - trace(f"_read_pyc({source}): not a code object") + trace("_read_pyc(%s): not a code object" % source) return None return co @@ -412,8 +414,8 @@ def _read_pyc( def rewrite_asserts( mod: ast.Module, source: bytes, - module_path: str | None = None, - config: Config | None = None, + module_path: Optional[str] = None, + config: Optional[Config] = None, ) -> None: """Rewrite the assert statements in mod.""" AssertionRewriter(module_path, config, source).run(mod) @@ -429,22 +431,13 @@ def _saferepr(obj: object) -> str: sequences, especially '\n{' and '\n}' are likely to be present in JSON reprs. """ - if isinstance(obj, types.MethodType): - # for bound methods, skip redundant information - return obj.__name__ - maxsize = _get_maxsize_for_saferepr(util._config) - if not maxsize: - return saferepr_unlimited(obj).replace("\n", "\\n") return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") -def _get_maxsize_for_saferepr(config: Config | None) -> int | None: +def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]: """Get `maxsize` configuration for saferepr based on the given config object.""" - if config is None: - verbosity = 0 - else: - verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + verbosity = config.getoption("verbose") if config is not None else 0 if verbosity >= 2: return None if verbosity >= 1: @@ -465,7 +458,7 @@ def _format_assertmsg(obj: object) -> str: # However in either case we want to preserve the newline. replaces = [("\n", "\n~"), ("%", "%%")] if not isinstance(obj, str): - obj = saferepr(obj, _get_maxsize_for_saferepr(util._config)) + obj = saferepr(obj) replaces.append(("\\n", "\n~")) for r1, r2 in replaces: @@ -476,8 +469,7 @@ def _format_assertmsg(obj: object) -> str: def _should_repr_global_name(obj: object) -> bool: if callable(obj): - # For pytest fixtures the __repr__ method provides more information than the function name. - return isinstance(obj, FixtureFunctionDefinition) + return False try: return not hasattr(obj, "__name__") @@ -486,7 +478,7 @@ def _should_repr_global_name(obj: object) -> bool: def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: - explanation = "(" + ((is_or and " or ") or " and ").join(explanations) + ")" + explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" return explanation.replace("%", "%%") @@ -496,7 +488,7 @@ def _call_reprcompare( expls: Sequence[str], each_obj: Sequence[object], ) -> str: - for i, res, expl in zip(range(len(ops)), results, expls, strict=True): + for i, res, expl in zip(range(len(ops)), results, expls): try: done = not res except Exception: @@ -558,14 +550,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]: @functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> dict[int, str]: +def _get_assertion_exprs(src: bytes) -> Dict[int, str]: """Return a mapping from {lineno: "assertion test expression"}.""" - ret: dict[int, str] = {} + ret: Dict[int, str] = {} depth = 0 - lines: list[str] = [] - assert_lineno: int | None = None - seen_lines: set[int] = set() + lines: List[str] = [] + assert_lineno: Optional[int] = None + seen_lines: Set[int] = set() def _write_and_reset() -> None: nonlocal depth, lines, assert_lineno, seen_lines @@ -599,7 +591,7 @@ def _get_assertion_exprs(src: bytes) -> dict[int, str]: # multi-line assert with message elif lineno in seen_lines: lines[-1] = lines[-1][:offset] - # multi line assert with escaped newline before message + # multi line assert with escapd newline before message else: lines.append(line[:offset]) _write_and_reset() @@ -672,7 +664,7 @@ class AssertionRewriter(ast.NodeVisitor): """ def __init__( - self, module_path: str | None, config: Config | None, source: bytes + self, module_path: Optional[str], config: Optional[Config], source: bytes ) -> None: super().__init__() self.module_path = module_path @@ -685,9 +677,9 @@ class AssertionRewriter(ast.NodeVisitor): self.enable_assertion_pass_hook = False self.source = source self.scope: tuple[ast.AST, ...] = () - self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( - defaultdict(dict) - ) + self.variables_overwrite: defaultdict[ + tuple[ast.AST, ...], Dict[str, str] + ] = defaultdict(dict) def run(self, mod: ast.Module) -> None: """Find all assert statements in *mod* and rewrite them.""" @@ -702,18 +694,28 @@ class AssertionRewriter(ast.NodeVisitor): if doc is not None and self.is_rewrite_disabled(doc): return pos = 0 + item = None for item in mod.body: - match item: - case ast.Expr(value=ast.Constant(value=str() as doc)) if ( - expect_docstring - ): - if self.is_rewrite_disabled(doc): - return - expect_docstring = False - case ast.ImportFrom(level=0, module="__future__"): - pass - case _: - break + if ( + expect_docstring + and isinstance(item, ast.Expr) + and isinstance(item.value, astStr) + ): + if sys.version_info >= (3, 8): + doc = item.value.value + else: + doc = item.value.s + if self.is_rewrite_disabled(doc): + return + expect_docstring = False + elif ( + isinstance(item, ast.ImportFrom) + and item.level == 0 + and item.module == "__future__" + ): + pass + else: + break pos += 1 # Special case: for a decorated function, set the lineno to that of the # first decorator, not the `def`. Issue #4984. @@ -722,15 +724,21 @@ class AssertionRewriter(ast.NodeVisitor): else: lineno = item.lineno # Now actually insert the special imports. - aliases = [ - ast.alias("builtins", "@py_builtins", lineno=lineno, col_offset=0), - ast.alias( - "_pytest.assertion.rewrite", - "@pytest_ar", - lineno=lineno, - col_offset=0, - ), - ] + if sys.version_info >= (3, 10): + aliases = [ + ast.alias("builtins", "@py_builtins", lineno=lineno, col_offset=0), + ast.alias( + "_pytest.assertion.rewrite", + "@pytest_ar", + lineno=lineno, + col_offset=0, + ), + ] + else: + aliases = [ + ast.alias("builtins", "@py_builtins"), + ast.alias("_pytest.assertion.rewrite", "@pytest_ar"), + ] imports = [ ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases ] @@ -738,10 +746,10 @@ class AssertionRewriter(ast.NodeVisitor): # Collect asserts. self.scope = (mod,) - nodes: list[ast.AST | Sentinel] = [mod] + nodes: List[Union[ast.AST, Sentinel]] = [mod] while nodes: node = nodes.pop() - if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): self.scope = tuple((*self.scope, node)) nodes.append(_SCOPE_END_MARKER) if node == _SCOPE_END_MARKER: @@ -750,7 +758,7 @@ class AssertionRewriter(ast.NodeVisitor): assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): - new: list[ast.AST] = [] + new: List[ast.AST] = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -783,7 +791,7 @@ class AssertionRewriter(ast.NodeVisitor): """Give *expr* a name.""" name = self.variable() self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) - return ast.copy_location(ast.Name(name, ast.Load()), expr) + return ast.Name(name, ast.Load()) def display(self, expr: ast.expr) -> ast.expr: """Call saferepr on the expression.""" @@ -822,7 +830,7 @@ class AssertionRewriter(ast.NodeVisitor): to format a string of %-formatted values as added by .explanation_param(). """ - self.explanation_specifiers: dict[str, ast.expr] = {} + self.explanation_specifiers: Dict[str, ast.expr] = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: @@ -836,7 +844,7 @@ class AssertionRewriter(ast.NodeVisitor): current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()] + keys = [astStr(key) for key in current.keys()] format_dict = ast.Dict(keys, list(current.values())) form = ast.BinOp(expl_expr, ast.Mod(), format_dict) name = "@py_format" + str(next(self.variable_counter)) @@ -845,13 +853,13 @@ class AssertionRewriter(ast.NodeVisitor): self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) return ast.Name(name, ast.Load()) - def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: + def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]: """Handle expressions we don't have custom code for.""" assert isinstance(node, ast.expr) res = self.assign(node) return res, self.explanation_param(self.display(res)) - def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: + def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: """Return the AST statements to replace the ast.Assert instance. This rewrites the test of an assertion to provide @@ -860,9 +868,8 @@ class AssertionRewriter(ast.NodeVisitor): the expression is false. """ if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: - import warnings - from _pytest.warning_types import PytestAssertRewriteWarning + import warnings # TODO: This assert should not be needed. assert self.module_path is not None @@ -875,15 +882,15 @@ class AssertionRewriter(ast.NodeVisitor): lineno=assert_.lineno, ) - self.statements: list[ast.stmt] = [] - self.variables: list[str] = [] + self.statements: List[ast.stmt] = [] + self.variables: List[str] = [] self.variable_counter = itertools.count() if self.enable_assertion_pass_hook: - self.format_variables: list[str] = [] + self.format_variables: List[str] = [] - self.stack: list[dict[str, ast.expr]] = [] - self.expl_stmts: list[ast.stmt] = [] + self.stack: List[Dict[str, ast.expr]] = [] + self.expl_stmts: List[ast.stmt] = [] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) @@ -891,16 +898,16 @@ class AssertionRewriter(ast.NodeVisitor): negation = ast.UnaryOp(ast.Not(), top_condition) if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook - msg = self.pop_format_context(ast.Constant(explanation)) + msg = self.pop_format_context(astStr(explanation)) # Failed if assert_.msg: assertmsg = self.helper("_format_assertmsg", assert_.msg) gluestr = "\n>assert " else: - assertmsg = ast.Constant("") + assertmsg = astStr("") gluestr = "assert " - err_explanation = ast.BinOp(ast.Constant(gluestr), ast.Add(), msg) + err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg) err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) err_name = ast.Name("AssertionError", ast.Load()) fmt = self.helper("_format_explanation", err_msg) @@ -916,27 +923,27 @@ class AssertionRewriter(ast.NodeVisitor): hook_call_pass = ast.Expr( self.helper( "_call_assertion_pass", - ast.Constant(assert_.lineno), - ast.Constant(orig), + astNum(assert_.lineno), + astStr(orig), fmt_pass, ) ) # If any hooks implement assert_pass hook hook_impl_test = ast.If( self.helper("_check_if_assertion_pass_impl"), - [*self.expl_stmts, hook_call_pass], + self.expl_stmts + [hook_call_pass], [], ) - statements_pass: list[ast.stmt] = [hook_impl_test] + statements_pass = [hook_impl_test] # Test for assertion condition main_test = ast.If(negation, statements_fail, statements_pass) self.statements.append(main_test) if self.format_variables: - variables: list[ast.expr] = [ + variables = [ ast.Name(name, ast.Store()) for name in self.format_variables ] - clear_format = ast.Assign(variables, ast.Constant(None)) + clear_format = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear_format) else: # Original assertion rewriting @@ -947,9 +954,9 @@ class AssertionRewriter(ast.NodeVisitor): assertmsg = self.helper("_format_assertmsg", assert_.msg) explanation = "\n>assert " + explanation else: - assertmsg = ast.Constant("") + assertmsg = astStr("") explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation)) + template = ast.BinOp(assertmsg, ast.Add(), astStr(explanation)) msg = self.pop_format_context(template) fmt = self.helper("_format_explanation", msg) err_name = ast.Name("AssertionError", ast.Load()) @@ -961,40 +968,37 @@ class AssertionRewriter(ast.NodeVisitor): # Clear temporary variables by setting them to None. if self.variables: variables = [ast.Name(name, ast.Store()) for name in self.variables] - clear = ast.Assign(variables, ast.Constant(None)) + clear = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear) # Fix locations (line numbers/column offsets). for stmt in self.statements: for node in traverse_node(stmt): - if getattr(node, "lineno", None) is None: - # apply the assertion location to all generated ast nodes without source location - # and preserve the location of existing nodes or generated nodes with an correct location. - ast.copy_location(node, assert_) + ast.copy_location(node, assert_) return self.statements - def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: + def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]: # This method handles the 'walrus operator' repr of the target # name if it's a local variable or _should_repr_global_name() # thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) - target_id = name.target.id - inlocs = ast.Compare(ast.Constant(target_id), [ast.In()], [locs]) + target_id = name.target.id # type: ignore[attr-defined] + inlocs = ast.Compare(astStr(target_id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) + expr = ast.IfExp(test, self.display(name), astStr(target_id)) return name, self.explanation_param(expr) - def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: + def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) - inlocs = ast.Compare(ast.Constant(name.id), [ast.In()], [locs]) + inlocs = ast.Compare(astStr(name.id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) + expr = ast.IfExp(test, self.display(name), astStr(name.id)) return name, self.explanation_param(expr) - def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: + def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: res_var = self.variable() expl_list = self.assign(ast.List([], ast.Load())) app = ast.Attribute(expl_list, "append", ast.Load()) @@ -1006,57 +1010,60 @@ class AssertionRewriter(ast.NodeVisitor): # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner: list[ast.stmt] = [] + fail_inner: List[ast.stmt] = [] # cond is set in a prior loop iteration below - self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa self.expl_stmts = fail_inner - match v: - # Check if the left operand is an ast.NamedExpr and the value has already been visited - case ast.Compare( - left=ast.NamedExpr(target=ast.Name(id=target_id)) - ) if target_id in [ - e.id for e in boolop.values[:i] if hasattr(e, "id") - ]: - pytest_temp = self.variable() - self.variables_overwrite[self.scope][target_id] = v.left # type:ignore[assignment] - # mypy's false positive, we're checking that the 'target' attribute exists. - v.left.target.id = pytest_temp # type:ignore[attr-defined] + # Check if the left operand is a namedExpr and the value has already been visited + if ( + isinstance(v, ast.Compare) + and isinstance(v.left, namedExpr) + and v.left.target.id + in [ + ast_expr.id + for ast_expr in boolop.values[:i] + if hasattr(ast_expr, "id") + ] + ): + pytest_temp = self.variable() + self.variables_overwrite[self.scope][ + v.left.target.id + ] = v.left # type:ignore[assignment] + v.left.target.id = pytest_temp self.push_format_context() res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) - expl_format = self.pop_format_context(ast.Constant(expl)) + expl_format = self.pop_format_context(astStr(expl)) call = ast.Call(app, [expl_format], []) self.expl_stmts.append(ast.Expr(call)) if i < levels: cond: ast.expr = res if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner: list[ast.stmt] = [] + inner: List[ast.stmt] = [] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save self.expl_stmts = fail_save - expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or)) + expl_template = self.helper("_format_boolop", expl_list, astNum(is_or)) expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: + def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]: pattern = UNARY_MAP[unary.op.__class__] operand_res, operand_expl = self.visit(unary.operand) - res = self.assign(ast.copy_location(ast.UnaryOp(unary.op, operand_res), unary)) + res = self.assign(ast.UnaryOp(unary.op, operand_res)) return res, pattern % (operand_expl,) - def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: + def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: symbol = BINOP_MAP[binop.op.__class__] left_expr, left_expl = self.visit(binop.left) right_expr, right_expl = self.visit(binop.right) explanation = f"({left_expl} {symbol} {right_expl})" - res = self.assign( - ast.copy_location(ast.BinOp(left_expr, binop.op, right_expr), binop) - ) + res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: + def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -1065,16 +1072,19 @@ class AssertionRewriter(ast.NodeVisitor): if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get( self.scope, {} ): - arg = self.variables_overwrite[self.scope][arg.id] # type:ignore[assignment] + arg = self.variables_overwrite[self.scope][ + arg.id + ] # type:ignore[assignment] res, expl = self.visit(arg) arg_expls.append(expl) new_args.append(res) for keyword in call.keywords: - match keyword.value: - case ast.Name(id=id) if id in self.variables_overwrite.get( - self.scope, {} - ): - keyword.value = self.variables_overwrite[self.scope][id] # type:ignore[assignment] + if isinstance( + keyword.value, ast.Name + ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}): + keyword.value = self.variables_overwrite[self.scope][ + keyword.value.id + ] # type:ignore[assignment] res, expl = self.visit(keyword.value) new_kwargs.append(ast.keyword(keyword.arg, res)) if keyword.arg: @@ -1083,68 +1093,70 @@ class AssertionRewriter(ast.NodeVisitor): arg_expls.append("**" + expl) expl = "{}({})".format(func_expl, ", ".join(arg_expls)) - new_call = ast.copy_location(ast.Call(new_func, new_args, new_kwargs), call) + new_call = ast.Call(new_func, new_args, new_kwargs) res = self.assign(new_call) res_expl = self.explanation_param(self.display(res)) outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" return res, outer_expl - def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: + def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]: # A Starred node can appear in a function call. res, expl = self.visit(starred.value) new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: + def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) value, value_expl = self.visit(attr.value) - res = self.assign( - ast.copy_location(ast.Attribute(value, attr.attr, ast.Load()), attr) - ) + res = self.assign(ast.Attribute(value, attr.attr, ast.Load())) res_expl = self.explanation_param(self.display(res)) pat = "%s\n{%s = %s.%s\n}" expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: + def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() # We first check if we have overwritten a variable in the previous assert - match comp.left: - case ast.Name(id=name_id) if name_id in self.variables_overwrite.get( - self.scope, {} - ): - comp.left = self.variables_overwrite[self.scope][name_id] # type: ignore[assignment] - case ast.NamedExpr(target=ast.Name(id=target_id)): - self.variables_overwrite[self.scope][target_id] = comp.left # type: ignore[assignment] + if isinstance( + comp.left, ast.Name + ) and comp.left.id in self.variables_overwrite.get(self.scope, {}): + comp.left = self.variables_overwrite[self.scope][ + comp.left.id + ] # type:ignore[assignment] + if isinstance(comp.left, namedExpr): + self.variables_overwrite[self.scope][ + comp.left.target.id + ] = comp.left # type:ignore[assignment] left_res, left_expl = self.visit(comp.left) - if isinstance(comp.left, ast.Compare | ast.BoolOp): + if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" res_variables = [self.variable() for i in range(len(comp.ops))] - load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] + load_names = [ast.Name(v, ast.Load()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables] - it = zip(range(len(comp.ops)), comp.ops, comp.comparators, strict=True) - expls: list[ast.expr] = [] - syms: list[ast.expr] = [] + it = zip(range(len(comp.ops)), comp.ops, comp.comparators) + expls = [] + syms = [] results = [left_res] for i, op, next_operand in it: - match (next_operand, left_res): - case ( - ast.NamedExpr(target=ast.Name(id=target_id)), - ast.Name(id=name_id), - ) if target_id == name_id: - next_operand.target.id = self.variable() - self.variables_overwrite[self.scope][name_id] = next_operand # type: ignore[assignment] - + if ( + isinstance(next_operand, namedExpr) + and isinstance(left_res, ast.Name) + and next_operand.target.id == left_res.id + ): + next_operand.target.id = self.variable() + self.variables_overwrite[self.scope][ + left_res.id + ] = next_operand # type:ignore[assignment] next_res, next_expl = self.visit(next_operand) - if isinstance(next_operand, ast.Compare | ast.BoolOp): + if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = f"({next_expl})" results.append(next_res) sym = BINOP_MAP[op.__class__] - syms.append(ast.Constant(sym)) + syms.append(astStr(sym)) expl = f"{left_expl} {sym} {next_expl}" - expls.append(ast.Constant(expl)) - res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp) + expls.append(astStr(expl)) + res_expr = ast.Compare(left_res, [op], [next_res]) self.statements.append(ast.Assign([store_names[i]], res_expr)) left_res, left_expl = next_res, next_expl # Use pytest.assertion.util._reprcompare if that's available. @@ -1179,10 +1191,7 @@ def try_makedirs(cache_dir: Path) -> bool: return False except OSError as e: # as of now, EROFS doesn't have an equivalent OSError-subclass - # - # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not - # implemented" for a read-only error - if e.errno in {errno.EROFS, errno.ENOSYS}: + if e.errno == errno.EROFS: return False raise return True @@ -1190,7 +1199,7 @@ def try_makedirs(cache_dir: Path) -> bool: def get_cache_dir(file_path: Path) -> Path: """Return the cache directory to write .pyc files for the given .py file path.""" - if sys.pycache_prefix: + if sys.version_info >= (3, 8) and sys.pycache_prefix: # given: # prefix = '/tmp/pycs' # path = '/home/user/proj/test_app.py' diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/truncate.py b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/truncate.py index 5820e6e8..dfd6f65d 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/truncate.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/truncate.py @@ -1,65 +1,51 @@ """Utilities for truncating assertion output. Current default behaviour is to truncate assertion explanations at -terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. +~8 terminal lines, unless running in "-vv" mode or running on CI. """ +from typing import List +from typing import Optional -from __future__ import annotations - -from _pytest.compat import running_on_ci -from _pytest.config import Config +from _pytest.assertion import util from _pytest.nodes import Item DEFAULT_MAX_LINES = 8 -DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80 +DEFAULT_MAX_CHARS = 8 * 80 USAGE_MSG = "use '-vv' to show" -def truncate_if_required(explanation: list[str], item: Item) -> list[str]: +def truncate_if_required( + explanation: List[str], item: Item, max_length: Optional[int] = None +) -> List[str]: """Truncate this assertion explanation if the given test item is eligible.""" - should_truncate, max_lines, max_chars = _get_truncation_parameters(item) - if should_truncate: - return _truncate_explanation( - explanation, - max_lines=max_lines, - max_chars=max_chars, - ) + if _should_truncate_item(item): + return _truncate_explanation(explanation) return explanation -def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]: - """Return the truncation parameters related to the given item, as (should truncate, max lines, max chars).""" - # We do not need to truncate if one of conditions is met: - # 1. Verbosity level is 2 or more; - # 2. Test is being run in CI environment; - # 3. Both truncation_limit_lines and truncation_limit_chars - # .ini parameters are set to 0 explicitly. - max_lines = item.config.getini("truncation_limit_lines") - max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES) - - max_chars = item.config.getini("truncation_limit_chars") - max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS) - - verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS) - - should_truncate = verbose < 2 and not running_on_ci() - should_truncate = should_truncate and (max_lines > 0 or max_chars > 0) - - return should_truncate, max_lines, max_chars +def _should_truncate_item(item: Item) -> bool: + """Whether or not this test item is eligible for truncation.""" + verbose = item.config.option.verbose + return verbose < 2 and not util.running_on_ci() def _truncate_explanation( - input_lines: list[str], - max_lines: int, - max_chars: int, -) -> list[str]: + input_lines: List[str], + max_lines: Optional[int] = None, + max_chars: Optional[int] = None, +) -> List[str]: """Truncate given list of strings that makes up the assertion explanation. - Truncates to either max_lines, or max_chars - whichever the input reaches + Truncates to either 8 lines, or 640 characters - whichever the input reaches first, taking the truncation explanation into account. The remaining lines will be replaced by a usage message. """ + if max_lines is None: + max_lines = DEFAULT_MAX_LINES + if max_chars is None: + max_chars = DEFAULT_MAX_CHARS + # Check if truncation required input_char_count = len("".join(input_lines)) # The length of the truncation explanation depends on the number of lines @@ -84,23 +70,16 @@ def _truncate_explanation( ): return input_lines # Truncate first to max_lines, and then truncate to max_chars if necessary - if max_lines > 0: - truncated_explanation = input_lines[:max_lines] - else: - truncated_explanation = input_lines + truncated_explanation = input_lines[:max_lines] truncated_char = True # We reevaluate the need to truncate chars following removal of some lines - if len("".join(truncated_explanation)) > tolerable_max_chars and max_chars > 0: + if len("".join(truncated_explanation)) > tolerable_max_chars: truncated_explanation = _truncate_by_char_count( truncated_explanation, max_chars ) else: truncated_char = False - if truncated_explanation == input_lines: - # No truncation happened, so we do not need to add any explanations - return truncated_explanation - truncated_line_count = len(input_lines) - len(truncated_explanation) if truncated_explanation[-1]: # Add ellipsis and take into account part-truncated final line @@ -111,15 +90,14 @@ def _truncate_explanation( else: # Add proper ellipsis when we were able to fit a full line exactly truncated_explanation[-1] = "..." - return [ - *truncated_explanation, + return truncated_explanation + [ "", f"...Full output truncated ({truncated_line_count} line" f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", ] -def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: +def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: # Find point at which input length exceeds total allowed length iterated_char_count = 0 for iterated_index, input_line in enumerate(input_lines): diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/util.py b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/util.py index f35d83a6..fc5dfdbd 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/util.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/assertion/util.py @@ -1,54 +1,36 @@ -# mypy: allow-untyped-defs """Utilities for assertion debugging.""" - -from __future__ import annotations - import collections.abc -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Mapping -from collections.abc import Sequence -from collections.abc import Set as AbstractSet +import os import pprint +from typing import AbstractSet from typing import Any -from typing import Literal -from typing import Protocol +from typing import Callable +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence from unicodedata import normalize -from _pytest import outcomes import _pytest._code -from _pytest._io.pprint import PrettyPrinter +from _pytest import outcomes +from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited -from _pytest.compat import running_on_ci from _pytest.config import Config - # The _reprcompare attribute on the util module is used by the new assertion # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare: Callable[[str, object, object], str | None] | None = None +_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. -_assertion_pass: Callable[[int, str, str], None] | None = None +_assertion_pass: Optional[Callable[[int, str, str], None]] = None # Config object which is assigned during pytest_runtest_protocol. -_config: Config | None = None - - -class _HighlightFunc(Protocol): - def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str: - """Apply highlighting to the given source.""" - - -def dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str: - """Dummy highlighter that returns the text unprocessed. - - Needed for _notin_text, as the diff gets post-processed to only show the "+" part. - """ - return source +_config: Optional[Config] = None def format_explanation(explanation: str) -> str: @@ -66,7 +48,7 @@ def format_explanation(explanation: str) -> str: return "\n".join(result) -def _split_explanation(explanation: str) -> list[str]: +def _split_explanation(explanation: str) -> List[str]: r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. @@ -83,7 +65,7 @@ def _split_explanation(explanation: str) -> list[str]: return lines -def _format_lines(lines: Sequence[str]) -> list[str]: +def _format_lines(lines: Sequence[str]) -> List[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting @@ -131,7 +113,7 @@ def isdict(x: Any) -> bool: def isset(x: Any) -> bool: - return isinstance(x, set | frozenset) + return isinstance(x, (set, frozenset)) def isnamedtuple(obj: Any) -> bool: @@ -150,7 +132,7 @@ def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) - except Exception: + except TypeError: return False @@ -169,7 +151,7 @@ def has_default_eq( code_filename = obj.__eq__.__code__.co_filename if isattrs(obj): - return "attrs generated " in code_filename + return "attrs generated eq" in code_filename return code_filename == "" # data class return True @@ -177,9 +159,9 @@ def has_default_eq( def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False -) -> list[str] | None: +) -> Optional[List[str]]: """Return specialised explanations for some operators/operands.""" - verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) + verbose = config.getoption("verbose") # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier. # See issue #3246. @@ -203,54 +185,34 @@ def assertrepr_compare( right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" - highlighter = config.get_terminal_writer()._highlight explanation = None try: if op == "==": - explanation = _compare_eq_any(left, right, highlighter, verbose) + explanation = _compare_eq_any(left, right, verbose) elif op == "not in": if istext(left) and istext(right): explanation = _notin_text(left, right, verbose) - elif op == "!=": - if isset(left) and isset(right): - explanation = ["Both sets are equal"] - elif op == ">=": - if isset(left) and isset(right): - explanation = _compare_gte_set(left, right, highlighter, verbose) - elif op == "<=": - if isset(left) and isset(right): - explanation = _compare_lte_set(left, right, highlighter, verbose) - elif op == ">": - if isset(left) and isset(right): - explanation = _compare_gt_set(left, right, highlighter, verbose) - elif op == "<": - if isset(left) and isset(right): - explanation = _compare_lt_set(left, right, highlighter, verbose) - except outcomes.Exit: raise except Exception: - repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash() explanation = [ - f"(pytest_assertion plugin: representation of details failed: {repr_crash}.", + "(pytest_assertion plugin: representation of details failed: {}.".format( + _pytest._code.ExceptionInfo.from_current()._getreprcrash() + ), " Probably an object has a faulty __repr__.)", ] if not explanation: return None - if explanation[0] != "": - explanation = ["", *explanation] - return [summary, *explanation] + return [summary] + explanation -def _compare_eq_any( - left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 -) -> list[str]: +def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: explanation = [] if istext(left) and istext(right): - explanation = _diff_text(left, right, highlighter, verbose) + explanation = _diff_text(left, right, verbose) else: from _pytest.python_api import ApproxBase @@ -260,31 +222,29 @@ def _compare_eq_any( other_side = right if isinstance(left, ApproxBase) else left explanation = approx_side._repr_compare(other_side) - elif type(left) is type(right) and ( + elif type(left) == type(right) and ( isdatacls(left) or isattrs(left) or isnamedtuple(left) ): # Note: unlike dataclasses/attrs, namedtuples compare only the # field values, not the type or field names. But this branch # intentionally only handles the same-type case, which was often # used in older code bases before dataclasses/attrs were available. - explanation = _compare_eq_cls(left, right, highlighter, verbose) + explanation = _compare_eq_cls(left, right, verbose) elif issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right, highlighter, verbose) + explanation = _compare_eq_sequence(left, right, verbose) elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right, highlighter, verbose) + explanation = _compare_eq_set(left, right, verbose) elif isdict(left) and isdict(right): - explanation = _compare_eq_dict(left, right, highlighter, verbose) + explanation = _compare_eq_dict(left, right, verbose) if isiterable(left) and isiterable(right): - expl = _compare_eq_iterable(left, right, highlighter, verbose) + expl = _compare_eq_iterable(left, right, verbose) explanation.extend(expl) return explanation -def _diff_text( - left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0 -) -> list[str]: +def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing @@ -292,7 +252,7 @@ def _diff_text( """ from difflib import ndiff - explanation: list[str] = [] + explanation: List[str] = [] if verbose < 1: i = 0 # just in case left or right has zero length @@ -302,7 +262,7 @@ def _diff_text( if i > 42: i -= 10 # Provide some context explanation = [ - f"Skipping {i} identical leading characters in diff, use -v to show" + "Skipping %s identical leading characters in diff, use -v to show" % i ] left = left[i:] right = right[i:] @@ -313,8 +273,8 @@ def _diff_text( if i > 42: i -= 10 # Provide some context explanation += [ - f"Skipping {i} identical trailing " - "characters in diff, use -v to show" + "Skipping {} identical trailing " + "characters in diff, use -v to show".format(i) ] left = left[:-i] right = right[:-i] @@ -325,55 +285,61 @@ def _diff_text( explanation += ["Strings contain only whitespace, escaping them using repr()"] # "right" is the expected base against which we compare "left", # see https://github.com/pytest-dev/pytest/issues/3333 - explanation.extend( - highlighter( - "\n".join( - line.strip("\n") - for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) - ), - lexer="diff", - ).splitlines() - ) + explanation += [ + line.strip("\n") + for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) + ] return explanation +def _surrounding_parens_on_own_lines(lines: List[str]) -> None: + """Move opening/closing parenthesis/bracket to own lines.""" + opening = lines[0][:1] + if opening in ["(", "[", "{"]: + lines[0] = " " + lines[0][1:] + lines[:] = [opening] + lines + closing = lines[-1][-1:] + if closing in [")", "]", "}"]: + lines[-1] = lines[-1][:-1] + "," + lines[:] = lines + [closing] + + def _compare_eq_iterable( - left: Iterable[Any], - right: Iterable[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: + left: Iterable[Any], right: Iterable[Any], verbose: int = 0 +) -> List[str]: if verbose <= 0 and not running_on_ci(): return ["Use -v to get more diff"] # dynamic import to speedup pytest import difflib - left_formatting = PrettyPrinter().pformat(left).splitlines() - right_formatting = PrettyPrinter().pformat(right).splitlines() + left_formatting = pprint.pformat(left).splitlines() + right_formatting = pprint.pformat(right).splitlines() - explanation = ["", "Full diff:"] + # Re-format for different output lengths. + lines_left = len(left_formatting) + lines_right = len(right_formatting) + if lines_left != lines_right: + left_formatting = _pformat_dispatch(left).splitlines() + right_formatting = _pformat_dispatch(right).splitlines() + + if lines_left > 1 or lines_right > 1: + _surrounding_parens_on_own_lines(left_formatting) + _surrounding_parens_on_own_lines(right_formatting) + + explanation = ["Full diff:"] # "right" is the expected base against which we compare "left", # see https://github.com/pytest-dev/pytest/issues/3333 explanation.extend( - highlighter( - "\n".join( - line.rstrip() - for line in difflib.ndiff(right_formatting, left_formatting) - ), - lexer="diff", - ).splitlines() + line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) ) return explanation def _compare_eq_sequence( - left: Sequence[Any], - right: Sequence[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: + left: Sequence[Any], right: Sequence[Any], verbose: int = 0 +) -> List[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - explanation: list[str] = [] + explanation: List[str] = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -393,10 +359,7 @@ def _compare_eq_sequence( left_value = left[i] right_value = right[i] - explanation.append( - f"At index {i} diff:" - f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" - ) + explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"] break if comparing_bytes: @@ -416,134 +379,74 @@ def _compare_eq_sequence( extra = saferepr(right[len_left]) if len_diff == 1: - explanation += [ - f"{dir_with_more} contains one more item: {highlighter(extra)}" - ] + explanation += [f"{dir_with_more} contains one more item: {extra}"] else: explanation += [ - f"{dir_with_more} contains {len_diff} more items, first extra item: {highlighter(extra)}" + "%s contains %d more items, first extra item: %s" + % (dir_with_more, len_diff, extra) ] return explanation def _compare_eq_set( - left: AbstractSet[Any], - right: AbstractSet[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: + left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 +) -> List[str]: explanation = [] - explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) - explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) - return explanation - - -def _compare_gt_set( - left: AbstractSet[Any], - right: AbstractSet[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: - explanation = _compare_gte_set(left, right, highlighter) - if not explanation: - return ["Both sets are equal"] - return explanation - - -def _compare_lt_set( - left: AbstractSet[Any], - right: AbstractSet[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: - explanation = _compare_lte_set(left, right, highlighter) - if not explanation: - return ["Both sets are equal"] - return explanation - - -def _compare_gte_set( - left: AbstractSet[Any], - right: AbstractSet[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: - return _set_one_sided_diff("right", right, left, highlighter) - - -def _compare_lte_set( - left: AbstractSet[Any], - right: AbstractSet[Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: - return _set_one_sided_diff("left", left, right, highlighter) - - -def _set_one_sided_diff( - posn: str, - set1: AbstractSet[Any], - set2: AbstractSet[Any], - highlighter: _HighlightFunc, -) -> list[str]: - explanation = [] - diff = set1 - set2 - if diff: - explanation.append(f"Extra items in the {posn} set:") - for item in diff: - explanation.append(highlighter(saferepr(item))) + diff_left = left - right + diff_right = right - left + if diff_left: + explanation.append("Extra items in the left set:") + for item in diff_left: + explanation.append(saferepr(item)) + if diff_right: + explanation.append("Extra items in the right set:") + for item in diff_right: + explanation.append(saferepr(item)) return explanation def _compare_eq_dict( - left: Mapping[Any, Any], - right: Mapping[Any, Any], - highlighter: _HighlightFunc, - verbose: int = 0, -) -> list[str]: - explanation: list[str] = [] + left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0 +) -> List[str]: + explanation: List[str] = [] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) same = {k: left[k] for k in common if left[k] == right[k]} if same and verbose < 2: - explanation += [f"Omitting {len(same)} identical items, use -vv to show"] + explanation += ["Omitting %s identical items, use -vv to show" % len(same)] elif same: explanation += ["Common items:"] - explanation += highlighter(pprint.pformat(same)).splitlines() + explanation += pprint.pformat(same).splitlines() diff = {k for k in common if left[k] != right[k]} if diff: explanation += ["Differing items:"] for k in diff: - explanation += [ - highlighter(saferepr({k: left[k]})) - + " != " - + highlighter(saferepr({k: right[k]})) - ] + explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] extra_left = set_left - set_right len_extra_left = len(extra_left) if len_extra_left: explanation.append( - f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:" + "Left contains %d more item%s:" + % (len_extra_left, "" if len_extra_left == 1 else "s") ) explanation.extend( - highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() + pprint.pformat({k: left[k] for k in extra_left}).splitlines() ) extra_right = set_right - set_left len_extra_right = len(extra_right) if len_extra_right: explanation.append( - f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:" + "Right contains %d more item%s:" + % (len_extra_right, "" if len_extra_right == 1 else "s") ) explanation.extend( - highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() + pprint.pformat({k: right[k] for k in extra_right}).splitlines() ) return explanation -def _compare_eq_cls( - left: Any, right: Any, highlighter: _HighlightFunc, verbose: int -) -> list[str]: +def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]: if not has_default_eq(left): return [] if isdatacls(left): @@ -572,37 +475,35 @@ def _compare_eq_cls( if same or diff: explanation += [""] if same and verbose < 2: - explanation.append(f"Omitting {len(same)} identical items, use -vv to show") + explanation.append("Omitting %s identical items, use -vv to show" % len(same)) elif same: explanation += ["Matching attributes:"] - explanation += highlighter(pprint.pformat(same)).splitlines() + explanation += pprint.pformat(same).splitlines() if diff: explanation += ["Differing attributes:"] - explanation += highlighter(pprint.pformat(diff)).splitlines() + explanation += pprint.pformat(diff).splitlines() for field in diff: field_left = getattr(left, field) field_right = getattr(right, field) explanation += [ "", - f"Drill down into differing attribute {field}:", - f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}", + "Drill down into differing attribute %s:" % field, + ("%s%s: %r != %r") % (indent, field, field_left, field_right), ] explanation += [ indent + line - for line in _compare_eq_any( - field_left, field_right, highlighter, verbose - ) + for line in _compare_eq_any(field_left, field_right, verbose) ] return explanation -def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: +def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: index = text.find(term) head = text[:index] tail = text[index + len(term) :] correct_text = head + tail - diff = _diff_text(text, correct_text, dummy_highlighter, verbose) - newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"] + diff = _diff_text(text, correct_text, verbose) + newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)] for line in diff: if line.startswith("Skipping"): continue @@ -613,3 +514,9 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: else: newdiff.append(line) return newdiff + + +def running_on_ci() -> bool: + """Check if we're currently running on a CI system.""" + env_vars = ["CI", "BUILD_NUMBER"] + return any(var in os.environ for var in env_vars) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/cacheprovider.py b/Backend/venv/lib/python3.12/site-packages/_pytest/cacheprovider.py index 4383f105..1ecb8650 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/cacheprovider.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/cacheprovider.py @@ -1,25 +1,24 @@ -# mypy: allow-untyped-defs """Implementation of the cache provider.""" - # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Iterable import dataclasses -import errno import json import os from pathlib import Path -import tempfile -from typing import final +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Set +from typing import Union from .pathlib import resolve_from_str from .pathlib import rm_rf from .reports import CollectReport from _pytest import nodes from _pytest._io import TerminalWriter +from _pytest.compat import final from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl @@ -28,11 +27,10 @@ from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.main import Session -from _pytest.nodes import Directory from _pytest.nodes import File +from _pytest.python import Package from _pytest.reports import TestReport - README_CONTENT = """\ # pytest cache directory # @@ -74,7 +72,7 @@ class Cache: self._config = config @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: + def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": """Create the Cache instance for a Config. :meta private: @@ -113,7 +111,6 @@ class Cache: """ check_ispytest(_ispytest) import warnings - from _pytest.warning_types import PytestCacheWarning warnings.warn( @@ -122,10 +119,6 @@ class Cache: stacklevel=3, ) - def _mkdir(self, path: Path) -> None: - self._ensure_cache_dir_and_supporting_files() - path.mkdir(exist_ok=True, parents=True) - def mkdir(self, name: str) -> Path: """Return a directory path object with the given name. @@ -144,7 +137,7 @@ class Cache: if len(path.parts) > 1: raise ValueError("name is not allowed to contain path separators") res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) - self._mkdir(res) + res.mkdir(exist_ok=True, parents=True) return res def _getvaluepath(self, key: str) -> Path: @@ -181,13 +174,19 @@ class Cache: """ path = self._getvaluepath(key) try: - self._mkdir(path.parent) + if path.parent.is_dir(): + cache_dir_exists_already = True + else: + cache_dir_exists_already = self._cachedir.exists() + path.parent.mkdir(exist_ok=True, parents=True) except OSError as exc: self.warn( f"could not create cache path {path}: {exc}", _ispytest=True, ) return + if not cache_dir_exists_already: + self._ensure_supporting_files() data = json.dumps(value, ensure_ascii=False, indent=2) try: f = path.open("w", encoding="UTF-8") @@ -200,85 +199,60 @@ class Cache: with f: f.write(data) - def _ensure_cache_dir_and_supporting_files(self) -> None: - """Create the cache dir and its supporting files.""" - if self._cachedir.is_dir(): - return + def _ensure_supporting_files(self) -> None: + """Create supporting files in the cache dir that are not really part of the cache.""" + readme_path = self._cachedir / "README.md" + readme_path.write_text(README_CONTENT, encoding="UTF-8") - self._cachedir.parent.mkdir(parents=True, exist_ok=True) - with tempfile.TemporaryDirectory( - prefix="pytest-cache-files-", - dir=self._cachedir.parent, - ) as newpath: - path = Path(newpath) + gitignore_path = self._cachedir.joinpath(".gitignore") + msg = "# Created by pytest automatically.\n*\n" + gitignore_path.write_text(msg, encoding="UTF-8") - # Reset permissions to the default, see #12308. - # Note: there's no way to get the current umask atomically, eek. - umask = os.umask(0o022) - os.umask(umask) - path.chmod(0o777 - umask) - - with open(path.joinpath("README.md"), "x", encoding="UTF-8") as f: - f.write(README_CONTENT) - with open(path.joinpath(".gitignore"), "x", encoding="UTF-8") as f: - f.write("# Created by pytest automatically.\n*\n") - with open(path.joinpath("CACHEDIR.TAG"), "xb") as f: - f.write(CACHEDIR_TAG_CONTENT) - - try: - path.rename(self._cachedir) - except OSError as e: - # If 2 concurrent pytests both race to the rename, the loser - # gets "Directory not empty" from the rename. In this case, - # everything is handled so just continue (while letting the - # temporary directory be cleaned up). - # On Windows, the error is a FileExistsError which translates to EEXIST. - if e.errno not in (errno.ENOTEMPTY, errno.EEXIST): - raise - else: - # Create a directory in place of the one we just moved so that - # `TemporaryDirectory`'s cleanup doesn't complain. - # - # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10. - # See https://github.com/python/cpython/issues/74168. Note that passing - # delete=False would do the wrong thing in case of errors and isn't supported - # until python 3.12. - path.mkdir() + cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") + cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) class LFPluginCollWrapper: - def __init__(self, lfplugin: LFPlugin) -> None: + def __init__(self, lfplugin: "LFPlugin") -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False - @hookimpl(wrapper=True) - def pytest_make_collect_report( - self, collector: nodes.Collector - ) -> Generator[None, CollectReport, CollectReport]: - res = yield - if isinstance(collector, Session | Directory): + @hookimpl(hookwrapper=True) + def pytest_make_collect_report(self, collector: nodes.Collector): + if isinstance(collector, (Session, Package)): + out = yield + res: CollectReport = out.get_result() + # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths - # Use stable sort to prioritize last failed. - def sort_key(node: nodes.Item | nodes.Collector) -> bool: - return node.path in lf_paths + # Use stable sort to priorize last failed. + def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + # Package.path is the __init__.py file, we need the directory. + if isinstance(node, Package): + path = node.path.parent + else: + path = node.path + return path in lf_paths res.result = sorted( res.result, key=sort_key, reverse=True, ) + return elif isinstance(collector, File): if collector.path in self.lfplugin._last_failed_paths: + out = yield + res = out.get_result() result = res.result lastfailed = self.lfplugin.lastfailed # Only filter with known failures. if not self._collected_at_least_one_failure: if not any(x.nodeid in lastfailed for x in result): - return res + return self.lfplugin.config.pluginmanager.register( LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" ) @@ -294,19 +268,21 @@ class LFPluginCollWrapper: # Keep all sub-collectors. or isinstance(x, nodes.Collector) ] - - return res + return + yield class LFPluginCollSkipfiles: - def __init__(self, lfplugin: LFPlugin) -> None: + def __init__(self, lfplugin: "LFPlugin") -> None: self.lfplugin = lfplugin @hookimpl def pytest_make_collect_report( self, collector: nodes.Collector - ) -> CollectReport | None: - if isinstance(collector, File): + ) -> Optional[CollectReport]: + # Packages are Files, but we only want to skip test-bearing Files, + # so don't filter Packages. + if isinstance(collector, File) and not isinstance(collector, Package): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -324,9 +300,9 @@ class LFPlugin: active_keys = "lf", "failedfirst" self.active = any(config.getoption(key) for key in active_keys) assert config.cache - self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count: int | None = None - self._report_status: str | None = None + self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._previously_failed_count: Optional[int] = None + self._report_status: Optional[str] = None self._skipped_files = 0 # count skipped files during collection due to --lf if config.getoption("lf"): @@ -335,7 +311,7 @@ class LFPlugin: LFPluginCollWrapper(self), "lfplugin-collwrapper" ) - def get_last_failed_paths(self) -> set[Path]: + def get_last_failed_paths(self) -> Set[Path]: """Return a set with all Paths of the previously failed nodeids and their parents.""" rootpath = self.config.rootpath @@ -346,9 +322,9 @@ class LFPlugin: result.update(path.parents) return {x for x in result if x.exists()} - def pytest_report_collectionfinish(self) -> str | None: - if self.active and self.config.get_verbosity() >= 0: - return f"run-last-failure: {self._report_status}" + def pytest_report_collectionfinish(self) -> Optional[str]: + if self.active and self.config.getoption("verbose") >= 0: + return "run-last-failure: %s" % self._report_status return None def pytest_runtest_logreport(self, report: TestReport) -> None: @@ -366,14 +342,14 @@ class LFPlugin: else: self.lastfailed[report.nodeid] = True - @hookimpl(wrapper=True, tryfirst=True) + @hookimpl(hookwrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, config: Config, items: list[nodes.Item] - ) -> Generator[None]: - res = yield + self, config: Config, items: List[nodes.Item] + ) -> Generator[None, None, None]: + yield if not self.active: - return res + return if self.lastfailed: previously_failed = [] @@ -388,8 +364,8 @@ class LFPlugin: if not previously_failed: # Running a subset of all tests with recorded failures # only outside of it. - self._report_status = ( - f"{len(self.lastfailed)} known failures not in selected tests" + self._report_status = "%d known failures not in selected tests" % ( + len(self.lastfailed), ) else: if self.config.getoption("lf"): @@ -400,13 +376,15 @@ class LFPlugin: noun = "failure" if self._previously_failed_count == 1 else "failures" suffix = " first" if self.config.getoption("failedfirst") else "" - self._report_status = ( - f"rerun previous {self._previously_failed_count} {noun}{suffix}" + self._report_status = "rerun previous {count} {noun}{suffix}".format( + count=self._previously_failed_count, suffix=suffix, noun=noun ) if self._skipped_files > 0: files_noun = "file" if self._skipped_files == 1 else "files" - self._report_status += f" (skipped {self._skipped_files} {files_noun})" + self._report_status += " (skipped {files} {files_noun})".format( + files=self._skipped_files, files_noun=files_noun + ) else: self._report_status = "no previously failed tests, " if self.config.getoption("last_failed_no_failures") == "none": @@ -416,8 +394,6 @@ class LFPlugin: else: self._report_status += "not deselecting items." - return res - def pytest_sessionfinish(self, session: Session) -> None: config = self.config if config.getoption("cacheshow") or hasattr(config, "workerinput"): @@ -438,13 +414,15 @@ class NFPlugin: assert config.cache is not None self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) - @hookimpl(wrapper=True, tryfirst=True) - def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> Generator[None]: - res = yield + @hookimpl(hookwrapper=True, tryfirst=True) + def pytest_collection_modifyitems( + self, items: List[nodes.Item] + ) -> Generator[None, None, None]: + yield if self.active: - new_items: dict[str, nodes.Item] = {} - other_items: dict[str, nodes.Item] = {} + new_items: Dict[str, nodes.Item] = {} + other_items: Dict[str, nodes.Item] = {} for item in items: if item.nodeid not in self.cached_nodeids: new_items[item.nodeid] = item @@ -458,10 +436,8 @@ class NFPlugin: else: self.cached_nodeids.update(item.nodeid for item in items) - return res - - def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: - return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: + return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return] def pytest_sessionfinish(self) -> None: config = self.config @@ -476,17 +452,14 @@ class NFPlugin: def pytest_addoption(parser: Parser) -> None: - """Add command-line options for cache functionality. - - :param parser: Parser object to add command-line options to. - """ group = parser.getgroup("general") group.addoption( "--lf", "--last-failed", action="store_true", dest="lf", - help="Rerun only the tests that failed at the last run (or all if none failed)", + help="Rerun only the tests that failed " + "at the last run (or all if none failed)", ) group.addoption( "--ff", @@ -540,7 +513,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session @@ -550,13 +523,6 @@ def pytest_cmdline_main(config: Config) -> int | ExitCode | None: @hookimpl(tryfirst=True) def pytest_configure(config: Config) -> None: - """Configure cache system and register related plugins. - - Creates the Cache instance and registers the last-failed (LFPlugin) - and new-first (NFPlugin) plugins with the plugin manager. - - :param config: pytest configuration object. - """ config.cache = Cache.for_config(config, _ispytest=True) config.pluginmanager.register(LFPlugin(config), "lfplugin") config.pluginmanager.register(NFPlugin(config), "nfplugin") @@ -578,7 +544,7 @@ def cache(request: FixtureRequest) -> Cache: return request.config.cache -def pytest_report_header(config: Config) -> str | None: +def pytest_report_header(config: Config) -> Optional[str]: """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": assert config.cache is not None @@ -595,16 +561,6 @@ def pytest_report_header(config: Config) -> str | None: def cacheshow(config: Config, session: Session) -> int: - """Display cache contents when --cache-show is used. - - Shows cached values and directories matching the specified glob pattern - (default: '*'). Displays cache location, cached test results, and - any cached directories created by plugins. - - :param config: pytest configuration object. - :param session: pytest session object. - :returns: Exit code (0 for success). - """ from pprint import pformat assert config.cache is not None @@ -622,25 +578,25 @@ def cacheshow(config: Config, session: Session) -> int: dummy = object() basedir = config.cache._cachedir vdir = basedir / Cache._CACHE_PREFIX_VALUES - tw.sep("-", f"cache values for {glob!r}") + tw.sep("-", "cache values for %r" % glob) for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): key = str(valpath.relative_to(vdir)) val = config.cache.get(key, dummy) if val is dummy: - tw.line(f"{key} contains unreadable content, will be ignored") + tw.line("%s contains unreadable content, will be ignored" % key) else: - tw.line(f"{key} contains:") + tw.line("%s contains:" % key) for line in pformat(val).splitlines(): tw.line(" " + line) ddir = basedir / Cache._CACHE_PREFIX_DIRS if ddir.is_dir(): contents = sorted(ddir.rglob(glob)) - tw.sep("-", f"cache directories for {glob!r}") + tw.sep("-", "cache directories for %r" % glob) for p in contents: # if p.is_dir(): # print("%s/" % p.relative_to(basedir)) if p.is_file(): key = str(p.relative_to(basedir)) - tw.line(f"{key} is a file of length {p.stat().st_size}") + tw.line(f"{key} is a file of length {p.stat().st_size:d}") return 0 diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/capture.py b/Backend/venv/lib/python3.12/site-packages/_pytest/capture.py index 6d98676b..a8ca0869 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/capture.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/capture.py @@ -1,36 +1,30 @@ -# mypy: allow-untyped-defs """Per-test stdout/stderr capturing mechanism.""" - -from __future__ import annotations - import abc import collections -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator import contextlib import io -from io import UnsupportedOperation import os import sys +from io import UnsupportedOperation from tempfile import TemporaryFile from types import TracebackType from typing import Any from typing import AnyStr from typing import BinaryIO -from typing import cast -from typing import Final -from typing import final +from typing import Generator from typing import Generic -from typing import Literal +from typing import Iterable +from typing import Iterator +from typing import List from typing import NamedTuple +from typing import Optional from typing import TextIO +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING +from typing import Union - -if TYPE_CHECKING: - from typing_extensions import Self - +from _pytest.compat import final from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -40,15 +34,17 @@ from _pytest.fixtures import SubRequest from _pytest.nodes import Collector from _pytest.nodes import File from _pytest.nodes import Item -from _pytest.reports import CollectReport +if TYPE_CHECKING: + from typing_extensions import Final + from typing_extensions import Literal -_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] + _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group.addoption( + group._addoption( "--capture", action="store", default="fd", @@ -56,7 +52,7 @@ def pytest_addoption(parser: Parser) -> None: choices=["fd", "sys", "no", "tee-sys"], help="Per-test capturing method: one of fd|sys|no|tee-sys", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-s", action="store_const", const="no", @@ -80,23 +76,6 @@ def _colorama_workaround() -> None: pass -def _readline_workaround() -> None: - """Ensure readline is imported early so it attaches to the correct stdio handles. - - This isn't a problem with the default GNU readline implementation, but in - some configurations, Python uses libedit instead (on macOS, and for prebuilt - binaries such as used by uv). - - In theory this is only needed if readline.backend == "libedit", but the - workaround consists of importing readline here, so we already worked around - the issue by the time we could check if we need to. - """ - try: - import readline # noqa: F401 - except ImportError: - pass - - def _windowsconsoleio_workaround(stream: TextIO) -> None: """Workaround for Windows Unicode console handling. @@ -125,16 +104,17 @@ def _windowsconsoleio_workaround(stream: TextIO) -> None: return # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). - if not hasattr(stream, "buffer"): # type: ignore[unreachable,unused-ignore] + if not hasattr(stream, "buffer"): # type: ignore[unreachable] return - raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer + buffered = hasattr(stream.buffer, "raw") + raw_stdout = stream.buffer.raw if buffered else stream.buffer # type: ignore[attr-defined] - if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined,unused-ignore] + if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined] return def _reopen_stdio(f, mode): - if not hasattr(stream.buffer, "raw") and mode[0] == "w": + if not buffered and mode[0] == "w": buffering = 0 else: buffering = -1 @@ -152,13 +132,12 @@ def _windowsconsoleio_workaround(stream: TextIO) -> None: sys.stderr = _reopen_stdio(sys.stderr, "wb") -@hookimpl(wrapper=True) -def pytest_load_initial_conftests(early_config: Config) -> Generator[None]: +@hookimpl(hookwrapper=True) +def pytest_load_initial_conftests(early_config: Config): ns = early_config.known_args_namespace if ns.capture == "fd": _windowsconsoleio_workaround(sys.stdout) _colorama_workaround() - _readline_workaround() pluginmanager = early_config.pluginmanager capman = CaptureManager(ns.capture) pluginmanager.register(capman, "capturemanager") @@ -168,16 +147,12 @@ def pytest_load_initial_conftests(early_config: Config) -> Generator[None]: # Finally trigger conftest loading but while capturing (issue #93). capman.start_global_capturing() - try: - try: - yield - finally: - capman.suspend_global_capture() - except BaseException: + outcome = yield + capman.suspend_global_capture() + if outcome.excinfo is not None: out, err = capman.read_global_capture() sys.stdout.write(out) sys.stderr.write(err) - raise # IO Helpers. @@ -196,8 +171,7 @@ class EncodedFile(io.TextIOWrapper): def mode(self) -> str: # TextIOWrapper doesn't expose a mode, but at least some of our # tests check it. - assert hasattr(self.buffer, "mode") - return cast(str, self.buffer.mode.replace("b", "")) + return self.buffer.mode.replace("b", "") class CaptureIO(io.TextIOWrapper): @@ -222,7 +196,6 @@ class TeeCaptureIO(CaptureIO): class DontReadFromInput(TextIO): @property def encoding(self) -> str: - assert sys.__stdin__ is not None return sys.__stdin__.encoding def read(self, size: int = -1) -> str: @@ -235,7 +208,7 @@ class DontReadFromInput(TextIO): def __next__(self) -> str: return self.readline() - def readlines(self, hint: int | None = -1) -> list[str]: + def readlines(self, hint: Optional[int] = -1) -> List[str]: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) @@ -267,7 +240,7 @@ class DontReadFromInput(TextIO): def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: int | None = None) -> int: + def truncate(self, size: Optional[int] = None) -> int: raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: @@ -279,14 +252,14 @@ class DontReadFromInput(TextIO): def writable(self) -> bool: return False - def __enter__(self) -> Self: + def __enter__(self) -> "DontReadFromInput": return self def __exit__( self, - type: type[BaseException] | None, - value: BaseException | None, - traceback: TracebackType | None, + type: Optional[Type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], ) -> None: pass @@ -361,7 +334,7 @@ class NoCapture(CaptureBase[str]): class SysCaptureBase(CaptureBase[AnyStr]): def __init__( - self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False + self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False ) -> None: name = patchsysdict[fd] self._old: TextIO = getattr(sys, name) @@ -378,7 +351,7 @@ class SysCaptureBase(CaptureBase[AnyStr]): return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( class_name, self.name, - (hasattr(self, "_old") and repr(self._old)) or "", + hasattr(self, "_old") and repr(self._old) or "", self._state, self.tmpfile, ) @@ -387,16 +360,16 @@ class SysCaptureBase(CaptureBase[AnyStr]): return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( self.__class__.__name__, self.name, - (hasattr(self, "_old") and repr(self._old)) or "", + hasattr(self, "_old") and repr(self._old) or "", self._state, self.tmpfile, ) - def _assert_state(self, op: str, states: tuple[str, ...]) -> None: - assert self._state in states, ( - "cannot {} in state {!r}: expected one of {}".format( - op, self._state, ", ".join(states) - ) + def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) ) def start(self) -> None: @@ -479,7 +452,7 @@ class FDCaptureBase(CaptureBase[AnyStr]): # Further complications are the need to support suspend() and the # possibility of FD reuse (e.g. the tmpfile getting the very same # target FD). The following approach is robust, I believe. - self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -504,16 +477,19 @@ class FDCaptureBase(CaptureBase[AnyStr]): self._state = "initialized" def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} {self.targetfd} oldfd={self.targetfd_save} " - f"_state={self._state!r} tmpfile={self.tmpfile!r}>" + return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.targetfd, + self.targetfd_save, + self._state, + self.tmpfile, ) - def _assert_state(self, op: str, states: tuple[str, ...]) -> None: - assert self._state in states, ( - "cannot {} in state {!r}: expected one of {}".format( - op, self._state, ", ".join(states) - ) + def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) ) def start(self) -> None: @@ -570,7 +546,7 @@ class FDCaptureBinary(FDCaptureBase[bytes]): res = self.tmpfile.buffer.read() self.tmpfile.seek(0) self.tmpfile.truncate() - return res # type: ignore[return-value] + return res def writeorg(self, data: bytes) -> None: """Write to original file descriptor.""" @@ -609,7 +585,7 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING: @final class CaptureResult(NamedTuple, Generic[AnyStr]): - """The result of :method:`caplog.readouterr() `.""" + """The result of :method:`CaptureFixture.readouterr`.""" out: AnyStr err: AnyStr @@ -617,10 +593,9 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING: else: class CaptureResult( - collections.namedtuple("CaptureResult", ["out", "err"]), # noqa: PYI024 - Generic[AnyStr], + collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] ): - """The result of :method:`caplog.readouterr() `.""" + """The result of :method:`CaptureFixture.readouterr`.""" __slots__ = () @@ -631,18 +606,21 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: CaptureBase[AnyStr] | None, - out: CaptureBase[AnyStr] | None, - err: CaptureBase[AnyStr] | None, + in_: Optional[CaptureBase[AnyStr]], + out: Optional[CaptureBase[AnyStr]], + err: Optional[CaptureBase[AnyStr]], ) -> None: - self.in_: CaptureBase[AnyStr] | None = in_ - self.out: CaptureBase[AnyStr] | None = out - self.err: CaptureBase[AnyStr] | None = err + self.in_: Optional[CaptureBase[AnyStr]] = in_ + self.out: Optional[CaptureBase[AnyStr]] = out + self.err: Optional[CaptureBase[AnyStr]] = err def __repr__(self) -> str: - return ( - f"" + return "".format( + self.out, + self.err, + self.in_, + self._state, + self._in_suspended, ) def start_capturing(self) -> None: @@ -654,7 +632,7 @@ class MultiCapture(Generic[AnyStr]): if self.err: self.err.start() - def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: + def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: @@ -709,7 +687,7 @@ class MultiCapture(Generic[AnyStr]): return CaptureResult(out, err) # type: ignore[arg-type] -def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]: +def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: if method == "fd": return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) elif method == "sys": @@ -745,22 +723,21 @@ class CaptureManager: needed to ensure the fixtures take precedence over the global capture. """ - def __init__(self, method: _CaptureMethod) -> None: + def __init__(self, method: "_CaptureMethod") -> None: self._method: Final = method - self._global_capturing: MultiCapture[str] | None = None - self._capture_fixture: CaptureFixture[Any] | None = None + self._global_capturing: Optional[MultiCapture[str]] = None + self._capture_fixture: Optional[CaptureFixture[Any]] = None def __repr__(self) -> str: - return ( - f"" + return "".format( + self._method, self._global_capturing, self._capture_fixture ) - def is_capturing(self) -> str | bool: + def is_capturing(self) -> Union[str, bool]: if self.is_globally_capturing(): return "global" if self._capture_fixture: - return f"fixture {self._capture_fixture.request.fixturename}" + return "fixture %s" % self._capture_fixture.request.fixturename return False # Global capturing control @@ -804,12 +781,14 @@ class CaptureManager: # Fixture Control - def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: + def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: if self._capture_fixture: current_fixture = self._capture_fixture.request.fixturename requested_fixture = capture_fixture.request.fixturename capture_fixture.request.raiseerror( - f"cannot use {requested_fixture} and {current_fixture} at the same time" + "cannot use {} and {} at the same time".format( + requested_fixture, current_fixture + ) ) self._capture_fixture = capture_fixture @@ -838,7 +817,7 @@ class CaptureManager: # Helper context managers @contextlib.contextmanager - def global_and_fixture_disabled(self) -> Generator[None]: + def global_and_fixture_disabled(self) -> Generator[None, None, None]: """Context manager to temporarily disable global and current fixture capturing.""" do_fixture = self._capture_fixture and self._capture_fixture._is_started() if do_fixture: @@ -855,7 +834,7 @@ class CaptureManager: self.resume_fixture() @contextlib.contextmanager - def item_capture(self, when: str, item: Item) -> Generator[None]: + def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: self.resume_global_capture() self.activate_fixture() try: @@ -864,45 +843,41 @@ class CaptureManager: self.deactivate_fixture() self.suspend_global_capture(in_=False) - out, err = self.read_global_capture() - item.add_report_section(when, "stdout", out) - item.add_report_section(when, "stderr", err) + out, err = self.read_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) # Hooks - @hookimpl(wrapper=True) - def pytest_make_collect_report( - self, collector: Collector - ) -> Generator[None, CollectReport, CollectReport]: + @hookimpl(hookwrapper=True) + def pytest_make_collect_report(self, collector: Collector): if isinstance(collector, File): self.resume_global_capture() - try: - rep = yield - finally: - self.suspend_global_capture() + outcome = yield + self.suspend_global_capture() out, err = self.read_global_capture() + rep = outcome.get_result() if out: rep.sections.append(("Captured stdout", out)) if err: rep.sections.append(("Captured stderr", err)) else: - rep = yield - return rep + yield - @hookimpl(wrapper=True) - def pytest_runtest_setup(self, item: Item) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]: with self.item_capture("setup", item): - return (yield) + yield - @hookimpl(wrapper=True) - def pytest_runtest_call(self, item: Item) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: with self.item_capture("call", item): - return (yield) + yield - @hookimpl(wrapper=True) - def pytest_runtest_teardown(self, item: Item) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]: with self.item_capture("teardown", item): - return (yield) + yield @hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self) -> None: @@ -919,17 +894,15 @@ class CaptureFixture(Generic[AnyStr]): def __init__( self, - captureclass: type[CaptureBase[AnyStr]], + captureclass: Type[CaptureBase[AnyStr]], request: SubRequest, *, - config: dict[str, Any] | None = None, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass: type[CaptureBase[AnyStr]] = captureclass + self.captureclass: Type[CaptureBase[AnyStr]] = captureclass self.request = request - self._config = config if config else {} - self._capture: MultiCapture[AnyStr] | None = None + self._capture: Optional[MultiCapture[AnyStr]] = None self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER @@ -937,8 +910,8 @@ class CaptureFixture(Generic[AnyStr]): if self._capture is None: self._capture = MultiCapture( in_=None, - out=self.captureclass(1, **self._config), - err=self.captureclass(2, **self._config), + out=self.captureclass(1), + err=self.captureclass(2), ) self._capture.start_capturing() @@ -984,7 +957,7 @@ class CaptureFixture(Generic[AnyStr]): return False @contextlib.contextmanager - def disabled(self) -> Generator[None]: + def disabled(self) -> Generator[None, None, None]: """Temporarily disable capturing while inside the ``with`` block.""" capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( "capturemanager" @@ -997,7 +970,7 @@ class CaptureFixture(Generic[AnyStr]): @fixture -def capsys(request: SubRequest) -> Generator[CaptureFixture[str]]: +def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method @@ -1025,42 +998,7 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str]]: @fixture -def capteesys(request: SubRequest) -> Generator[CaptureFixture[str]]: - r"""Enable simultaneous text capturing and pass-through of writes - to ``sys.stdout`` and ``sys.stderr`` as defined by ``--capture=``. - - - The captured output is made available via ``capteesys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - The output is also passed-through, allowing it to be "live-printed", - reported, or both as defined by ``--capture=``. - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capteesys): - print("hello") - captured = capteesys.readouterr() - assert captured.out == "hello\n" - """ - capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture( - SysCapture, request, config=dict(tee=True), _ispytest=True - ) - capman.set_fixture(capture_fixture) - capture_fixture._start() - yield capture_fixture - capture_fixture.close() - capman.unset_fixture() - - -@fixture -def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: +def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` @@ -1088,7 +1026,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: @fixture -def capfd(request: SubRequest) -> Generator[CaptureFixture[str]]: +def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: r"""Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -1116,7 +1054,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str]]: @fixture -def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]: +def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/compat.py b/Backend/venv/lib/python3.12/site-packages/_pytest/compat.py index 72c3d091..1d0add73 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/compat.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/compat.py @@ -1,28 +1,41 @@ -# mypy: allow-untyped-defs -"""Python version compatibility code and random general utilities.""" - +"""Python version compatibility code.""" from __future__ import annotations -from collections.abc import Callable +import dataclasses import enum import functools import inspect -from inspect import Parameter -from inspect import Signature import os -from pathlib import Path import sys +from inspect import Parameter +from inspect import signature +from pathlib import Path from typing import Any -from typing import Final +from typing import Callable +from typing import Generic from typing import NoReturn +from typing import TYPE_CHECKING +from typing import TypeVar import py +# fmt: off +# Workaround for https://github.com/sphinx-doc/sphinx/issues/10351. +# If `overload` is imported from `compat` instead of from `typing`, +# Sphinx doesn't recognize it as `overload` and the API docs for +# overloaded functions look good again. But type checkers handle +# it fine. +# fmt: on +if True: + from typing import overload as overload -if sys.version_info >= (3, 14): - from annotationlib import Format +if TYPE_CHECKING: + from typing_extensions import Final +_T = TypeVar("_T") +_S = TypeVar("_S") + #: constant to prepare valuing pylib path replacements/lazy proxies later on # intended for removal in pytest 8.0 or 9.0 @@ -42,16 +55,32 @@ def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: Final = NotSetType.token +NOTSET: Final = NotSetType.token # noqa: E305 # fmt: on +if sys.version_info >= (3, 8): + import importlib.metadata + + importlib_metadata = importlib.metadata +else: + import importlib_metadata as importlib_metadata # noqa: F401 + + +def _format_args(func: Callable[..., Any]) -> str: + return str(signature(func)) + + +def is_generator(func: object) -> bool: + genfunc = inspect.isgeneratorfunction(func) + return genfunc and not iscoroutinefunction(func) + def iscoroutinefunction(func: object) -> bool: """Return True if func is a coroutine function (a function defined with async def syntax, and doesn't contain yield), or a function decorated with @asyncio.coroutine. - Note: copied and modified from Python 3.5's builtin coroutines.py to avoid + Note: copied and modified from Python 3.5's builtin couroutines.py to avoid importing asyncio directly, which in turns also initializes the "logging" module as a side-effect (see issue #8). """ @@ -64,14 +93,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def signature(obj: Callable[..., Any]) -> Signature: - """Return signature without evaluating annotations.""" - if sys.version_info >= (3, 14): - return inspect.signature(obj, annotation_format=Format.STRING) - return inspect.signature(obj) - - -def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: +def getlocation(function, curdir: str | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno @@ -81,8 +103,8 @@ def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str: except ValueError: pass else: - return f"{relfn}:{lineno + 1}" - return f"{fn}:{lineno + 1}" + return "%s:%d" % (relfn, lineno + 1) + return "%s:%d" % (fn, lineno + 1) def num_mock_patch_args(function) -> int: @@ -105,9 +127,10 @@ def num_mock_patch_args(function) -> int: def getfuncargnames( - function: Callable[..., object], + function: Callable[..., Any], *, name: str = "", + is_method: bool = False, cls: type | None = None, ) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. @@ -118,8 +141,9 @@ def getfuncargnames( * Aren't bound with functools.partial. * Aren't replaced with mocks. - The cls arguments indicate that the function should be treated as a bound - method even though it's not unless the function is a static method. + The is_method and cls arguments indicate that the function should + be treated as a bound method even though it's not unless, only in + the case of cls, the function is a static method. The name parameter should be the original name in which the function was collected. """ @@ -133,7 +157,7 @@ def getfuncargnames( # creates a tuple of the names of the parameters that don't have # defaults. try: - parameters = signature(function).parameters.values() + parameters = signature(function).parameters except (ValueError, TypeError) as e: from _pytest.outcomes import fail @@ -144,7 +168,7 @@ def getfuncargnames( arg_names = tuple( p.name - for p in parameters + for p in parameters.values() if ( p.kind is Parameter.POSITIONAL_OR_KEYWORD or p.kind is Parameter.KEYWORD_ONLY @@ -155,9 +179,9 @@ def getfuncargnames( name = function.__name__ # If this function should be treated as a bound method even though - # it's passed as an unbound method or function, and its first parameter - # wasn't defined as positional only, remove the first parameter name. - if not any(p.kind is Parameter.POSITIONAL_ONLY for p in parameters) and ( + # it's passed as an unbound method or function, remove the first + # parameter name. + if is_method or ( # Not using `getattr` because we don't want to resolve the staticmethod. # Not using `cls.__dict__` because we want to check the entire MRO. cls @@ -192,13 +216,25 @@ _non_printable_ascii_translate_table.update( ) +def _translate_non_printable(s: str) -> str: + return s.translate(_non_printable_ascii_translate_table) + + +STRING_TYPES = bytes, str + + +def _bytes_to_ascii(val: bytes) -> str: + return val.decode("ascii", "backslashreplace") + + def ascii_escaped(val: bytes | str) -> str: r"""If val is pure ASCII, return it as an str, otherwise, escape bytes objects into a sequence of escaped bytes: b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6' - and escapes strings into a sequence of escaped unicode ids, e.g.: + and escapes unicode objects into a sequence of escaped unicode + ids, e.g.: r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' @@ -209,22 +245,67 @@ def ascii_escaped(val: bytes | str) -> str: a UTF-8 string. """ if isinstance(val, bytes): - ret = val.decode("ascii", "backslashreplace") + ret = _bytes_to_ascii(val) else: ret = val.encode("unicode_escape").decode("ascii") - return ret.translate(_non_printable_ascii_translate_table) + return _translate_non_printable(ret) + + +@dataclasses.dataclass +class _PytestWrapper: + """Dummy wrapper around a function object for internal use only. + + Used to correctly unwrap the underlying function object when we are + creating fixtures, because we wrap the function object ourselves with a + decorator to issue warnings when the fixture function is called directly. + """ + + obj: Any def get_real_func(obj): """Get the real function object of the (possibly) wrapped object by - :func:`functools.wraps`, or :func:`functools.partial`.""" - obj = inspect.unwrap(obj) + functools.wraps or functools.partial.""" + start_obj = obj + for i in range(100): + # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function + # to trigger a warning if it gets called directly instead of by pytest: we don't + # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774) + new_obj = getattr(obj, "__pytest_wrapped__", None) + if isinstance(new_obj, _PytestWrapper): + obj = new_obj.obj + break + new_obj = getattr(obj, "__wrapped__", None) + if new_obj is None: + break + obj = new_obj + else: + from _pytest._io.saferepr import saferepr + raise ValueError( + ("could not find real function of {start}\nstopped at {current}").format( + start=saferepr(start_obj), current=saferepr(obj) + ) + ) if isinstance(obj, functools.partial): obj = obj.func return obj +def get_real_method(obj, holder): + """Attempt to obtain the real function object that might be wrapping + ``obj``, while at the same time returning a bound method to ``holder`` if + the original object was a bound method.""" + try: + is_method = hasattr(obj, "__func__") + obj = get_real_func(obj) + except Exception: # pragma: no cover + return obj + if is_method and hasattr(obj, "__get__") and callable(obj.__get__): + obj = obj.__get__(holder) + return obj + + def getimfunc(func): try: return func.__func__ @@ -257,6 +338,47 @@ def safe_isclass(obj: object) -> bool: return False +if TYPE_CHECKING: + if sys.version_info >= (3, 8): + from typing import final as final + else: + from typing_extensions import final as final +elif sys.version_info >= (3, 8): + from typing import final as final +else: + + def final(f): + return f + + +if sys.version_info >= (3, 8): + from functools import cached_property as cached_property +else: + + class cached_property(Generic[_S, _T]): + __slots__ = ("func", "__doc__") + + def __init__(self, func: Callable[[_S], _T]) -> None: + self.func = func + self.__doc__ = func.__doc__ + + @overload + def __get__( + self, instance: None, owner: type[_S] | None = ... + ) -> cached_property[_S, _T]: + ... + + @overload + def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T: + ... + + def __get__(self, instance, owner=None): + if instance is None: + return self + value = instance.__dict__[self.func.__name__] = self.func(instance) + return value + + def get_user_id() -> int | None: """Return the current process's real user id or None if it could not be determined. @@ -278,37 +400,36 @@ def get_user_id() -> int | None: return uid if uid != ERROR else None -if sys.version_info >= (3, 11): - from typing import assert_never -else: - - def assert_never(value: NoReturn) -> NoReturn: - assert False, f"Unhandled value: {value} ({type(value).__name__})" - - -class CallableBool: - """ - A bool-like object that can also be called, returning its true/false value. - - Used for backwards compatibility in cases where something was supposed to be a method - but was implemented as a simple attribute by mistake (see `TerminalReporter.isatty`). - - Do not use in new code. - """ - - def __init__(self, value: bool) -> None: - self._value = value - - def __bool__(self) -> bool: - return self._value - - def __call__(self) -> bool: - return self._value - - -def running_on_ci() -> bool: - """Check if we're currently running on a CI system.""" - # Only enable CI mode if one of these env variables is defined and non-empty. - # Note: review `regendoc` tox env in case this list is changed. - env_vars = ["CI", "BUILD_NUMBER"] - return any(os.environ.get(var) for var in env_vars) +# Perform exhaustiveness checking. +# +# Consider this example: +# +# MyUnion = Union[int, str] +# +# def handle(x: MyUnion) -> int { +# if isinstance(x, int): +# return 1 +# elif isinstance(x, str): +# return 2 +# else: +# raise Exception('unreachable') +# +# Now suppose we add a new variant: +# +# MyUnion = Union[int, str, bytes] +# +# After doing this, we must remember ourselves to go and update the handle +# function to handle the new variant. +# +# With `assert_never` we can do better: +# +# // raise Exception('unreachable') +# return assert_never(x) +# +# Now, if we forget to handle the new variant, the type-checker will emit a +# compile-time error, instead of the runtime error we would have gotten +# previously. +# +# This also work for Enums (if you use `is` to compare) and Literals. +def assert_never(value: NoReturn) -> NoReturn: + assert False, f"Unhandled value: {value} ({type(value).__name__})" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__init__.py index 9b2afe3e..e3990d17 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__init__.py @@ -1,65 +1,55 @@ -# mypy: allow-untyped-defs -"""Command line options, config-file and conftest.py processing.""" - -from __future__ import annotations - +"""Command line options, ini-file and conftest.py processing.""" import argparse -import builtins import collections.abc -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence -import contextlib import copy import dataclasses import enum -from functools import lru_cache import glob -import importlib.metadata import inspect import os -import pathlib import re import shlex import sys -from textwrap import dedent import types -from types import FunctionType -from typing import Any -from typing import cast -from typing import Final -from typing import final -from typing import IO -from typing import TextIO -from typing import TYPE_CHECKING import warnings +from functools import lru_cache +from pathlib import Path +from textwrap import dedent +from types import FunctionType +from types import TracebackType +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Generator +from typing import IO +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import TextIO +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union -import pluggy from pluggy import HookimplMarker -from pluggy import HookimplOpts from pluggy import HookspecMarker -from pluggy import HookspecOpts from pluggy import PluginManager -from .compat import PathAwareHookProxy +import _pytest._code +import _pytest.deprecated +import _pytest.hookspec from .exceptions import PrintHelp as PrintHelp from .exceptions import UsageError as UsageError from .findpaths import determine_setup -from _pytest import __version__ -import _pytest._code from _pytest._code import ExceptionInfo from _pytest._code import filter_traceback -from _pytest._code.code import TracebackStyle from _pytest._io import TerminalWriter -from _pytest.compat import assert_never -from _pytest.config.argparsing import Argument -from _pytest.config.argparsing import FILE_OR_DIR -from _pytest.config.argparsing import Parser -import _pytest.deprecated -import _pytest.hookspec +from _pytest.compat import final +from _pytest.compat import importlib_metadata # type: ignore[attr-defined] from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.pathlib import absolutepath @@ -72,11 +62,11 @@ from _pytest.stash import Stash from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import warn_explicit_for - if TYPE_CHECKING: - from _pytest.assertion.rewrite import AssertionRewritingHook - from _pytest.cacheprovider import Cache + from _pytest._code.code import _TracebackStyle from _pytest.terminal import TerminalReporter + from .argparsing import Argument + _PluggyPlugin = object """A type to represent plugin objects. @@ -114,21 +104,21 @@ class ExitCode(enum.IntEnum): #: pytest couldn't find tests. NO_TESTS_COLLECTED = 5 - __module__ = "pytest" - class ConftestImportFailure(Exception): def __init__( self, - path: pathlib.Path, - *, - cause: Exception, + path: Path, + excinfo: Tuple[Type[Exception], Exception, TracebackType], ) -> None: + super().__init__(path, excinfo) self.path = path - self.cause = cause + self.excinfo = excinfo def __str__(self) -> str: - return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" + return "{}: {} (from {})".format( + self.excinfo[0].__name__, self.excinfo[1], self.path + ) def filter_traceback_for_conftest_import_failure( @@ -142,33 +132,10 @@ def filter_traceback_for_conftest_import_failure( return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) -def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None: - exc_info = ExceptionInfo.from_exception(e.cause) - tw = TerminalWriter(file) - tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) - exc_info.traceback = exc_info.traceback.filter( - filter_traceback_for_conftest_import_failure - ) - exc_repr = ( - exc_info.getrepr(style="short", chain=False) - if exc_info.traceback - else exc_info.exconly() - ) - formatted_tb = str(exc_repr) - for line in formatted_tb.splitlines(): - tw.line(line.rstrip(), red=True) - - -def print_usage_error(e: UsageError, file: TextIO) -> None: - tw = TerminalWriter(file) - for msg in e.args: - tw.line(f"ERROR: {msg}\n", red=True) - - def main( - args: list[str] | os.PathLike[str] | None = None, - plugins: Sequence[str | _PluggyPlugin] | None = None, -) -> int | ExitCode: + args: Optional[Union[List[str], "os.PathLike[str]"]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> Union[int, ExitCode]: """Perform an in-process test run. :param args: @@ -178,37 +145,41 @@ def main( :returns: An exit code. """ - # Handle a single `--version` argument early to avoid starting up the entire pytest infrastructure. - new_args = sys.argv[1:] if args is None else args - if isinstance(new_args, Sequence) and new_args.count("--version") == 1: - sys.stdout.write(f"pytest {__version__}\n") - return ExitCode.OK - - old_pytest_version = os.environ.get("PYTEST_VERSION") try: - os.environ["PYTEST_VERSION"] = __version__ try: - config = _prepareconfig(new_args, plugins) + config = _prepareconfig(args, plugins) except ConftestImportFailure as e: - print_conftest_import_error(e, file=sys.stderr) + exc_info = ExceptionInfo.from_exc_info(e.excinfo) + tw = TerminalWriter(sys.stderr) + tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) + exc_info.traceback = exc_info.traceback.filter( + filter_traceback_for_conftest_import_failure + ) + exc_repr = ( + exc_info.getrepr(style="short", chain=False) + if exc_info.traceback + else exc_info.exconly() + ) + formatted_tb = str(exc_repr) + for line in formatted_tb.splitlines(): + tw.line(line.rstrip(), red=True) return ExitCode.USAGE_ERROR - - try: - ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) - try: - return ExitCode(ret) - except ValueError: - return ret - finally: - config._ensure_unconfigure() - except UsageError as e: - print_usage_error(e, file=sys.stderr) - return ExitCode.USAGE_ERROR - finally: - if old_pytest_version is None: - os.environ.pop("PYTEST_VERSION", None) else: - os.environ["PYTEST_VERSION"] = old_pytest_version + try: + ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( + config=config + ) + try: + return ExitCode(ret) + except ValueError: + return ret + finally: + config._ensure_unconfigure() + except UsageError as e: + tw = TerminalWriter(sys.stderr) + for msg in e.args: + tw.line(f"ERROR: {msg}\n", red=True) + return ExitCode.USAGE_ERROR def console_main() -> int: @@ -264,8 +235,7 @@ essential_plugins = ( "helpconfig", # Provides -p. ) -default_plugins = ( - *essential_plugins, +default_plugins = essential_plugins + ( "python", "terminal", "debugging", @@ -277,45 +247,46 @@ default_plugins = ( "monkeypatch", "recwarn", "pastebin", + "nose", "assertion", "junitxml", "doctest", "cacheprovider", + "freeze_support", "setuponly", "setupplan", "stepwise", - "unraisableexception", - "threadexception", "warnings", "logging", "reports", + "python_path", + *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), "faulthandler", - "subtests", ) -builtin_plugins = { - *default_plugins, - "pytester", - "pytester_assertions", -} +builtin_plugins = set(default_plugins) +builtin_plugins.add("pytester") +builtin_plugins.add("pytester_assertions") def get_config( - args: Iterable[str] | None = None, - plugins: Sequence[str | _PluggyPlugin] | None = None, -) -> Config: - # Subsequent calls to main will create a fresh instance. + args: Optional[List[str]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> "Config": + # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() - invocation_params = Config.InvocationParams( - args=args or (), - plugins=plugins, - dir=pathlib.Path.cwd(), + config = Config( + pluginmanager, + invocation_params=Config.InvocationParams( + args=args or (), + plugins=plugins, + dir=Path.cwd(), + ), ) - config = Config(pluginmanager, invocation_params=invocation_params) - if invocation_params.args: + if args is not None: # Handle any "-p no:plugin" args. - pluginmanager.consider_preparse(invocation_params.args, exclude_only=True) + pluginmanager.consider_preparse(args, exclude_only=True) for spec in default_plugins: pluginmanager.import_plugin(spec) @@ -323,7 +294,7 @@ def get_config( return config -def get_plugin_manager() -> PytestPluginManager: +def get_plugin_manager() -> "PytestPluginManager": """Obtain a new instance of the :py:class:`pytest.PytestPluginManager`, with default plugins already loaded. @@ -335,10 +306,12 @@ def get_plugin_manager() -> PytestPluginManager: def _prepareconfig( - args: list[str] | os.PathLike[str], - plugins: Sequence[str | _PluggyPlugin] | None = None, -) -> Config: - if isinstance(args, os.PathLike): + args: Optional[Union[List[str], "os.PathLike[str]"]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> "Config": + if args is None: + args = sys.argv[1:] + elif isinstance(args, os.PathLike): args = [os.fspath(args)] elif not isinstance(args, list): msg = ( # type:ignore[unreachable] @@ -346,8 +319,8 @@ def _prepareconfig( ) raise TypeError(msg.format(args, type(args))) - initial_config = get_config(args, plugins) - pluginmanager = initial_config.pluginmanager + config = get_config(args, plugins) + pluginmanager = config.pluginmanager try: if plugins: for plugin in plugins: @@ -355,16 +328,16 @@ def _prepareconfig( pluginmanager.consider_pluginarg(plugin) else: pluginmanager.register(plugin) - config: Config = pluginmanager.hook.pytest_cmdline_parse( + config = pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args ) return config except BaseException: - initial_config._ensure_unconfigure() + config._ensure_unconfigure() raise -def _get_directory(path: pathlib.Path) -> pathlib.Path: +def _get_directory(path: Path) -> Path: """Get the directory of a path - itself if already a directory.""" if path.is_file(): return path.parent @@ -375,10 +348,10 @@ def _get_directory(path: pathlib.Path) -> pathlib.Path: def _get_legacy_hook_marks( method: Any, hook_type: str, - opt_names: tuple[str, ...], -) -> dict[str, bool]: + opt_names: Tuple[str, ...], +) -> Dict[str, bool]: if TYPE_CHECKING: - # abuse typeguard from importlib to avoid massive method type union that's lacking an alias + # abuse typeguard from importlib to avoid massive method type union thats lacking a alias assert inspect.isroutine(method) known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} must_warn: list[str] = [] @@ -415,20 +388,19 @@ class PytestPluginManager(PluginManager): """ def __init__(self) -> None: - from _pytest.assertion import DummyRewriteHook - from _pytest.assertion import RewriteHook + import _pytest.assertion super().__init__("pytest") # -- State related to local conftest plugins. # All loaded conftest modules. - self._conftest_plugins: set[types.ModuleType] = set() + self._conftest_plugins: Set[types.ModuleType] = set() # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} + self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: pathlib.Path | None = None + self._confcutdir: Optional[Path] = None # If set, conftest loading is skipped. self._noconftest = False @@ -437,12 +409,14 @@ class PytestPluginManager(PluginManager): # session (#9478), often with the same path, so cache it. self._get_directory = lru_cache(256)(_get_directory) + self._duplicatepaths: Set[Path] = set() + # plugins that were explicitly skipped with pytest.skip # list of (module name, skip reason) # previously we would issue a warning when a plugin was skipped, but # since we refactored warnings as first citizens of Config, they are # just stored here to be used later. - self.skipped_plugins: list[tuple[str, str]] = [] + self.skipped_plugins: List[Tuple[str, str]] = [] self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -462,20 +436,17 @@ class PytestPluginManager(PluginManager): self.enable_tracing() # Config._consider_importhook will set a real object if required. - self.rewrite_hook: RewriteHook = DummyRewriteHook() + self.rewrite_hook = _pytest.assertion.DummyRewriteHook() # Used to know when we are importing conftests after the pytest_configure stage. self._configured = False - def parse_hookimpl_opts( - self, plugin: _PluggyPlugin, name: str - ) -> HookimplOpts | None: - """:meta private:""" + def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes # (see issue #1073). if not name.startswith("pytest_"): return None - # Ignore names which cannot be hooks. + # Ignore names which can not be hooks. if name == "pytest_plugins": return None @@ -488,24 +459,25 @@ class PytestPluginManager(PluginManager): if not inspect.isroutine(method): return None # Collect unmarked hooks as long as they have the `pytest_' prefix. - legacy = _get_legacy_hook_marks( + return _get_legacy_hook_marks( # type: ignore[return-value] method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") ) - return cast(HookimplOpts, legacy) - def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: - """:meta private:""" + def parse_hookspec_opts(self, module_or_class, name: str): opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: method = getattr(module_or_class, name) if name.startswith("pytest_"): - legacy = _get_legacy_hook_marks( - method, "spec", ("firstresult", "historic") + opts = _get_legacy_hook_marks( # type: ignore[assignment] + method, + "spec", + ("firstresult", "historic"), ) - opts = cast(HookspecOpts, legacy) return opts - def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: + def register( + self, plugin: _PluggyPlugin, name: Optional[str] = None + ) -> Optional[str]: if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( @@ -516,30 +488,26 @@ class PytestPluginManager(PluginManager): ) ) return None - plugin_name = super().register(plugin, name) - if plugin_name is not None: + ret: Optional[str] = super().register(plugin, name) + if ret: self.hook.pytest_plugin_registered.call_historic( - kwargs=dict( - plugin=plugin, - plugin_name=plugin_name, - manager=self, - ) + kwargs=dict(plugin=plugin, manager=self) ) if isinstance(plugin, types.ModuleType): self.consider_module(plugin) - return plugin_name + return ret def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: _PluggyPlugin | None = self.get_plugin(name) + plugin: Optional[_PluggyPlugin] = self.get_plugin(name) return plugin def hasplugin(self, name: str) -> bool: """Return whether a plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config: Config) -> None: + def pytest_configure(self, config: "Config") -> None: """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. @@ -562,15 +530,12 @@ class PytestPluginManager(PluginManager): # def _set_initial_conftests( self, - args: Sequence[str | pathlib.Path], + args: Sequence[Union[str, Path]], pyargs: bool, noconftest: bool, - rootpath: pathlib.Path, - confcutdir: pathlib.Path | None, - invocation_dir: pathlib.Path, - importmode: ImportMode | str, - *, - consider_namespace_packages: bool, + rootpath: Path, + confcutdir: Optional[Path], + importmode: Union[ImportMode, str], ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -579,120 +544,81 @@ class PytestPluginManager(PluginManager): All builtin and 3rd party plugins will have been loaded, however, so common options will not confuse our logic here. """ - self._confcutdir = ( - absolutepath(invocation_dir / confcutdir) if confcutdir else None - ) + current = Path.cwd() + self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None self._noconftest = noconftest self._using_pyargs = pyargs foundanchor = False - for initial_path in args: - path = str(initial_path) + for intitial_path in args: + path = str(intitial_path) # remove node-id syntax i = path.find("::") if i != -1: path = path[:i] - anchor = absolutepath(invocation_dir / path) + anchor = absolutepath(current / path) # Ensure we do not break if what appears to be an anchor # is in fact a very long option (#10169, #11394). if safe_exists(anchor): - self._try_load_conftest( - anchor, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + self._try_load_conftest(anchor, importmode, rootpath) foundanchor = True if not foundanchor: - self._try_load_conftest( - invocation_dir, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + self._try_load_conftest(current, importmode, rootpath) - def _is_in_confcutdir(self, path: pathlib.Path) -> bool: - """Whether to consider the given path to load conftests from.""" + def _is_in_confcutdir(self, path: Path) -> bool: + """Whether a path is within the confcutdir. + + When false, should not load conftest. + """ if self._confcutdir is None: return True - # The semantics here are literally: - # Do not load a conftest if it is found upwards from confcut dir. - # But this is *not* the same as: - # Load only conftests from confcutdir or below. - # At first glance they might seem the same thing, however we do support use cases where - # we want to load conftests that are not found in confcutdir or below, but are found - # in completely different directory hierarchies like packages installed - # in out-of-source trees. - # (see #9767 for a regression where the logic was inverted). return path not in self._confcutdir.parents def _try_load_conftest( - self, - anchor: pathlib.Path, - importmode: str | ImportMode, - rootpath: pathlib.Path, - *, - consider_namespace_packages: bool, + self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path ) -> None: - self._loadconftestmodules( - anchor, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + self._getconftestmodules(anchor, importmode, rootpath) # let's also consider test* subdirs if anchor.is_dir(): for x in anchor.glob("test*"): if x.is_dir(): - self._loadconftestmodules( - x, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + self._getconftestmodules(x, importmode, rootpath) - def _loadconftestmodules( - self, - path: pathlib.Path, - importmode: str | ImportMode, - rootpath: pathlib.Path, - *, - consider_namespace_packages: bool, - ) -> None: + def _getconftestmodules( + self, path: Path, importmode: Union[str, ImportMode], rootpath: Path + ) -> Sequence[types.ModuleType]: if self._noconftest: - return + return [] directory = self._get_directory(path) # Optimization: avoid repeated searches in the same directory. # Assumes always called with same importmode and rootpath. - if directory in self._dirpath2confmods: - return + existing_clist = self._dirpath2confmods.get(directory) + if existing_clist is not None: + return existing_clist + # XXX these days we may rather want to use config.rootpath + # and allow users to opt into looking into the rootdir parent + # directories instead of requiring to specify confcutdir. clist = [] for parent in reversed((directory, *directory.parents)): if self._is_in_confcutdir(parent): conftestpath = parent / "conftest.py" if conftestpath.is_file(): - mod = self._importconftest( - conftestpath, - importmode, - rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + mod = self._importconftest(conftestpath, importmode, rootpath) clist.append(mod) self._dirpath2confmods[directory] = clist - - def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: - directory = self._get_directory(path) - return self._dirpath2confmods.get(directory, ()) + return clist def _rget_with_confmod( self, name: str, - path: pathlib.Path, - ) -> tuple[types.ModuleType, Any]: - modules = self._getconftestmodules(path) + path: Path, + importmode: Union[str, ImportMode], + rootpath: Path, + ) -> Tuple[types.ModuleType, Any]: + modules = self._getconftestmodules(path, importmode, rootpath=rootpath) for mod in reversed(modules): try: return mod, getattr(mod, name) @@ -701,39 +627,22 @@ class PytestPluginManager(PluginManager): raise KeyError(name) def _importconftest( - self, - conftestpath: pathlib.Path, - importmode: str | ImportMode, - rootpath: pathlib.Path, - *, - consider_namespace_packages: bool, + self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path ) -> types.ModuleType: - conftestpath_plugin_name = str(conftestpath) - existing = self.get_plugin(conftestpath_plugin_name) + existing = self.get_plugin(str(conftestpath)) if existing is not None: return cast(types.ModuleType, existing) - # conftest.py files there are not in a Python package all have module - # name "conftest", and thus conflict with each other. Clear the existing - # before loading the new one, otherwise the existing one will be - # returned from the module cache. pkgpath = resolve_package_path(conftestpath) if pkgpath is None: - try: - del sys.modules[conftestpath.stem] - except KeyError: - pass + _ensure_removed_sysmodule(conftestpath.stem) try: - mod = import_path( - conftestpath, - mode=importmode, - root=rootpath, - consider_namespace_packages=consider_namespace_packages, - ) + mod = import_path(conftestpath, mode=importmode, root=rootpath) except Exception as e: assert e.__traceback__ is not None - raise ConftestImportFailure(conftestpath, cause=e) from e + exc_info = (type(e), e, e.__traceback__) + raise ConftestImportFailure(conftestpath, exc_info) from e self._check_non_top_pytest_plugins(mod, conftestpath) @@ -742,21 +651,16 @@ class PytestPluginManager(PluginManager): if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if dirpath in path.parents or path == dirpath: - if mod in mods: - raise AssertionError( - f"While trying to load conftest path {conftestpath!s}, " - f"found that the module {mod} is already loaded with path {mod.__file__}. " - "This is not supposed to happen. Please report this issue to pytest." - ) + assert mod not in mods mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") - self.consider_conftest(mod, registration_name=conftestpath_plugin_name) + self.consider_conftest(mod) return mod def _check_non_top_pytest_plugins( self, mod: types.ModuleType, - conftestpath: pathlib.Path, + conftestpath: Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -809,7 +713,7 @@ class PytestPluginManager(PluginManager): if arg.startswith("no:"): name = arg[3:] if name in essential_plugins: - raise UsageError(f"plugin {name} cannot be disabled") + raise UsageError("plugin %s cannot be disabled" % name) # PR #4304: remove stepwise if cacheprovider is blocked. if name == "cacheprovider": @@ -821,17 +725,18 @@ class PytestPluginManager(PluginManager): self.set_blocked("pytest_" + name) else: name = arg - # Unblock the plugin. - self.unblock(name) + # Unblock the plugin. None indicates that it has been blocked. + # There is no interface with pluggy for this. + if self._name2plugin.get(name, -1) is None: + del self._name2plugin[name] if not name.startswith("pytest_"): - self.unblock("pytest_" + name) + if self._name2plugin.get("pytest_" + name, -1) is None: + del self._name2plugin["pytest_" + name] self.import_plugin(arg, consider_entry_points=True) - def consider_conftest( - self, conftestmodule: types.ModuleType, registration_name: str - ) -> None: + def consider_conftest(self, conftestmodule: types.ModuleType) -> None: """:meta private:""" - self.register(conftestmodule, name=registration_name) + self.register(conftestmodule, name=conftestmodule.__file__) def consider_env(self) -> None: """:meta private:""" @@ -842,7 +747,7 @@ class PytestPluginManager(PluginManager): self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) def _import_plugin_specs( - self, spec: None | types.ModuleType | str | Sequence[str] + self, spec: Union[None, types.ModuleType, str, Sequence[str]] ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: @@ -859,7 +764,7 @@ class PytestPluginManager(PluginManager): # basename for historic purposes but must be imported with the # _pytest prefix. assert isinstance(modname, str), ( - f"module name as text required, got {modname!r}" + "module name as text required, got %r" % modname ) if self.is_blocked(modname) or self.get_plugin(modname) is not None: return @@ -887,8 +792,8 @@ class PytestPluginManager(PluginManager): def _get_plugin_specs_as_list( - specs: None | types.ModuleType | str | Sequence[str], -) -> list[str]: + specs: Union[None, types.ModuleType, str, Sequence[str]] +) -> List[str]: """Parse a plugins specification into a list of plugin names.""" # None means empty. if specs is None: @@ -903,10 +808,18 @@ def _get_plugin_specs_as_list( if isinstance(specs, collections.abc.Sequence): return list(specs) raise UsageError( - f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" + "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" + % specs ) +def _ensure_removed_sysmodule(modname: str) -> None: + try: + del sys.modules[modname] + except KeyError: + pass + + class Notset: def __repr__(self): return "" @@ -1004,24 +917,24 @@ class Config: .. note:: Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` - configuration option are handled by pytest, not being included in the ``args`` attribute. + ini option are handled by pytest, not being included in the ``args`` attribute. Plugins accessing ``InvocationParams`` must be aware of that. """ - args: tuple[str, ...] + args: Tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Sequence[str | _PluggyPlugin] | None + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] """Extra plugins, might be `None`.""" - dir: pathlib.Path + dir: Path """The directory from which :func:`pytest.main` was invoked.""" def __init__( self, *, args: Iterable[str], - plugins: Sequence[str | _PluggyPlugin] | None, - dir: pathlib.Path, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + dir: Path, ) -> None: object.__setattr__(self, "args", tuple(args)) object.__setattr__(self, "plugins", plugins) @@ -1036,23 +949,21 @@ class Config: #: Command line arguments. ARGS = enum.auto() #: Invocation directory. - INVOCATION_DIR = enum.auto() - INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias + INCOVATION_DIR = enum.auto() #: 'testpaths' configuration value. TESTPATHS = enum.auto() - # Set by cacheprovider plugin. - cache: Cache - def __init__( self, pluginmanager: PytestPluginManager, *, - invocation_params: InvocationParams | None = None, + invocation_params: Optional[InvocationParams] = None, ) -> None: + from .argparsing import Parser, FILE_OR_DIR + if invocation_params is None: invocation_params = self.InvocationParams( - args=(), plugins=None, dir=pathlib.Path.cwd() + args=(), plugins=None, dir=Path.cwd() ) self.option = argparse.Namespace() @@ -1067,8 +978,9 @@ class Config: :type: InvocationParams """ + _a = FILE_OR_DIR self._parser = Parser( - usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]", + usage=f"%(prog)s [options] [{_a}] [{_a}] [...]", processopt=self._processopt, _ispytest=True, ) @@ -1087,86 +999,92 @@ class Config: # Deprecated alias. Was never public. Can be removed in a few releases. self._store = self.stash + from .compat import PathAwareHookProxy + self.trace = self.pluginmanager.trace.root.get("config") - self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] - self._inicache: dict[str, Any] = {} - self._opt2dest: dict[str, str] = {} - self._cleanup_stack = contextlib.ExitStack() + self.hook = PathAwareHookProxy(self.pluginmanager.hook) + self._inicache: Dict[str, Any] = {} + self._override_ini: Sequence[str] = () + self._opt2dest: Dict[str, str] = {} + self._cleanup: List[Callable[[], None]] = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) self.args_source = Config.ArgsSource.ARGS - self.args: list[str] = [] + self.args: List[str] = [] + + if TYPE_CHECKING: + from _pytest.cacheprovider import Cache + + self.cache: Optional[Cache] = None @property - def rootpath(self) -> pathlib.Path: + def rootpath(self) -> Path: """The path to the :ref:`rootdir `. + :type: pathlib.Path + .. versionadded:: 6.1 """ return self._rootpath @property - def inipath(self) -> pathlib.Path | None: + def inipath(self) -> Optional[Path]: """The path to the :ref:`configfile `. + :type: Optional[pathlib.Path] + .. versionadded:: 6.1 """ return self._inipath def add_cleanup(self, func: Callable[[], None]) -> None: """Add a function to be called when the config object gets out of - use (usually coinciding with pytest_unconfigure). - """ - self._cleanup_stack.callback(func) + use (usually coinciding with pytest_unconfigure).""" + self._cleanup.append(func) def _do_configure(self) -> None: assert not self._configured self._configured = True - self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) + with warnings.catch_warnings(): + warnings.simplefilter("default") + self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) def _ensure_unconfigure(self) -> None: - try: - if self._configured: - self._configured = False - try: - self.hook.pytest_unconfigure(config=self) - finally: - self.hook.pytest_configure._call_history = [] - finally: - try: - self._cleanup_stack.close() - finally: - self._cleanup_stack = contextlib.ExitStack() + if self._configured: + self._configured = False + self.hook.pytest_unconfigure(config=self) + self.hook.pytest_configure._call_history = [] + while self._cleanup: + fin = self._cleanup.pop() + fin() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( + terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( "terminalreporter" ) assert terminalreporter is not None return terminalreporter._tw def pytest_cmdline_parse( - self, pluginmanager: PytestPluginManager, args: list[str] - ) -> Config: + self, pluginmanager: PytestPluginManager, args: List[str] + ) -> "Config": try: self.parse(args) except UsageError: - # Handle `--version --version` and `--help` here in a minimal fashion. + # Handle --version and --help here in a minimal fashion. # This gets done via helpconfig normally, but its # pytest_cmdline_main is not called in case of errors. if getattr(self.option, "version", False) or "--version" in args: - from _pytest.helpconfig import show_version_verbose + from _pytest.helpconfig import showversion - # Note that `--version` (single argument) is handled early by `Config.main()`, so the only - # way we are reaching this point is via `--version --version`. - show_version_verbose(self) + showversion(self) elif ( getattr(self.option, "help", False) or "--help" in args or "-h" in args ): - self._parser.optparser.print_help() + self._parser._getparser().print_help() sys.stdout.write( "\nNOTE: displaying only minimal help due to UsageError.\n\n" ) @@ -1178,10 +1096,10 @@ class Config: def notify_exception( self, excinfo: ExceptionInfo[BaseException], - option: argparse.Namespace | None = None, + option: Optional[argparse.Namespace] = None, ) -> None: if option and getattr(option, "fulltrace", False): - style: TracebackStyle = "long" + style: _TracebackStyle = "long" else: style = "native" excrepr = excinfo.getrepr( @@ -1190,22 +1108,18 @@ class Config: res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) if not any(res): for line in str(excrepr).split("\n"): - sys.stderr.write(f"INTERNALERROR> {line}\n") + sys.stderr.write("INTERNALERROR> %s\n" % line) sys.stderr.flush() def cwd_relative_nodeid(self, nodeid: str) -> str: # nodeid's are relative to the rootpath, compute relative to cwd. if self.invocation_params.dir != self.rootpath: - base_path_part, *nodeid_part = nodeid.split("::") - # Only process path part - fullpath = self.rootpath / base_path_part - relative_path = bestrelpath(self.invocation_params.dir, fullpath) - - nodeid = "::".join([relative_path, *nodeid_part]) + fullpath = self.rootpath / nodeid + nodeid = bestrelpath(self.invocation_params.dir, fullpath) return nodeid @classmethod - def fromdictargs(cls, option_dict: Mapping[str, Any], args: list[str]) -> Config: + def fromdictargs(cls, option_dict, args) -> "Config": """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) @@ -1214,7 +1128,7 @@ class Config: config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt: Argument) -> None: + def _processopt(self, opt: "Argument") -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest @@ -1223,12 +1137,12 @@ class Config: setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: Config) -> None: + def pytest_load_initial_conftests(self, early_config: "Config") -> None: # We haven't fully parsed the command line arguments yet, so # early_config.args it not set yet. But we need it for # discovering the initial conftests. So "pre-run" the logic here. # It will be done for real in `parse()`. - args, _args_source = early_config._decide_args( + args, args_source = early_config._decide_args( args=early_config.known_args_namespace.file_or_dir, pyargs=early_config.known_args_namespace.pyargs, testpaths=early_config.getini("testpaths"), @@ -1242,25 +1156,43 @@ class Config: noconftest=early_config.known_args_namespace.noconftest, rootpath=early_config.rootpath, confcutdir=early_config.known_args_namespace.confcutdir, - invocation_dir=early_config.invocation_params.dir, importmode=early_config.known_args_namespace.importmode, - consider_namespace_packages=early_config.getini( - "consider_namespace_packages" - ), ) - def _consider_importhook(self) -> None: + def _initini(self, args: Sequence[str]) -> None: + ns, unknown_args = self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + rootpath, inipath, inicfg = determine_setup( + ns.inifilename, + ns.file_or_dir + unknown_args, + rootdir_cmd_arg=ns.rootdir or None, + config=self, + ) + self._rootpath = rootpath + self._inipath = inipath + self.inicfg = inicfg + self._parser.extra_info["rootdir"] = str(self.rootpath) + self._parser.extra_info["inifile"] = str(self.inipath) + self._parser.addini("addopts", "Extra command line options", "args") + self._parser.addini("minversion", "Minimally required pytest version") + self._parser.addini( + "required_plugins", + "Plugins that must be present for pytest to run", + type="args", + default=[], + ) + self._override_ini = ns.override_ini or () + + def _consider_importhook(self, args: Sequence[str]) -> None: """Install the PEP 302 import hook if using assertion rewriting. Needs to parse the --assert= option from the commandline and find all the installed plugins to mark them for rewriting by the importhook. """ - mode = getattr(self.known_args_namespace, "assertmode", "plain") - - disable_autoload = getattr( - self.known_args_namespace, "disable_plugin_autoload", False - ) or bool(os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")) + ns, unknown_args = self._parser.parse_known_and_unknown_args(args) + mode = getattr(ns, "assertmode", "plain") if mode == "rewrite": import _pytest.assertion @@ -1269,25 +1201,22 @@ class Config: except SystemError: mode = "plain" else: - self._mark_plugins_for_rewrite(hook, disable_autoload) + self._mark_plugins_for_rewrite(hook) self._warn_about_missing_assertion(mode) - def _mark_plugins_for_rewrite( - self, hook: AssertionRewritingHook, disable_autoload: bool - ) -> None: + def _mark_plugins_for_rewrite(self, hook) -> None: """Given an importhook, mark for rewrite any top-level modules or packages in the distribution package for all pytest plugins.""" self.pluginmanager.rewrite_hook = hook - if disable_autoload: - # We don't autoload from distribution package entry points, - # no need to continue. + if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): + # We don't autoload from setuptools entry points, no need to continue. return package_files = ( str(file) - for dist in importlib.metadata.distributions() + for dist in importlib_metadata.distributions() if any(ep.group == "pytest11" for ep in dist.entry_points) for file in dist.files or [] ) @@ -1295,45 +1224,31 @@ class Config: for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _configure_python_path(self) -> None: - # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` - for path in reversed(self.getini("pythonpath")): - sys.path.insert(0, str(path)) - self.add_cleanup(self._unconfigure_python_path) - - def _unconfigure_python_path(self) -> None: - for path in self.getini("pythonpath"): - path_str = str(path) - if path_str in sys.path: - sys.path.remove(path_str) - - def _validate_args(self, args: list[str], via: str) -> list[str]: + def _validate_args(self, args: List[str], via: str) -> List[str]: """Validate known args.""" - self._parser.extra_info["config source"] = via + self._parser._config_source_hint = via # type: ignore try: self._parser.parse_known_and_unknown_args( args, namespace=copy.copy(self.option) ) finally: - self._parser.extra_info.pop("config source", None) + del self._parser._config_source_hint # type: ignore return args def _decide_args( self, *, - args: list[str], - pyargs: bool, - testpaths: list[str], - invocation_dir: pathlib.Path, - rootpath: pathlib.Path, + args: List[str], + pyargs: List[str], + testpaths: List[str], + invocation_dir: Path, + rootpath: Path, warn: bool, - ) -> tuple[list[str], ArgsSource]: + ) -> Tuple[List[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. :param warn: Whether can issue warnings. - - :returns: The args and the args source. Guaranteed to be non-empty. """ if args: source = Config.ArgsSource.ARGS @@ -1360,36 +1275,97 @@ class Config: else: result = [] if not result: - source = Config.ArgsSource.INVOCATION_DIR + source = Config.ArgsSource.INCOVATION_DIR result = [str(invocation_dir)] return result, source - @hookimpl(wrapper=True) - def pytest_collection(self) -> Generator[None, object, object]: - # Validate invalid configuration keys after collection is done so we - # take in account options added by late-loading conftest files. + def _preparse(self, args: List[str], addopts: bool = True) -> None: + if addopts: + env_addopts = os.environ.get("PYTEST_ADDOPTS", "") + if len(env_addopts): + args[:] = ( + self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") + + args + ) + self._initini(args) + if addopts: + args[:] = ( + self._validate_args(self.getini("addopts"), "via addopts config") + args + ) + + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.option) + ) + self._checkversion() + self._consider_importhook(args) + self.pluginmanager.consider_preparse(args, exclude_only=False) + if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): + # Don't autoload from setuptools entry point. Only explicitly specified + # plugins are going to be loaded. + self.pluginmanager.load_setuptools_entrypoints("pytest11") + self.pluginmanager.consider_env() + + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.known_args_namespace) + ) + + self._validate_plugins() + self._warn_about_skipped_plugins() + + if self.known_args_namespace.strict: + self.issue_config_time_warning( + _pytest.deprecated.STRICT_OPTION, stacklevel=2 + ) + + if self.known_args_namespace.confcutdir is None: + if self.inipath is not None: + confcutdir = str(self.inipath.parent) + else: + confcutdir = str(self.rootpath) + self.known_args_namespace.confcutdir = confcutdir try: - return (yield) - finally: - self._validate_config_options() + self.hook.pytest_load_initial_conftests( + early_config=self, args=args, parser=self._parser + ) + except ConftestImportFailure as e: + if self.known_args_namespace.help or self.known_args_namespace.version: + # we don't want to prevent --help/--version to work + # so just let is pass and print a warning at the end + self.issue_config_time_warning( + PytestConfigWarning(f"could not load initial conftests: {e.path}"), + stacklevel=2, + ) + else: + raise + + @hookimpl(hookwrapper=True) + def pytest_collection(self) -> Generator[None, None, None]: + # Validate invalid ini keys after collection is done so we take in account + # options added by late-loading conftest files. + yield + self._validate_config_options() def _checkversion(self) -> None: import pytest - minver_ini_value = self.inicfg.get("minversion", None) - minver = minver_ini_value.value if minver_ini_value is not None else None + minver = self.inicfg.get("minversion", None) if minver: # Imported lazily to improve start-up time. from packaging.version import Version if not isinstance(minver, str): raise pytest.UsageError( - f"{self.inipath}: 'minversion' must be a single value" + "%s: 'minversion' must be a single value" % self.inipath ) if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( - f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'" + "%s: 'minversion' requires pytest-%s, actual pytest-%s'" + % ( + self.inipath, + minver, + pytest.__version__, + ) ) def _validate_config_options(self) -> None: @@ -1402,9 +1378,8 @@ class Config: return # Imported lazily to improve start-up time. - from packaging.requirements import InvalidRequirement - from packaging.requirements import Requirement from packaging.version import Version + from packaging.requirements import InvalidRequirement, Requirement plugin_info = self.pluginmanager.list_plugin_distinfo() plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} @@ -1430,131 +1405,47 @@ class Config: ) def _warn_or_fail_if_strict(self, message: str) -> None: - strict_config = self.getini("strict_config") - if strict_config is None: - strict_config = self.getini("strict") - if strict_config: + if self.known_args_namespace.strict_config: raise UsageError(message) self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) - def _get_unknown_ini_keys(self) -> set[str]: - known_keys = self._parser._inidict.keys() | self._parser._ini_aliases.keys() - return self.inicfg.keys() - known_keys + def _get_unknown_ini_keys(self) -> List[str]: + parser_inicfg = self._parser._inidict + return [name for name in self.inicfg if name not in parser_inicfg] - def parse(self, args: list[str], addopts: bool = True) -> None: + def parse(self, args: List[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. - assert self.args == [], ( - "can only parse cmdline args at most once per Config object" - ) - + assert ( + self.args == [] + ), "can only parse cmdline args at most once per Config object" self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) ) - - if addopts: - env_addopts = os.environ.get("PYTEST_ADDOPTS", "") - if len(env_addopts): - args[:] = ( - self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") - + args - ) - - ns = self._parser.parse_known_args(args, namespace=copy.copy(self.option)) - rootpath, inipath, inicfg, ignored_config_files = determine_setup( - inifile=ns.inifilename, - override_ini=ns.override_ini, - args=ns.file_or_dir, - rootdir_cmd_arg=ns.rootdir or None, - invocation_dir=self.invocation_params.dir, - ) - self._rootpath = rootpath - self._inipath = inipath - self._ignored_config_files = ignored_config_files - self.inicfg = inicfg - self._parser.extra_info["rootdir"] = str(self.rootpath) - self._parser.extra_info["inifile"] = str(self.inipath) - - self._parser.addini("addopts", "Extra command line options", "args") - self._parser.addini("minversion", "Minimally required pytest version") - self._parser.addini( - "pythonpath", type="paths", help="Add paths to sys.path", default=[] - ) - self._parser.addini( - "required_plugins", - "Plugins that must be present for pytest to run", - type="args", - default=[], - ) - - if addopts: - args[:] = ( - self._validate_args(self.getini("addopts"), "via addopts config") + args - ) - - self.known_args_namespace = self._parser.parse_known_args( - args, namespace=copy.copy(self.option) - ) - self._checkversion() - self._consider_importhook() - self._configure_python_path() - self.pluginmanager.consider_preparse(args, exclude_only=False) - if ( - not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD") - and not self.known_args_namespace.disable_plugin_autoload - ): - # Autoloading from distribution package entry point has - # not been disabled. - self.pluginmanager.load_setuptools_entrypoints("pytest11") - # Otherwise only plugins explicitly specified in PYTEST_PLUGINS - # are going to be loaded. - self.pluginmanager.consider_env() - - self._parser.parse_known_args(args, namespace=self.known_args_namespace) - - self._validate_plugins() - self._warn_about_skipped_plugins() - - if self.known_args_namespace.confcutdir is None: - if self.inipath is not None: - confcutdir = str(self.inipath.parent) - else: - confcutdir = str(self.rootpath) - self.known_args_namespace.confcutdir = confcutdir + self._preparse(args, addopts=addopts) + # XXX deprecated hook: + self.hook.pytest_cmdline_preparse(config=self, args=args) + self._parser.after_preparse = True # type: ignore try: - self.hook.pytest_load_initial_conftests( - early_config=self, args=args, parser=self._parser + args = self._parser.parse_setoption( + args, self.option, namespace=self.option + ) + self.args, self.args_source = self._decide_args( + args=args, + pyargs=self.known_args_namespace.pyargs, + testpaths=self.getini("testpaths"), + invocation_dir=self.invocation_params.dir, + rootpath=self.rootpath, + warn=True, ) - except ConftestImportFailure as e: - if self.known_args_namespace.help or self.known_args_namespace.version: - # we don't want to prevent --help/--version to work - # so just let it pass and print a warning at the end - self.issue_config_time_warning( - PytestConfigWarning(f"could not load initial conftests: {e.path}"), - stacklevel=2, - ) - else: - raise - - try: - self._parser.parse(args, namespace=self.option) except PrintHelp: - return - - self.args, self.args_source = self._decide_args( - args=getattr(self.option, FILE_OR_DIR), - pyargs=self.option.pyargs, - testpaths=self.getini("testpaths"), - invocation_dir=self.invocation_params.dir, - rootpath=self.rootpath, - warn=True, - ) + pass def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: """Issue and handle a warning during the "configure" stage. During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` - function because it is not possible to have hook wrappers around ``pytest_configure``. + function because it is not possible to have hookwrappers around ``pytest_configure``. This function is mainly intended for plugins that need to issue warnings during ``pytest_configure`` (or similar stages). @@ -1586,129 +1477,68 @@ class Config: ) def addinivalue_line(self, name: str, line: str) -> None: - """Add a line to a configuration option. The option must have been + """Add a line to an ini-file option. The option must have been declared but might not yet be set in which case the line becomes the first line in its value.""" x = self.getini(name) assert isinstance(x, list) x.append(line) # modifies the cached list inline - def getini(self, name: str) -> Any: - """Return configuration value the an :ref:`configuration file `. - - If a configuration value is not defined in a - :ref:`configuration file `, then the ``default`` value - provided while registering the configuration through - :func:`parser.addini ` will be returned. - Please note that you can even provide ``None`` as a valid - default value. - - If ``default`` is not provided while registering using - :func:`parser.addini `, then a default value - based on the ``type`` parameter passed to - :func:`parser.addini ` will be returned. - The default values based on ``type`` are: - ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]`` - ``bool`` : ``False`` - ``string`` : empty string ``""`` - ``int`` : ``0`` - ``float`` : ``0.0`` - - If neither the ``default`` nor the ``type`` parameter is passed - while registering the configuration through - :func:`parser.addini `, then the configuration - is treated as a string and a default empty string '' is returned. + def getini(self, name: str): + """Return configuration value from an :ref:`ini file `. If the specified name hasn't been registered through a prior :func:`parser.addini ` call (usually from a plugin), a ValueError is raised. """ - canonical_name = self._parser._ini_aliases.get(name, name) try: - return self._inicache[canonical_name] + return self._inicache[name] except KeyError: - pass - self._inicache[canonical_name] = val = self._getini(canonical_name) - return val + self._inicache[name] = val = self._getini(name) + return val # Meant for easy monkeypatching by legacypath plugin. # Can be inlined back (with no cover removed) once legacypath is gone. - def _getini_unknown_type(self, name: str, type: str, value: object): - msg = ( - f"Option {name} has unknown configuration type {type} with value {value!r}" - ) - raise ValueError(msg) # pragma: no cover + def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): + msg = f"unknown configuration type: {type}" + raise ValueError(msg, value) # pragma: no cover def _getini(self, name: str): - # If this is an alias, resolve to canonical name. - canonical_name = self._parser._ini_aliases.get(name, name) - try: - _description, type, default = self._parser._inidict[canonical_name] + description, type, default = self._parser._inidict[name] except KeyError as e: raise ValueError(f"unknown configuration value: {name!r}") from e - - # Collect all possible values (canonical name + aliases) from inicfg. - # Each candidate is (ConfigValue, is_canonical). - candidates = [] - if canonical_name in self.inicfg: - candidates.append((self.inicfg[canonical_name], True)) - for alias, target in self._parser._ini_aliases.items(): - if target == canonical_name and alias in self.inicfg: - candidates.append((self.inicfg[alias], False)) - - if not candidates: - return default - - # Pick the best candidate based on precedence: - # 1. CLI override takes precedence over file, then - # 2. Canonical name takes precedence over alias. - selected = max(candidates, key=lambda x: (x[0].origin == "override", x[1]))[0] - value = selected.value - mode = selected.mode - - if mode == "ini": - # In ini mode, values are always str | list[str]. - assert isinstance(value, (str, list)) - return self._getini_ini(name, canonical_name, type, value, default) - elif mode == "toml": - return self._getini_toml(name, canonical_name, type, value, default) + override_value = self._get_override_ini_value(name) + if override_value is None: + try: + value = self.inicfg[name] + except KeyError: + if default is not None: + return default + if type is None: + return "" + return [] else: - assert_never(mode) - - def _getini_ini( - self, - name: str, - canonical_name: str, - type: str, - value: str | list[str], - default: Any, - ): - """Handle config values read in INI mode. - - In INI mode, values are stored as str or list[str] only, and coerced - from string based on the registered type. - """ - # Note: some coercions are only required if we are reading from .ini - # files, because the file format doesn't contain type information, but - # when reading from toml (in ini mode) we will get either str or list of - # str values (see load_config_dict_from_file). For example: + value = override_value + # Coerce the values based on types. + # + # Note: some coercions are only required if we are reading from .ini files, because + # the file format doesn't contain type information, but when reading from toml we will + # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). + # For example: # # ini: # a_line_list = "tests acceptance" + # in this case, we need to split the string to obtain a list of strings. # - # in this case, we need to split the string to obtain a list of strings. - # - # toml (ini mode): + # toml: # a_line_list = ["tests", "acceptance"] + # in this case, we already have a list ready to use. # - # in this case, we already have a list ready to use. if type == "paths": - dp = ( - self.inipath.parent - if self.inipath is not None - else self.invocation_params.dir - ) + # TODO: This assert is probably not valid in all cases. + assert self.inipath is not None + dp = self.inipath.parent input_values = shlex.split(value) if isinstance(value, str) else value return [dp / x for x in input_values] elif type == "args": @@ -1722,133 +1552,59 @@ class Config: return _strtobool(str(value).strip()) elif type == "string": return value - elif type == "int": - if not isinstance(value, str): - raise TypeError( - f"Expected an int string for option {name} of type integer, but got: {value!r}" - ) from None - return int(value) - elif type == "float": - if not isinstance(value, str): - raise TypeError( - f"Expected a float string for option {name} of type float, but got: {value!r}" - ) from None - return float(value) - else: - return self._getini_unknown_type(name, type, value) - - def _getini_toml( - self, - name: str, - canonical_name: str, - type: str, - value: object, - default: Any, - ): - """Handle TOML config values with strict type validation and no coercion. - - In TOML mode, values already have native types from TOML parsing. - We validate types match expectations exactly, including list items. - """ - value_type = builtins.type(value).__name__ - if type == "paths": - # Expect a list of strings. - if not isinstance(value, list): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list for type 'paths', " - f"got {value_type}: {value!r}" - ) - for i, item in enumerate(value): - if not isinstance(item, str): - item_type = builtins.type(item).__name__ - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list of strings, " - f"but item at index {i} is {item_type}: {item!r}" - ) - dp = ( - self.inipath.parent - if self.inipath is not None - else self.invocation_params.dir - ) - return [dp / x for x in value] - elif type in {"args", "linelist"}: - # Expect a list of strings. - if not isinstance(value, list): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list for type '{type}', " - f"got {value_type}: {value!r}" - ) - for i, item in enumerate(value): - if not isinstance(item, str): - item_type = builtins.type(item).__name__ - raise TypeError( - f"{self.inipath}: config option '{name}' expects a list of strings, " - f"but item at index {i} is {item_type}: {item!r}" - ) - return list(value) - elif type == "bool": - # Expect a boolean. - if not isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a bool, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "int": - # Expect an integer (but not bool, which is a subclass of int). - if not isinstance(value, int) or isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects an int, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "float": - # Expect a float or integer only. - if not isinstance(value, (float, int)) or isinstance(value, bool): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a float, " - f"got {value_type}: {value!r}" - ) - return value - elif type == "string": - # Expect a string. - if not isinstance(value, str): - raise TypeError( - f"{self.inipath}: config option '{name}' expects a string, " - f"got {value_type}: {value!r}" - ) + elif type is None: return value else: return self._getini_unknown_type(name, type, value) def _getconftest_pathlist( - self, name: str, path: pathlib.Path - ) -> list[pathlib.Path] | None: + self, name: str, path: Path, rootpath: Path + ) -> Optional[List[Path]]: try: - mod, relroots = self.pluginmanager._rget_with_confmod(name, path) + mod, relroots = self.pluginmanager._rget_with_confmod( + name, path, self.getoption("importmode"), rootpath + ) except KeyError: return None assert mod.__file__ is not None - modpath = pathlib.Path(mod.__file__).parent - values: list[pathlib.Path] = [] + modpath = Path(mod.__file__).parent + values: List[Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): - relroot = pathlib.Path(relroot) + relroot = Path(relroot) else: relroot = relroot.replace("/", os.sep) relroot = absolutepath(modpath / relroot) values.append(relroot) return values - def getoption(self, name: str, default: Any = notset, skip: bool = False): + def _get_override_ini_value(self, name: str) -> Optional[str]: + value = None + # override_ini is a list of "ini=value" options. + # Always use the last item if multiple values are set for same ini-name, + # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. + for ini_config in self._override_ini: + try: + key, user_ini_value = ini_config.split("=", 1) + except ValueError as e: + raise UsageError( + "-o/--override-ini expects option=value style (got: {!r}).".format( + ini_config + ) + ) from e + else: + if key == name: + value = user_ini_value + return value + + def getoption(self, name: str, default=notset, skip: bool = False): """Return command line option value. - :param name: Name of the option. You may also specify + :param name: Name of the option. You may also specify the literal ``--OPT`` option instead of the "dest" option name. - :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`. - Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``. - :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value. - Note that even if ``True``, if a default was specified it will be returned instead of a skip. + :param default: Default value if no option of that name exists. + :param skip: If True, raise pytest.skip if option does not exists + or has a None value. """ name = self._opt2dest.get(name, name) try: @@ -1873,91 +1629,6 @@ class Config: """Deprecated, use getoption(skip=True) instead.""" return self.getoption(name, skip=True) - #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`). - VERBOSITY_ASSERTIONS: Final = "assertions" - #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`). - VERBOSITY_TEST_CASES: Final = "test_cases" - #: Verbosity type for failed subtests (see :confval:`verbosity_subtests`). - VERBOSITY_SUBTESTS: Final = "subtests" - - _VERBOSITY_INI_DEFAULT: Final = "auto" - - def get_verbosity(self, verbosity_type: str | None = None) -> int: - r"""Retrieve the verbosity level for a fine-grained verbosity type. - - :param verbosity_type: Verbosity type to get level for. If a level is - configured for the given type, that value will be returned. If the - given type is not a known verbosity type, the global verbosity - level will be returned. If the given type is None (default), the - global verbosity level will be returned. - - To configure a level for a fine-grained verbosity type, the - configuration file should have a setting for the configuration name - and a numeric value for the verbosity level. A special value of "auto" - can be used to explicitly use the global verbosity level. - - Example: - - .. tab:: toml - - .. code-block:: toml - - [tool.pytest] - verbosity_assertions = 2 - - .. tab:: ini - - .. code-block:: ini - - [pytest] - verbosity_assertions = 2 - - .. code-block:: console - - pytest -v - - .. code-block:: python - - print(config.get_verbosity()) # 1 - print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS)) # 2 - """ - global_level = self.getoption("verbose", default=0) - assert isinstance(global_level, int) - if verbosity_type is None: - return global_level - - ini_name = Config._verbosity_ini_name(verbosity_type) - if ini_name not in self._parser._inidict: - return global_level - - level = self.getini(ini_name) - if level == Config._VERBOSITY_INI_DEFAULT: - return global_level - - return int(level) - - @staticmethod - def _verbosity_ini_name(verbosity_type: str) -> str: - return f"verbosity_{verbosity_type}" - - @staticmethod - def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: - """Add a output verbosity configuration option for the given output type. - - :param parser: Parser for command line arguments and config-file values. - :param verbosity_type: Fine-grained verbosity category. - :param help: Description of the output this type controls. - - The value should be retrieved via a call to - :py:func:`config.get_verbosity(type) `. - """ - parser.addini( - Config._verbosity_ini_name(verbosity_type), - help=help, - type="string", - default=Config._VERBOSITY_INI_DEFAULT, - ) - def _warn_about_missing_assertion(self, mode: str) -> None: if not _assertion_supported(): if mode == "plain": @@ -1997,7 +1668,7 @@ def _assertion_supported() -> bool: def create_terminal_writer( - config: Config, file: TextIO | None = None + config: Config, file: Optional[TextIO] = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. @@ -2041,7 +1712,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: +) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -2083,17 +1754,15 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) try: - action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] + action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: - raise UsageError(error_template.format(error=str(e))) from None + raise UsageError(error_template.format(error=str(e))) try: - category: type[Warning] = _resolve_warning_category(category_) - except ImportError: - raise + category: Type[Warning] = _resolve_warning_category(category_) except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") - raise UsageError(error_template.format(error=exception_text)) from None + raise UsageError(error_template.format(error=exception_text)) if message and escape: message = re.escape(message) if module and escape: @@ -2106,20 +1775,13 @@ def parse_warning_filter( except ValueError as e: raise UsageError( error_template.format(error=f"invalid lineno {lineno_!r}: {e}") - ) from None + ) else: lineno = 0 - try: - re.compile(message) - re.compile(module) - except re.error as e: - raise UsageError( - error_template.format(error=f"Invalid regex {e.pattern!r}: {e}") - ) from None return action, message, category, module, lineno -def _resolve_warning_category(category: str) -> type[Warning]: +def _resolve_warning_category(category: str) -> Type[Warning]: """ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) propagate so we can get access to their tracebacks (#9218). @@ -2138,7 +1800,7 @@ def _resolve_warning_category(category: str) -> type[Warning]: cat = getattr(m, klass) if not issubclass(cat, Warning): raise UsageError(f"{cat} is not a Warning subclass") - return cast(type[Warning], cat) + return cast(Type[Warning], cat) def apply_warning_filters( @@ -2148,19 +1810,7 @@ def apply_warning_filters( # Filters should have this precedence: cmdline options, config. # Filters should be applied in the inverse order of precedence. for arg in config_filters: - try: - warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) - except ImportError as e: - warnings.warn( - f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning - ) - continue + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) for arg in cmdline_filters: - try: - warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) - except ImportError as e: - warnings.warn( - f"Failed to import filter module '{e.name}': {arg}", PytestConfigWarning - ) - continue + warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..862e8aa7 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc new file mode 100644 index 00000000..dbd31814 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/argparsing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..fcd42784 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 00000000..4c352454 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/findpaths.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/findpaths.cpython-312.pyc new file mode 100644 index 00000000..9ada0632 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/config/__pycache__/findpaths.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/argparsing.py b/Backend/venv/lib/python3.12/site-packages/_pytest/config/argparsing.py index 8216ad8b..d3f01916 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/config/argparsing.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/config/argparsing.py @@ -1,83 +1,69 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - import argparse -from collections.abc import Callable -from collections.abc import Mapping -from collections.abc import Sequence import os import sys +import warnings +from gettext import gettext from typing import Any -from typing import final -from typing import Literal +from typing import Callable +from typing import cast +from typing import Dict +from typing import List +from typing import Mapping from typing import NoReturn +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union -from .exceptions import UsageError import _pytest._io +from _pytest.compat import final +from _pytest.config.exceptions import UsageError +from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT +from _pytest.deprecated import ARGUMENT_TYPE_STR +from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE from _pytest.deprecated import check_ispytest +if TYPE_CHECKING: + from typing_extensions import Literal FILE_OR_DIR = "file_or_dir" -class NotSet: - def __repr__(self) -> str: - return "" - - -NOT_SET = NotSet() - - @final class Parser: - """Parser for command line arguments and config-file values. + """Parser for command line arguments and ini-file values. :ivar extra_info: Dict of generic param -> value to display in case there's an error processing the command line arguments. """ + prog: Optional[str] = None + def __init__( self, - usage: str | None = None, - processopt: Callable[[Argument], None] | None = None, + usage: Optional[str] = None, + processopt: Optional[Callable[["Argument"], None]] = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - - from _pytest._argcomplete import filescompleter - + self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) + self._groups: List[OptionGroup] = [] self._processopt = processopt - self.extra_info: dict[str, Any] = {} - self.optparser = PytestArgumentParser(self, usage, self.extra_info) - anonymous_arggroup = self.optparser.add_argument_group("Custom options") - self._anonymous = OptionGroup( - anonymous_arggroup, "_anonymous", self, _ispytest=True - ) - self._groups = [self._anonymous] - file_or_dir_arg = self.optparser.add_argument(FILE_OR_DIR, nargs="*") - file_or_dir_arg.completer = filescompleter # type: ignore + self._usage = usage + self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {} + self._ininames: List[str] = [] + self.extra_info: Dict[str, Any] = {} - self._inidict: dict[str, tuple[str, str, Any]] = {} - # Maps alias -> canonical name. - self._ini_aliases: dict[str, str] = {} - - @property - def prog(self) -> str: - return self.optparser.prog - - @prog.setter - def prog(self, value: str) -> None: - self.optparser.prog = value - - def processoption(self, option: Argument) -> None: + def processoption(self, option: "Argument") -> None: if self._processopt: if option.dest: self._processopt(option) def getgroup( - self, name: str, description: str = "", after: str | None = None - ) -> OptionGroup: + self, name: str, description: str = "", after: Optional[str] = None + ) -> "OptionGroup": """Get (or create) a named option Group. :param name: Name of the option group. @@ -93,17 +79,12 @@ class Parser: for group in self._groups: if group.name == name: return group - - arggroup = self.optparser.add_argument_group(description or name) - group = OptionGroup(arggroup, name, self, _ispytest=True) + group = OptionGroup(name, description, parser=self, _ispytest=True) i = 0 for i, grp in enumerate(self._groups): if grp.name == after: break self._groups.insert(i + 1, group) - # argparse doesn't provide a way to control `--help` order, so must - # access its internals ☹. - self.optparser._action_groups.insert(i + 1, self.optparser._action_groups.pop()) return group def addoption(self, *opts: str, **attrs: Any) -> None: @@ -112,7 +93,7 @@ class Parser: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :meth:`add_argument() + Same attributes as the argparse library's :py:func:`add_argument() ` function accepts. After command line parsing, options are available on the pytest config @@ -124,32 +105,50 @@ class Parser: def parse( self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, + args: Sequence[Union[str, "os.PathLike[str]"]], + namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: - """Parse the arguments. - - Unlike ``parse_known_args`` and ``parse_known_and_unknown_args``, - raises PrintHelp on `--help` and UsageError on unknown flags - - :meta private: - """ from _pytest._argcomplete import try_argcomplete + self.optparser = self._getparser() try_argcomplete(self.optparser) strargs = [os.fspath(x) for x in args] - if namespace is None: - namespace = argparse.Namespace() - try: - namespace._raise_print_help = True - return self.optparser.parse_intermixed_args(strargs, namespace=namespace) - finally: - del namespace._raise_print_help + return self.optparser.parse_args(strargs, namespace=namespace) + + def _getparser(self) -> "MyOptionParser": + from _pytest._argcomplete import filescompleter + + optparser = MyOptionParser(self, self.extra_info, prog=self.prog) + groups = self._groups + [self._anonymous] + for group in groups: + if group.options: + desc = group.description or group.name + arggroup = optparser.add_argument_group(desc) + for option in group.options: + n = option.names() + a = option.attrs() + arggroup.add_argument(*n, **a) + file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*") + # bash like autocompletion for dirs (appending '/') + # Type ignored because typeshed doesn't know about argcomplete. + file_or_dir_arg.completer = filescompleter # type: ignore + return optparser + + def parse_setoption( + self, + args: Sequence[Union[str, "os.PathLike[str]"]], + option: argparse.Namespace, + namespace: Optional[argparse.Namespace] = None, + ) -> List[str]: + parsedoption = self.parse(args, namespace=namespace) + for name, value in parsedoption.__dict__.items(): + setattr(option, name, value) + return cast(List[str], getattr(parsedoption, FILE_OR_DIR)) def parse_known_args( self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, + args: Sequence[Union[str, "os.PathLike[str]"]], + namespace: Optional[argparse.Namespace] = None, ) -> argparse.Namespace: """Parse the known arguments at this point. @@ -159,47 +158,35 @@ class Parser: def parse_known_and_unknown_args( self, - args: Sequence[str | os.PathLike[str]], - namespace: argparse.Namespace | None = None, - ) -> tuple[argparse.Namespace, list[str]]: + args: Sequence[Union[str, "os.PathLike[str]"]], + namespace: Optional[argparse.Namespace] = None, + ) -> Tuple[argparse.Namespace, List[str]]: """Parse the known arguments at this point, and also return the - remaining unknown flag arguments. + remaining unknown arguments. :returns: A tuple containing an argparse namespace object for the known - arguments, and a list of unknown flag arguments. + arguments, and a list of the unknown arguments. """ + optparser = self._getparser() strargs = [os.fspath(x) for x in args] - if sys.version_info < (3, 12, 8) or (3, 13) <= sys.version_info < (3, 13, 1): - # Older argparse have a bugged parse_known_intermixed_args. - namespace, unknown = self.optparser.parse_known_args(strargs, namespace) - assert namespace is not None - file_or_dir = getattr(namespace, FILE_OR_DIR) - unknown_flags: list[str] = [] - for arg in unknown: - (unknown_flags if arg.startswith("-") else file_or_dir).append(arg) - return namespace, unknown_flags - else: - return self.optparser.parse_known_intermixed_args(strargs, namespace) + return optparser.parse_known_args(strargs, namespace=namespace) def addini( self, name: str, help: str, - type: Literal[ - "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" - ] - | None = None, - default: Any = NOT_SET, - *, - aliases: Sequence[str] = (), + type: Optional[ + "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']" + ] = None, + default: Any = None, ) -> None: - """Register a configuration file option. + """Register an ini-file option. :param name: - Name of the configuration. + Name of the ini-variable. :param type: - Type of the configuration. Can be: + Type of the variable. Can be: * ``string``: a string * ``bool``: a boolean @@ -207,90 +194,27 @@ class Parser: * ``linelist``: a list of strings, separated by line breaks * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell * ``pathlist``: a list of ``py.path``, separated as in a shell - * ``int``: an integer - * ``float``: a floating-point number - - .. versionadded:: 8.4 - - The ``float`` and ``int`` types. - - For ``paths`` and ``pathlist`` types, they are considered relative to the config-file. - In case the execution is happening without a config-file defined, - they will be considered relative to the current working directory (for example with ``--override-ini``). .. versionadded:: 7.0 The ``paths`` variable type. - .. versionadded:: 8.1 - Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of a config-file. - Defaults to ``string`` if ``None`` or not passed. :param default: - Default value if no config-file option exists but is queried. - :param aliases: - Additional names by which this option can be referenced. - Aliases resolve to the canonical name. + Default value if no ini-file option exists but is queried. - .. versionadded:: 9.0 - The ``aliases`` parameter. - - The value of configuration keys can be retrieved via a call to + The value of ini-variables can be retrieved via a call to :py:func:`config.getini(name) `. """ - assert type in ( - None, - "string", - "paths", - "pathlist", - "args", - "linelist", - "bool", - "int", - "float", - ) - if type is None: - type = "string" - if default is NOT_SET: - default = get_ini_default_for_type(type) - + assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") self._inidict[name] = (help, type, default) - - for alias in aliases: - if alias in self._inidict: - raise ValueError( - f"alias {alias!r} conflicts with existing configuration option" - ) - if (already := self._ini_aliases.get(alias)) is not None: - raise ValueError(f"{alias!r} is already an alias of {already!r}") - self._ini_aliases[alias] = name - - -def get_ini_default_for_type( - type: Literal[ - "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float" - ], -) -> Any: - """ - Used by addini to get the default value for a given config option type, when - default is not supplied. - """ - if type in ("paths", "pathlist", "args", "linelist"): - return [] - elif type == "bool": - return False - elif type == "int": - return 0 - elif type == "float": - return 0.0 - else: - return "" + self._ininames.append(name) class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" - def __init__(self, msg: str, option: Argument | str) -> None: + def __init__(self, msg: str, option: Union["Argument", str]) -> None: self.msg = msg self.option_id = str(option) @@ -310,22 +234,46 @@ class Argument: https://docs.python.org/3/library/optparse.html#optparse-standard-option-types """ + _typ_map = {"int": int, "string": str, "float": float, "complex": complex} + def __init__(self, *names: str, **attrs: Any) -> None: """Store params in private vars for use in add_argument.""" self._attrs = attrs - self._short_opts: list[str] = [] - self._long_opts: list[str] = [] + self._short_opts: List[str] = [] + self._long_opts: List[str] = [] + if "%default" in (attrs.get("help") or ""): + warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3) try: - self.type = attrs["type"] + typ = attrs["type"] except KeyError: pass + else: + # This might raise a keyerror as well, don't want to catch that. + if isinstance(typ, str): + if typ == "choice": + warnings.warn( + ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names), + stacklevel=4, + ) + # argparse expects a type here take it from + # the type of the first element + attrs["type"] = type(attrs["choices"][0]) + else: + warnings.warn( + ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4 + ) + attrs["type"] = Argument._typ_map[typ] + # Used in test_parseopt -> test_parse_defaultgetter. + self.type = attrs["type"] + else: + self.type = typ try: # Attribute existence is tested in Config._processopt. self.default = attrs["default"] except KeyError: pass self._set_opt_strings(names) - dest: str | None = attrs.get("dest") + dest: Optional[str] = attrs.get("dest") if dest: self.dest = dest elif self._long_opts: @@ -337,16 +285,23 @@ class Argument: self.dest = "???" # Needed for the error repr. raise ArgumentError("need a long or short option", self) from e - def names(self) -> list[str]: + def names(self) -> List[str]: return self._short_opts + self._long_opts def attrs(self) -> Mapping[str, Any]: # Update any attributes set by processopt. - for attr in ("default", "dest", "help", self.dest): + attrs = "default dest help".split() + attrs.append(self.dest) + for attr in attrs: try: self._attrs[attr] = getattr(self, attr) except AttributeError: pass + if self._attrs.get("help"): + a = self._attrs["help"] + a = a.replace("%default", "%(default)s") + # a = a.replace('%prog', '%(prog)s') + self._attrs["help"] = a return self._attrs def _set_opt_strings(self, opts: Sequence[str]) -> None: @@ -357,29 +312,29 @@ class Argument: for opt in opts: if len(opt) < 2: raise ArgumentError( - f"invalid option string {opt!r}: " - "must be at least two characters long", + "invalid option string %r: " + "must be at least two characters long" % opt, self, ) elif len(opt) == 2: if not (opt[0] == "-" and opt[1] != "-"): raise ArgumentError( - f"invalid short option string {opt!r}: " - "must be of the form -x, (x any non-dash char)", + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, self, ) self._short_opts.append(opt) else: if not (opt[0:2] == "--" and opt[2] != "-"): raise ArgumentError( - f"invalid long option string {opt!r}: " - "must start with --, followed by non-dash", + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, self, ) self._long_opts.append(opt) def __repr__(self) -> str: - args: list[str] = [] + args: List[str] = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -397,15 +352,16 @@ class OptionGroup: def __init__( self, - arggroup: argparse._ArgumentGroup, name: str, - parser: Parser | None, + description: str = "", + parser: Optional[Parser] = None, + *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self._arggroup = arggroup self.name = name - self.options: list[Argument] = [] + self.description = description + self.options: List[Argument] = [] self.parser = parser def addoption(self, *opts: str, **attrs: Any) -> None: @@ -419,14 +375,14 @@ class OptionGroup: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :meth:`add_argument() + Same attributes as the argparse library's :py:func:`add_argument() ` function accepts. """ conflict = set(opts).intersection( name for opt in self.options for name in opt.names() ) if conflict: - raise ValueError(f"option names {conflict} already added") + raise ValueError("option names %s already added" % conflict) option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=False) @@ -434,47 +390,101 @@ class OptionGroup: option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: + def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: if not shortupper: for opt in option._short_opts: if opt[0] == "-" and opt[1].islower(): raise ValueError("lowercase shortoptions reserved") - if self.parser: self.parser.processoption(option) - - self._arggroup.add_argument(*option.names(), **option.attrs()) self.options.append(option) -class PytestArgumentParser(argparse.ArgumentParser): +class MyOptionParser(argparse.ArgumentParser): def __init__( self, parser: Parser, - usage: str | None, - extra_info: dict[str, str], + extra_info: Optional[Dict[str, Any]] = None, + prog: Optional[str] = None, ) -> None: self._parser = parser super().__init__( - usage=usage, + prog=prog, + usage=parser._usage, add_help=False, formatter_class=DropShorterLongHelpFormatter, allow_abbrev=False, - fromfile_prefix_chars="@", ) # extra_info is a dict of (param -> value) to display if there's # an usage error to provide more contextual information to the user. - self.extra_info = extra_info + self.extra_info = extra_info if extra_info else {} def error(self, message: str) -> NoReturn: """Transform argparse error message into UsageError.""" msg = f"{self.prog}: error: {message}" - if self.extra_info: - msg += "\n" + "\n".join( - f" {k}: {v}" for k, v in sorted(self.extra_info.items()) - ) + + if hasattr(self._parser, "_config_source_hint"): + # Type ignored because the attribute is set dynamically. + msg = f"{msg} ({self._parser._config_source_hint})" # type: ignore + raise UsageError(self.format_usage() + msg) + # Type ignored because typeshed has a very complex type in the superclass. + def parse_args( # type: ignore + self, + args: Optional[Sequence[str]] = None, + namespace: Optional[argparse.Namespace] = None, + ) -> argparse.Namespace: + """Allow splitting of positional arguments.""" + parsed, unrecognized = self.parse_known_args(args, namespace) + if unrecognized: + for arg in unrecognized: + if arg and arg[0] == "-": + lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))] + for k, v in sorted(self.extra_info.items()): + lines.append(f" {k}: {v}") + self.error("\n".join(lines)) + getattr(parsed, FILE_OR_DIR).extend(unrecognized) + return parsed + + if sys.version_info[:2] < (3, 9): # pragma: no cover + # Backport of https://github.com/python/cpython/pull/14316 so we can + # disable long --argument abbreviations without breaking short flags. + def _parse_optional( + self, arg_string: str + ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: + if not arg_string: + return None + if not arg_string[0] in self.prefix_chars: + return None + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + if len(arg_string) == 1: + return None + if "=" in arg_string: + option_string, explicit_arg = arg_string.split("=", 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + if self.allow_abbrev or not arg_string.startswith("--"): + option_tuples = self._get_option_tuples(arg_string) + if len(option_tuples) > 1: + msg = gettext( + "ambiguous option: %(option)s could match %(matches)s" + ) + options = ", ".join(option for _, option, _ in option_tuples) + self.error(msg % {"option": arg_string, "matches": options}) + elif len(option_tuples) == 1: + (option_tuple,) = option_tuples + return option_tuple + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + if " " in arg_string: + return None + return None, arg_string, None + class DropShorterLongHelpFormatter(argparse.HelpFormatter): """Shorten help for long options that differ only in extra hyphens. @@ -494,7 +504,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: str | None = getattr(action, "_formatted_action_invocation", None) + res: Optional[str] = getattr(action, "_formatted_action_invocation", None) if res: return res options = orgstr.split(", ") @@ -503,13 +513,13 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - short_long: dict[str, str] = {} + short_long: Dict[str, str] = {} for option in options: if len(option) == 2 or option[2] == " ": continue if not option.startswith("--"): raise ArgumentError( - f'long optional argument without "--": [{option}]', option + 'long optional argument without "--": [%s]' % (option), option ) xxoption = option[2:] shortened = xxoption.replace("-", "") @@ -539,40 +549,3 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): for line in text.splitlines(): lines.extend(textwrap.wrap(line.strip(), width)) return lines - - -class OverrideIniAction(argparse.Action): - """Custom argparse action that makes a CLI flag equivalent to overriding an - option, in addition to behaving like `store_true`. - - This can simplify things since code only needs to inspect the config option - and not consider the CLI flag. - """ - - def __init__( - self, - option_strings: Sequence[str], - dest: str, - nargs: int | str | None = None, - *args, - ini_option: str, - ini_value: str, - **kwargs, - ) -> None: - super().__init__(option_strings, dest, 0, *args, **kwargs) - self.ini_option = ini_option - self.ini_value = ini_value - - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - *args, - **kwargs, - ) -> None: - setattr(namespace, self.dest, True) - current_overrides = getattr(namespace, "override_ini", None) - if current_overrides is None: - current_overrides = [] - current_overrides.append(f"{self.ini_option}={self.ini_value}") - setattr(namespace, "override_ini", current_overrides) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/compat.py b/Backend/venv/lib/python3.12/site-packages/_pytest/config/compat.py index 21eab4c7..5bd922a4 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/config/compat.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/config/compat.py @@ -1,20 +1,15 @@ -from __future__ import annotations - -from collections.abc import Mapping import functools -from pathlib import Path -from typing import Any import warnings - -import pluggy +from pathlib import Path +from typing import Optional from ..compat import LEGACY_PATH from ..compat import legacy_path from ..deprecated import HOOK_LEGACY_PATH_ARG - +from _pytest.nodes import _check_path # hookname: (Path, LEGACY_PATH) -imply_paths_hooks: Mapping[str, tuple[str, str]] = { +imply_paths_hooks = { "pytest_ignore_collect": ("collection_path", "path"), "pytest_collect_file": ("file_path", "path"), "pytest_pycollect_makemodule": ("module_path", "path"), @@ -23,14 +18,6 @@ imply_paths_hooks: Mapping[str, tuple[str, str]] = { } -def _check_path(path: Path, fspath: LEGACY_PATH) -> None: - if Path(fspath) != path: - raise ValueError( - f"Path({fspath!r}) != {path!r}\n" - "if both path and fspath are given they need to be equal" - ) - - class PathAwareHookProxy: """ this helper wraps around hook callers @@ -40,24 +27,24 @@ class PathAwareHookProxy: this may have to be changed later depending on bugs """ - def __init__(self, hook_relay: pluggy.HookRelay) -> None: - self._hook_relay = hook_relay + def __init__(self, hook_caller): + self.__hook_caller = hook_caller - def __dir__(self) -> list[str]: - return dir(self._hook_relay) + def __dir__(self): + return dir(self.__hook_caller) - def __getattr__(self, key: str) -> pluggy.HookCaller: - hook: pluggy.HookCaller = getattr(self._hook_relay, key) + def __getattr__(self, key, _wraps=functools.wraps): + hook = getattr(self.__hook_caller, key) if key not in imply_paths_hooks: self.__dict__[key] = hook return hook else: path_var, fspath_var = imply_paths_hooks[key] - @functools.wraps(hook) - def fixed_hook(**kw: Any) -> Any: - path_value: Path | None = kw.pop(path_var, None) - fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None) + @_wraps(hook) + def fixed_hook(**kw): + path_value: Optional[Path] = kw.pop(path_var, None) + fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None) if fspath_value is not None: warnings.warn( HOOK_LEGACY_PATH_ARG.format( @@ -78,8 +65,6 @@ class PathAwareHookProxy: kw[fspath_var] = fspath_value return hook(**kw) - fixed_hook.name = hook.name # type: ignore[attr-defined] - fixed_hook.spec = hook.spec # type: ignore[attr-defined] fixed_hook.__name__ = key self.__dict__[key] = fixed_hook - return fixed_hook # type: ignore[return-value] + return fixed_hook diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/exceptions.py b/Backend/venv/lib/python3.12/site-packages/_pytest/config/exceptions.py index d84a9ea6..4f1320e7 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/config/exceptions.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/config/exceptions.py @@ -1,14 +1,10 @@ -from __future__ import annotations - -from typing import final +from _pytest.compat import final @final class UsageError(Exception): """Error in pytest usage or invocation.""" - __module__ = "pytest" - class PrintHelp(Exception): """Raised when pytest should print its help to skip the rest of the diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/config/findpaths.py b/Backend/venv/lib/python3.12/site-packages/_pytest/config/findpaths.py index 3c628a09..02674ffa 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/config/findpaths.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/config/findpaths.py @@ -1,14 +1,14 @@ -from __future__ import annotations - -from collections.abc import Iterable -from collections.abc import Sequence -from dataclasses import dataclass -from dataclasses import KW_ONLY import os -from pathlib import Path import sys -from typing import Literal -from typing import TypeAlias +from pathlib import Path +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union import iniconfig @@ -18,29 +18,8 @@ from _pytest.pathlib import absolutepath from _pytest.pathlib import commonpath from _pytest.pathlib import safe_exists - -@dataclass(frozen=True) -class ConfigValue: - """Represents a configuration value with its origin and parsing mode. - - This allows tracking whether a value came from a configuration file - or from a CLI override (--override-ini), which is important for - determining precedence when dealing with ini option aliases. - - The mode tracks the parsing mode/data model used for the value: - - "ini": from INI files or [tool.pytest.ini_options], where the only - supported value types are `str` or `list[str]`. - - "toml": from TOML files (not in INI mode), where native TOML types - are preserved. - """ - - value: object - _: KW_ONLY - origin: Literal["file", "override"] - mode: Literal["ini", "toml"] - - -ConfigDict: TypeAlias = dict[str, ConfigValue] +if TYPE_CHECKING: + from . import Config def _parse_ini_config(path: Path) -> iniconfig.IniConfig: @@ -57,23 +36,21 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: def load_config_dict_from_file( filepath: Path, -) -> ConfigDict | None: +) -> Optional[Dict[str, Union[str, List[str]]]]: """Load pytest configuration from the given file path, if supported. Return None if the file does not contain valid pytest configuration. """ + # Configuration from ini files are obtained from the [pytest] section, if present. if filepath.suffix == ".ini": iniconfig = _parse_ini_config(filepath) if "pytest" in iniconfig: - return { - k: ConfigValue(v, origin="file", mode="ini") - for k, v in iniconfig["pytest"].items() - } + return dict(iniconfig["pytest"].items()) else: # "pytest.ini" files are always the source of configuration, even if empty. - if filepath.name in {"pytest.ini", ".pytest.ini"}: + if filepath.name == "pytest.ini": return {} # '.cfg' files are considered if they contain a "[tool:pytest]" section. @@ -81,18 +58,13 @@ def load_config_dict_from_file( iniconfig = _parse_ini_config(filepath) if "tool:pytest" in iniconfig.sections: - return { - k: ConfigValue(v, origin="file", mode="ini") - for k, v in iniconfig["tool:pytest"].items() - } + return dict(iniconfig["tool:pytest"].items()) elif "pytest" in iniconfig.sections: # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086). fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False) - # '.toml' files are considered if they contain a [tool.pytest] table (toml mode) - # or [tool.pytest.ini_options] table (ini mode) for pyproject.toml, - # or [pytest] table (toml mode) for pytest.toml/.pytest.toml. + # '.toml' files are considered if they contain a [tool.pytest.ini_options] table. elif filepath.suffix == ".toml": if sys.version_info >= (3, 11): import tomllib @@ -105,67 +77,25 @@ def load_config_dict_from_file( except tomllib.TOMLDecodeError as exc: raise UsageError(f"{filepath}: {exc}") from exc - # pytest.toml and .pytest.toml use [pytest] table directly. - if filepath.name in ("pytest.toml", ".pytest.toml"): - pytest_config = config.get("pytest", {}) - if pytest_config: - # TOML mode - preserve native TOML types. - return { - k: ConfigValue(v, origin="file", mode="toml") - for k, v in pytest_config.items() - } - # "pytest.toml" files are always the source of configuration, even if empty. - return {} + result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) + if result is not None: + # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), + # however we need to convert all scalar values to str for compatibility with the rest + # of the configuration system, which expects strings only. + def make_scalar(v: object) -> Union[str, List[str]]: + return v if isinstance(v, list) else str(v) - # pyproject.toml uses [tool.pytest] or [tool.pytest.ini_options]. - else: - tool_pytest = config.get("tool", {}).get("pytest", {}) - - # Check for toml mode config: [tool.pytest] with content outside of ini_options. - toml_config = {k: v for k, v in tool_pytest.items() if k != "ini_options"} - # Check for ini mode config: [tool.pytest.ini_options]. - ini_config = tool_pytest.get("ini_options", None) - - if toml_config and ini_config: - raise UsageError( - f"{filepath}: Cannot use both [tool.pytest] (native TOML types) and " - "[tool.pytest.ini_options] (string-based INI format) simultaneously. " - "Please use [tool.pytest] with native TOML types (recommended) " - "or [tool.pytest.ini_options] for backwards compatibility." - ) - - if toml_config: - # TOML mode - preserve native TOML types. - return { - k: ConfigValue(v, origin="file", mode="toml") - for k, v in toml_config.items() - } - - elif ini_config is not None: - # INI mode - TOML supports richer data types than INI files, but we need to - # convert all scalar values to str for compatibility with the INI system. - def make_scalar(v: object) -> str | list[str]: - return v if isinstance(v, list) else str(v) - - return { - k: ConfigValue(make_scalar(v), origin="file", mode="ini") - for k, v in ini_config.items() - } + return {k: make_scalar(v) for k, v in result.items()} return None def locate_config( - invocation_dir: Path, args: Iterable[Path], -) -> tuple[Path | None, Path | None, ConfigDict, Sequence[str]]: +) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: """Search in the list of arguments for a valid ini-file for pytest, - and return a tuple of (rootdir, inifile, cfg-dict, ignored-config-files), where - ignored-config-files is a list of config basenames found that contain - pytest configuration but were ignored.""" + and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ - "pytest.toml", - ".pytest.toml", "pytest.ini", ".pytest.ini", "pyproject.toml", @@ -174,39 +104,21 @@ def locate_config( ] args = [x for x in args if not str(x).startswith("-")] if not args: - args = [invocation_dir] - found_pyproject_toml: Path | None = None - ignored_config_files: list[str] = [] - + args = [Path.cwd()] for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): for config_name in config_names: p = base / config_name if p.is_file(): - if p.name == "pyproject.toml" and found_pyproject_toml is None: - found_pyproject_toml = p ini_config = load_config_dict_from_file(p) if ini_config is not None: - index = config_names.index(config_name) - for remainder in config_names[index + 1 :]: - p2 = base / remainder - if ( - p2.is_file() - and load_config_dict_from_file(p2) is not None - ): - ignored_config_files.append(remainder) - return base, p, ini_config, ignored_config_files - if found_pyproject_toml is not None: - return found_pyproject_toml.parent, found_pyproject_toml, {}, [] - return None, None, {}, [] + return base, p, ini_config + return None, None, {} -def get_common_ancestor( - invocation_dir: Path, - paths: Iterable[Path], -) -> Path: - common_ancestor: Path | None = None +def get_common_ancestor(paths: Iterable[Path]) -> Path: + common_ancestor: Optional[Path] = None for path in paths: if not path.exists(): continue @@ -222,13 +134,13 @@ def get_common_ancestor( if shared is not None: common_ancestor = shared if common_ancestor is None: - common_ancestor = invocation_dir + common_ancestor = Path.cwd() elif common_ancestor.is_file(): common_ancestor = common_ancestor.parent return common_ancestor -def get_dirs_from_args(args: Iterable[str]) -> list[Path]: +def get_dirs_from_args(args: Iterable[str]) -> List[Path]: def is_option(x: str) -> bool: return x.startswith("-") @@ -250,70 +162,26 @@ def get_dirs_from_args(args: Iterable[str]) -> list[Path]: return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] -def parse_override_ini(override_ini: Sequence[str] | None) -> ConfigDict: - """Parse the -o/--override-ini command line arguments and return the overrides. - - :raises UsageError: - If one of the values is malformed. - """ - overrides = {} - # override_ini is a list of "ini=value" options. - # Always use the last item if multiple values are set for same ini-name, - # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. - for ini_config in override_ini or (): - try: - key, user_ini_value = ini_config.split("=", 1) - except ValueError as e: - raise UsageError( - f"-o/--override-ini expects option=value style (got: {ini_config!r})." - ) from e - else: - overrides[key] = ConfigValue(user_ini_value, origin="override", mode="ini") - return overrides - - CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." def determine_setup( - *, - inifile: str | None, - override_ini: Sequence[str] | None, + inifile: Optional[str], args: Sequence[str], - rootdir_cmd_arg: str | None, - invocation_dir: Path, -) -> tuple[Path, Path | None, ConfigDict, Sequence[str]]: - """Determine the rootdir, inifile and ini configuration values from the - command line arguments. - - :param inifile: - The `--inifile` command line argument, if given. - :param override_ini: - The -o/--override-ini command line arguments, if given. - :param args: - The free command line arguments. - :param rootdir_cmd_arg: - The `--rootdir` command line argument, if given. - :param invocation_dir: - The working directory when pytest was invoked. - - :raises UsageError: - """ + rootdir_cmd_arg: Optional[str] = None, + config: Optional["Config"] = None, +) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: rootdir = None dirs = get_dirs_from_args(args) - ignored_config_files: Sequence[str] = [] - if inifile: inipath_ = absolutepath(inifile) - inipath: Path | None = inipath_ + inipath: Optional[Path] = inipath_ inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: rootdir = inipath_.parent else: - ancestor = get_common_ancestor(invocation_dir, dirs) - rootdir, inipath, inicfg, ignored_config_files = locate_config( - invocation_dir, [ancestor] - ) + ancestor = get_common_ancestor(dirs) + rootdir, inipath, inicfg = locate_config([ancestor]) if rootdir is None and rootdir_cmd_arg is None: for possible_rootdir in (ancestor, *ancestor.parents): if (possible_rootdir / "setup.py").is_file(): @@ -321,25 +189,25 @@ def determine_setup( break else: if dirs != [ancestor]: - rootdir, inipath, inicfg, _ = locate_config(invocation_dir, dirs) + rootdir, inipath, inicfg = locate_config(dirs) if rootdir is None: - rootdir = get_common_ancestor( - invocation_dir, [invocation_dir, ancestor] - ) + if config is not None: + cwd = config.invocation_params.dir + else: + cwd = Path.cwd() + rootdir = get_common_ancestor([cwd, ancestor]) if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) if not rootdir.is_dir(): raise UsageError( - f"Directory '{rootdir}' not found. Check your '--rootdir' option." + "Directory '{}' not found. Check your '--rootdir' option.".format( + rootdir + ) ) - - ini_overrides = parse_override_ini(override_ini) - inicfg.update(ini_overrides) - assert rootdir is not None - return rootdir, inipath, inicfg, ignored_config_files + return rootdir, inipath, inicfg or {} def is_fs_root(p: Path) -> bool: diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/debugging.py b/Backend/venv/lib/python3.12/site-packages/_pytest/debugging.py index de1b2688..a3f80802 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/debugging.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/debugging.py @@ -1,21 +1,21 @@ -# mypy: allow-untyped-defs -# ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" - -from __future__ import annotations - import argparse -from collections.abc import Callable -from collections.abc import Generator import functools import sys import types -from typing import Any import unittest +from typing import Any +from typing import Callable +from typing import Generator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union from _pytest import outcomes from _pytest._code import ExceptionInfo -from _pytest.capture import CaptureManager from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.config import hookimpl @@ -24,10 +24,13 @@ from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError from _pytest.nodes import Node from _pytest.reports import BaseReport -from _pytest.runner import CallInfo + +if TYPE_CHECKING: + from _pytest.capture import CaptureManager + from _pytest.runner import CallInfo -def _validate_usepdb_cls(value: str) -> tuple[str, str]: +def _validate_usepdb_cls(value: str) -> Tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") @@ -40,13 +43,13 @@ def _validate_usepdb_cls(value: str) -> tuple[str, str]: def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group.addoption( + group._addoption( "--pdb", dest="usepdb", action="store_true", help="Start the interactive Python debugger on errors or KeyboardInterrupt", ) - group.addoption( + group._addoption( "--pdbcls", dest="usepdb_cls", metavar="modulename:classname", @@ -54,7 +57,7 @@ def pytest_addoption(parser: Parser) -> None: help="Specify a custom interactive Python debugger for use with --pdb." "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb", ) - group.addoption( + group._addoption( "--trace", dest="trace", action="store_true", @@ -92,22 +95,22 @@ def pytest_configure(config: Config) -> None: class pytestPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: PytestPluginManager | None = None - _config: Config | None = None - _saved: list[ - tuple[Callable[..., None], PytestPluginManager | None, Config | None] + _pluginmanager: Optional[PytestPluginManager] = None + _config: Optional[Config] = None + _saved: List[ + Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]] ] = [] _recursive_debug = 0 - _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None + _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None @classmethod - def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: + def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman: CaptureManager | None): + def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): if not cls._config: import pdb @@ -146,10 +149,12 @@ class pytestPDB: return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): import _pytest.config - class PytestPdbWrapper(pdb_cls): + # Type ignored because mypy doesn't support "dynamic" + # inheritance like this. + class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] _pytest_capman = capman _continued = False @@ -159,9 +164,6 @@ class pytestPDB: cls._recursive_debug -= 1 return ret - if hasattr(pdb_cls, "do_debug"): - do_debug.__doc__ = pdb_cls.do_debug.__doc__ - def do_continue(self, arg): ret = super().do_continue(arg) if cls._recursive_debug == 0: @@ -177,7 +179,8 @@ class pytestPDB: else: tw.sep( ">", - f"PDB continue (IO-capturing resumed for {capturing})", + "PDB continue (IO-capturing resumed for %s)" + % capturing, ) assert capman is not None capman.resume() @@ -188,17 +191,15 @@ class pytestPDB: self._continued = True return ret - if hasattr(pdb_cls, "do_continue"): - do_continue.__doc__ = pdb_cls.do_continue.__doc__ - do_c = do_cont = do_continue def do_quit(self, arg): - # Raise Exit outcome when quit command is used in pdb. - # - # This is a bit of a hack - it would be better if BdbQuit - # could be handled, but this would require to wrap the - # whole pytest run, and adjust the report etc. + """Raise Exit outcome when quit command is used in pdb. + + This is a bit of a hack - it would be better if BdbQuit + could be handled, but this would require to wrap the + whole pytest run, and adjust the report etc. + """ ret = super().do_quit(arg) if cls._recursive_debug == 0: @@ -206,9 +207,6 @@ class pytestPDB: return ret - if hasattr(pdb_cls, "do_quit"): - do_quit.__doc__ = pdb_cls.do_quit.__doc__ - do_q = do_quit do_exit = do_quit @@ -243,7 +241,7 @@ class pytestPDB: import _pytest.config if cls._pluginmanager is None: - capman: CaptureManager | None = None + capman: Optional[CaptureManager] = None else: capman = cls._pluginmanager.getplugin("capturemanager") if capman: @@ -265,7 +263,8 @@ class pytestPDB: elif capturing: tw.sep( ">", - f"PDB {method} (IO-capturing turned off for {capturing})", + "PDB %s (IO-capturing turned off for %s)" + % (method, capturing), ) else: tw.sep(">", f"PDB {method}") @@ -286,7 +285,7 @@ class pytestPDB: class PdbInvoke: def pytest_exception_interact( - self, node: Node, call: CallInfo[Any], report: BaseReport + self, node: Node, call: "CallInfo[Any]", report: BaseReport ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: @@ -300,18 +299,18 @@ class PdbInvoke: _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: - exc_or_tb = _postmortem_exc_or_tb(excinfo) - post_mortem(exc_or_tb) + tb = _postmortem_traceback(excinfo) + post_mortem(tb) class PdbTrace: - @hookimpl(wrapper=True) - def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: + @hookimpl(hookwrapper=True) + def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]: wrap_pytest_function_for_tracing(pyfuncitem) - return (yield) + yield -def wrap_pytest_function_for_tracing(pyfuncitem) -> None: +def wrap_pytest_function_for_tracing(pyfuncitem): """Change the Python function object of the given Function item by a wrapper which actually enters pdb before calling the python function itself, effectively leaving the user in the pdb prompt in the first @@ -323,14 +322,14 @@ def wrap_pytest_function_for_tracing(pyfuncitem) -> None: # python < 3.7.4) runcall's first param is `func`, which means we'd get # an exception if one of the kwargs to testfunction was called `func`. @functools.wraps(testfunction) - def wrapper(*args, **kwargs) -> None: + def wrapper(*args, **kwargs): func = functools.partial(testfunction, *args, **kwargs) _pdb.runcall(func) pyfuncitem.obj = wrapper -def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None: +def maybe_wrap_pytest_function_for_tracing(pyfuncitem): """Wrap the given pytestfunct item for tracing support if --trace was given in the command line.""" if pyfuncitem.config.getvalue("trace"): @@ -340,7 +339,7 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None: def _enter_pdb( node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport ) -> BaseReport: - # XXX we reuse the TerminalReporter's terminalwriter + # XXX we re-use the TerminalReporter's terminalwriter # because this seems to avoid some encoding related troubles # for not completely clear reasons. tw = node.config.pluginmanager.getplugin("terminalreporter")._tw @@ -362,46 +361,31 @@ def _enter_pdb( tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") - tb_or_exc = _postmortem_exc_or_tb(excinfo) + tb = _postmortem_traceback(excinfo) rep._pdbshown = True # type: ignore[attr-defined] - post_mortem(tb_or_exc) + post_mortem(tb) return rep -def _postmortem_exc_or_tb( - excinfo: ExceptionInfo[BaseException], -) -> types.TracebackType | BaseException: +def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType: from doctest import UnexpectedException - get_exc = sys.version_info >= (3, 13) if isinstance(excinfo.value, UnexpectedException): # A doctest.UnexpectedException is not useful for post_mortem. # Use the underlying exception instead: - underlying_exc = excinfo.value - if get_exc: - return underlying_exc.exc_info[1] - - return underlying_exc.exc_info[2] + return excinfo.value.exc_info[2] elif isinstance(excinfo.value, ConftestImportFailure): # A config.ConftestImportFailure is not useful for post_mortem. # Use the underlying exception instead: - cause = excinfo.value.cause - if get_exc: - return cause - - assert cause.__traceback__ is not None - return cause.__traceback__ + return excinfo.value.excinfo[2] else: assert excinfo._excinfo is not None - if get_exc: - return excinfo._excinfo[1] - return excinfo._excinfo[2] -def post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None: +def post_mortem(t: types.TracebackType) -> None: p = pytestPDB._init_pdb("post_mortem") p.reset() - p.interaction(None, tb_or_exc) + p.interaction(None, t) if p.quitting: outcomes.exit("Quitting debugger") diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/deprecated.py b/Backend/venv/lib/python3.12/site-packages/_pytest/deprecated.py index cb5d2e93..b9c10df7 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/deprecated.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/deprecated.py @@ -8,52 +8,111 @@ All constants defined in this module should be either instances of :class:`PytestWarning`, or :class:`UnformattedWarning` in case of warnings which need to format their messages. """ - -from __future__ import annotations - from warnings import warn from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestRemovedIn9Warning -from _pytest.warning_types import PytestRemovedIn10Warning +from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import UnformattedWarning - # set of plugins which have been integrated into the core; we use this list to ignore # them during registration to avoid conflicts DEPRECATED_EXTERNAL_PLUGINS = { "pytest_catchlog", "pytest_capturelog", "pytest_faulthandler", - "pytest_subtests", } +NOSE_SUPPORT = UnformattedWarning( + PytestRemovedIn8Warning, + "Support for nose tests is deprecated and will be removed in a future release.\n" + "{nodeid} is using nose method: `{method}` ({stage})\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", +) -# This could have been removed pytest 8, but it's harmless and common, so no rush to remove. +NOSE_SUPPORT_METHOD = UnformattedWarning( + PytestRemovedIn8Warning, + "Support for nose tests is deprecated and will be removed in a future release.\n" + "{nodeid} is using nose-specific method: `{method}(self)`\n" + "To remove this warning, rename it to `{method}_method(self)`\n" + "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", +) + + +# This can be* removed pytest 8, but it's harmless and common, so no rush to remove. +# * If you're in the future: "could have been". YIELD_FIXTURE = PytestDeprecationWarning( "@pytest.yield_fixture is deprecated.\n" "Use @pytest.fixture instead; they are the same." ) +WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( + "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n" + "Please use pytest_load_initial_conftests hook instead." +) + +FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning( + "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; " + "use self.session.gethookproxy() and self.session.isinitpath() instead. " +) + +STRICT_OPTION = PytestRemovedIn8Warning( + "The --strict option is deprecated, use --strict-markers instead." +) + # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") +ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning( + 'pytest now uses argparse. "%default" should be changed to "%(default)s"', +) + +ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning( + PytestRemovedIn8Warning, + "`type` argument to addoption() is the string {typ!r}." + " For choices this is optional and can be omitted, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: {names})", +) + +ARGUMENT_TYPE_STR = UnformattedWarning( + PytestRemovedIn8Warning, + "`type` argument to addoption() is the string {typ!r}, " + " but when supplied should be a type (for example `str` or `int`)." + " (options: {names})", +) + HOOK_LEGACY_PATH_ARG = UnformattedWarning( - PytestRemovedIn9Warning, + PytestRemovedIn8Warning, "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n" "see https://docs.pytest.org/en/latest/deprecations.html" "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", ) NODE_CTOR_FSPATH_ARG = UnformattedWarning( - PytestRemovedIn9Warning, + PytestRemovedIn8Warning, "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " "Please use the (path: pathlib.Path) argument instead.\n" "See https://docs.pytest.org/en/latest/deprecations.html" "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", ) +WARNS_NONE_ARG = PytestRemovedIn8Warning( + "Passing None has been deprecated.\n" + "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" + "#additional-use-cases-of-warnings-in-tests" + " for alternatives in common use cases." +) + +KEYWORD_MSG_ARG = UnformattedWarning( + PytestRemovedIn8Warning, + "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", +) + +INSTANCE_COLLECTOR = PytestRemovedIn8Warning( + "The pytest.Instance collector type is deprecated and is no longer used. " + "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", +) HOOK_LEGACY_MARKING = UnformattedWarning( PytestDeprecationWarning, "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" @@ -63,18 +122,6 @@ HOOK_LEGACY_MARKING = UnformattedWarning( "#configuring-hook-specs-impls-using-markers", ) -MARKED_FIXTURE = PytestRemovedIn9Warning( - "Marks applied to fixtures have no effect\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" -) - -MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES = PytestRemovedIn10Warning( - "monkeypatch.syspath_prepend() called with pkg_resources legacy namespace packages detected.\n" - "Legacy namespace packages (using pkg_resources.declare_namespace) are deprecated.\n" - "Please use native namespace packages (PEP 420) instead.\n" - "See https://docs.pytest.org/en/stable/deprecations.html#monkeypatch-fixup-namespace-packages" -) - # You want to make some `__init__` or function "private". # # def my_private_function(some, args): diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/doctest.py b/Backend/venv/lib/python3.12/site-packages/_pytest/doctest.py index cd255f5e..ca41a98e 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/doctest.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/doctest.py @@ -1,26 +1,28 @@ -# mypy: allow-untyped-defs """Discover and run doctests in modules and test files.""" - -from __future__ import annotations - import bdb -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Sequence -from contextlib import contextmanager import functools import inspect import os -from pathlib import Path import platform -import re import sys import traceback import types -from typing import Any -from typing import TYPE_CHECKING import warnings +from contextlib import contextmanager +from pathlib import Path +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union from _pytest import outcomes from _pytest._code.code import ExceptionInfo @@ -31,22 +33,20 @@ from _pytest.compat import safe_getattr from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.fixtures import fixture -from _pytest.fixtures import TopRequest +from _pytest.fixtures import FixtureRequest from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import OutcomeException from _pytest.outcomes import skip from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import import_path from _pytest.python import Module from _pytest.python_api import approx from _pytest.warning_types import PytestWarning - if TYPE_CHECKING: import doctest - from typing_extensions import Self - DOCTEST_REPORT_CHOICE_NONE = "none" DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" @@ -64,7 +64,7 @@ DOCTEST_REPORT_CHOICES = ( # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: type[doctest.OutputChecker] | None = None +CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None def pytest_addoption(parser: Parser) -> None: @@ -105,7 +105,7 @@ def pytest_addoption(parser: Parser) -> None: "--doctest-ignore-import-errors", action="store_true", default=False, - help="Ignore doctest collection errors", + help="Ignore doctest ImportErrors", dest="doctest_ignore_import_errors", ) group.addoption( @@ -126,15 +126,17 @@ def pytest_unconfigure() -> None: def pytest_collect_file( file_path: Path, parent: Collector, -) -> DoctestModule | DoctestTextfile | None: +) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config if file_path.suffix == ".py": if config.option.doctestmodules and not any( (_is_setup_py(file_path), _is_main_py(file_path)) ): - return DoctestModule.from_parent(parent, path=file_path) + mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path) + return mod elif _is_doctest(config, file_path, parent): - return DoctestTextfile.from_parent(parent, path=file_path) + txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path) + return txt return None @@ -158,7 +160,7 @@ def _is_main_py(path: Path) -> bool: class ReprFailDoctest(TerminalRepr): def __init__( - self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] + self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] ) -> None: self.reprlocation_lines = reprlocation_lines @@ -170,12 +172,12 @@ class ReprFailDoctest(TerminalRepr): class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: + def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> type[doctest.DocTestRunner]: +def _init_runner_class() -> Type["doctest.DocTestRunner"]: import doctest class PytestDoctestRunner(doctest.DebugRunner): @@ -187,8 +189,8 @@ def _init_runner_class() -> type[doctest.DocTestRunner]: def __init__( self, - checker: doctest.OutputChecker | None = None, - verbose: bool | None = None, + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: @@ -198,8 +200,8 @@ def _init_runner_class() -> type[doctest.DocTestRunner]: def report_failure( self, out, - test: doctest.DocTest, - example: doctest.Example, + test: "doctest.DocTest", + example: "doctest.Example", got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) @@ -211,9 +213,9 @@ def _init_runner_class() -> type[doctest.DocTestRunner]: def report_unexpected_exception( self, out, - test: doctest.DocTest, - example: doctest.Example, - exc_info: tuple[type[BaseException], BaseException, types.TracebackType], + test: "doctest.DocTest", + example: "doctest.Example", + exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], ) -> None: if isinstance(exc_info[1], OutcomeException): raise exc_info[1] @@ -229,11 +231,11 @@ def _init_runner_class() -> type[doctest.DocTestRunner]: def _get_runner( - checker: doctest.OutputChecker | None = None, - verbose: bool | None = None, + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> doctest.DocTestRunner: +) -> "doctest.DocTestRunner": # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -252,50 +254,45 @@ class DoctestItem(Item): def __init__( self, name: str, - parent: DoctestTextfile | DoctestModule, - runner: doctest.DocTestRunner, - dtest: doctest.DocTest, + parent: "Union[DoctestTextfile, DoctestModule]", + runner: Optional["doctest.DocTestRunner"] = None, + dtest: Optional["doctest.DocTest"] = None, ) -> None: super().__init__(name, parent) self.runner = runner self.dtest = dtest - - # Stuff needed for fixture support. self.obj = None - fm = self.session._fixturemanager - fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None) - self._fixtureinfo = fixtureinfo - self.fixturenames = fixtureinfo.names_closure - self._initrequest() + self.fixture_request: Optional[FixtureRequest] = None @classmethod - def from_parent( # type: ignore[override] + def from_parent( # type: ignore cls, - parent: DoctestTextfile | DoctestModule, + parent: "Union[DoctestTextfile, DoctestModule]", *, name: str, - runner: doctest.DocTestRunner, - dtest: doctest.DocTest, - ) -> Self: + runner: "doctest.DocTestRunner", + dtest: "doctest.DocTest", + ): # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) - def _initrequest(self) -> None: - self.funcargs: dict[str, object] = {} - self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] - def setup(self) -> None: - self._request._fillfixtures() - globs = dict(getfixture=self._request.getfixturevalue) - for name, value in self._request.getfixturevalue("doctest_namespace").items(): - globs[name] = value - self.dtest.globs.update(globs) + if self.dtest is not None: + self.fixture_request = _setup_fixtures(self) + globs = dict(getfixture=self.fixture_request.getfixturevalue) + for name, value in self.fixture_request.getfixturevalue( + "doctest_namespace" + ).items(): + globs[name] = value + self.dtest.globs.update(globs) def runtest(self) -> None: + assert self.dtest is not None + assert self.runner is not None _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures: list[doctest.DocTestFailure] = [] + failures: List["doctest.DocTestFailure"] = [] # Type ignored because we change the type of `out` from what # doctest expects. self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] @@ -317,14 +314,14 @@ class DoctestItem(Item): def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> str | TerminalRepr: + ) -> Union[str, TerminalRepr]: import doctest - failures: ( - Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None - ) = None + failures: Optional[ + Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] + ] = None if isinstance( - excinfo.value, doctest.DocTestFailure | doctest.UnexpectedException + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): failures = [excinfo.value] elif isinstance(excinfo.value, MultipleDoctestFailures): @@ -353,7 +350,7 @@ class DoctestItem(Item): # add line numbers to the left of the error message assert test.lineno is not None lines = [ - f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines) + "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines) ] # trim docstring error lines to 10 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] @@ -371,18 +368,19 @@ class DoctestItem(Item): ).split("\n") else: inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) - lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"] + lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] lines += [ x.strip("\n") for x in traceback.format_exception(*failure.exc_info) ] reprlocation_lines.append((reprlocation, lines)) return ReprFailDoctest(reprlocation_lines) - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: - return self.path, self.dtest.lineno, f"[doctest] {self.name}" + def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + assert self.dtest is not None + return self.path, self.dtest.lineno, "[doctest] %s" % self.name -def _get_flag_lookup() -> dict[str, int]: +def _get_flag_lookup() -> Dict[str, int]: import doctest return dict( @@ -398,8 +396,8 @@ def _get_flag_lookup() -> dict[str, int]: ) -def get_optionflags(config: Config) -> int: - optionflags_str = config.getini("doctest_optionflags") +def get_optionflags(parent): + optionflags_str = parent.config.getini("doctest_optionflags") flag_lookup_table = _get_flag_lookup() flag_acc = 0 for flag in optionflags_str: @@ -407,8 +405,8 @@ def get_optionflags(config: Config) -> int: return flag_acc -def _get_continue_on_failure(config: Config) -> bool: - continue_on_failure: bool = config.getvalue("doctest_continue_on_failure") +def _get_continue_on_failure(config): + continue_on_failure = config.getvalue("doctest_continue_on_failure") if continue_on_failure: # We need to turn off this if we use pdb since we should stop at # the first failure. @@ -431,7 +429,7 @@ class DoctestTextfile(Module): name = self.path.name globs = {"__name__": "__main__"} - optionflags = get_optionflags(self.config) + optionflags = get_optionflags(self) runner = _get_runner( verbose=False, @@ -448,7 +446,7 @@ class DoctestTextfile(Module): ) -def _check_all_skipped(test: doctest.DocTest) -> None: +def _check_all_skipped(test: "doctest.DocTest") -> None: """Raise pytest.skip() if all examples in the given DocTest have the SKIP option set.""" import doctest @@ -468,13 +466,13 @@ def _is_mocked(obj: object) -> bool: @contextmanager -def _patch_unwrap_mock_aware() -> Generator[None]: +def _patch_unwrap_mock_aware() -> Generator[None, None, None]: """Context manager which replaces ``inspect.unwrap`` with a version that's aware of mock objects and doesn't recurse into them.""" real_unwrap = inspect.unwrap def _mock_aware_unwrap( - func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None + func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None ) -> Any: try: if stop is None or stop is _is_mocked: @@ -483,9 +481,9 @@ def _patch_unwrap_mock_aware() -> Generator[None]: return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) except Exception as e: warnings.warn( - f"Got {e!r} when unwrapping {func!r}. This is usually caused " + "Got %r when unwrapping %r. This is usually caused " "by a violation of Python's object protocol; see e.g. " - "https://github.com/pytest-dev/pytest/issues/5080", + "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), PytestWarning, ) raise @@ -502,32 +500,41 @@ class DoctestModule(Module): import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - py_ver_info_minor = sys.version_info[:2] - is_find_lineno_broken = ( - py_ver_info_minor < (3, 11) - or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9) - or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3) - ) - if is_find_lineno_broken: + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. - def _find_lineno(self, obj, source_lines): - """On older Pythons, doctest code does not take into account - `@property`. https://github.com/python/cpython/issues/61648 + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ - Moreover, wrapped Doctests need to be unwrapped so the correct - line number is returned. #8796 - """ - if isinstance(obj, property): - obj = getattr(obj, "fget", obj) + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. https://bugs.python.org/issue17446 - if hasattr(obj, "__wrapped__"): - # Get the main obj in case of it being wrapped - obj = inspect.unwrap(obj) + Wrapped Doctests will need to be unwrapped so the correct + line number is returned. This will be reported upstream. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): # Type ignored because this is a private function. - return super()._find_lineno( # type:ignore[misc] - obj, - source_lines, + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen ) if sys.version_info < (3, 13): @@ -538,27 +545,38 @@ class DoctestModule(Module): Here we override `_from_module` to check the underlying function instead. https://github.com/python/cpython/issues/107995 """ - if isinstance(object, functools.cached_property): + if hasattr(functools, "cached_property") and isinstance( + object, functools.cached_property + ): object = object.func # Type ignored because this is a private function. return super()._from_module(module, object) # type: ignore[misc] - try: - module = self.obj - except Collector.CollectError: - if self.config.getvalue("doctest_ignore_import_errors"): - skip(f"unable to import module {self.path!r}") - else: - raise - - # While doctests currently don't support fixtures directly, we still - # need to pick up autouse fixtures. - self.session._fixturemanager.parsefactories(self) + else: # pragma: no cover + pass + if self.path.name == "conftest.py": + module = self.config.pluginmanager._importconftest( + self.path, + self.config.getoption("importmode"), + rootpath=self.config.rootpath, + ) + else: + try: + module = import_path( + self.path, + root=self.config.rootpath, + mode=self.config.getoption("importmode"), + ) + except ImportError: + if self.config.getvalue("doctest_ignore_import_errors"): + skip("unable to import module %r" % self.path) + else: + raise # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() - optionflags = get_optionflags(self.config) + optionflags = get_optionflags(self) runner = _get_runner( verbose=False, optionflags=optionflags, @@ -573,8 +591,25 @@ class DoctestModule(Module): ) -def _init_checker_class() -> type[doctest.OutputChecker]: +def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: + """Used by DoctestTextfile and DoctestItem to setup fixture information.""" + + def func() -> None: + pass + + doctest_item.funcargs = {} # type: ignore[attr-defined] + fm = doctest_item.session._fixturemanager + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] + node=doctest_item, func=func, cls=None, funcargs=False + ) + fixture_request = FixtureRequest(doctest_item, _ispytest=True) + fixture_request._fillfixtures() + return fixture_request + + +def _init_checker_class() -> Type["doctest.OutputChecker"]: import doctest + import re class LiteralsOutputChecker(doctest.OutputChecker): # Based on doctest_nose_plugin.py from the nltk project @@ -617,7 +652,7 @@ def _init_checker_class() -> type[doctest.OutputChecker]: if not allow_unicode and not allow_bytes and not allow_number: return False - def remove_prefixes(regex: re.Pattern[str], txt: str) -> str: + def remove_prefixes(regex: Pattern[str], txt: str) -> str: return re.sub(regex, r"\1\2", txt) if allow_unicode: @@ -639,9 +674,9 @@ def _init_checker_class() -> type[doctest.OutputChecker]: if len(wants) != len(gots): return got offset = 0 - for w, g in zip(wants, gots, strict=True): - fraction: str | None = w.group("fraction") - exponent: str | None = w.group("exponent1") + for w, g in zip(wants, gots): + fraction: Optional[str] = w.group("fraction") + exponent: Optional[str] = w.group("exponent1") if exponent is None: exponent = w.group("exponent2") precision = 0 if fraction is None else len(fraction) @@ -660,7 +695,7 @@ def _init_checker_class() -> type[doctest.OutputChecker]: return LiteralsOutputChecker -def _get_checker() -> doctest.OutputChecker: +def _get_checker() -> "doctest.OutputChecker": """Return a doctest.OutputChecker subclass that supports some additional options: @@ -719,7 +754,7 @@ def _get_report_choice(key: str) -> int: @fixture(scope="session") -def doctest_namespace() -> dict[str, Any]: +def doctest_namespace() -> Dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/faulthandler.py b/Backend/venv/lib/python3.12/site-packages/_pytest/faulthandler.py index 080cf583..36040bff 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/faulthandler.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/faulthandler.py @@ -1,47 +1,31 @@ -from __future__ import annotations - -from collections.abc import Generator import os import sys +from typing import Generator +import pytest from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.nodes import Item from _pytest.stash import StashKey -import pytest -fault_handler_original_stderr_fd_key = StashKey[int]() fault_handler_stderr_fd_key = StashKey[int]() +fault_handler_originally_enabled_key = StashKey[bool]() def pytest_addoption(parser: Parser) -> None: - help_timeout = ( + help = ( "Dump the traceback of all threads if a test takes " "more than TIMEOUT seconds to finish" ) - help_exit_on_timeout = ( - "Exit the test process if a test takes more than " - "faulthandler_timeout seconds to finish" - ) - parser.addini("faulthandler_timeout", help_timeout, default=0.0) - parser.addini( - "faulthandler_exit_on_timeout", help_exit_on_timeout, type="bool", default=False - ) + parser.addini("faulthandler_timeout", help, default=0.0) def pytest_configure(config: Config) -> None: import faulthandler - # at teardown we want to restore the original faulthandler fileno - # but faulthandler has no api to return the original fileno - # so here we stash the stderr fileno to be used at teardown - # sys.stderr and sys.__stderr__ may be closed or patched during the session - # so we can't rely on their values being good at that point (#11572). - stderr_fileno = get_stderr_fileno() - if faulthandler.is_enabled(): - config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno - config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno) + config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno()) + config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) @@ -53,10 +37,9 @@ def pytest_unconfigure(config: Config) -> None: if fault_handler_stderr_fd_key in config.stash: os.close(config.stash[fault_handler_stderr_fd_key]) del config.stash[fault_handler_stderr_fd_key] - # Re-enable the faulthandler if it was originally enabled. - if fault_handler_original_stderr_fd_key in config.stash: - faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key]) - del config.stash[fault_handler_original_stderr_fd_key] + if config.stash.get(fault_handler_originally_enabled_key, False): + # Re-enable the faulthandler if it was originally enabled. + faulthandler.enable(file=get_stderr_fileno()) def get_stderr_fileno() -> int: @@ -71,7 +54,6 @@ def get_stderr_fileno() -> int: # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors # This is potentially dangerous, but the best we can do. - assert sys.__stderr__ is not None return sys.__stderr__.fileno() @@ -79,27 +61,20 @@ def get_timeout_config_value(config: Config) -> float: return float(config.getini("faulthandler_timeout") or 0.0) -def get_exit_on_timeout_config_value(config: Config) -> bool: - exit_on_timeout = config.getini("faulthandler_exit_on_timeout") - assert isinstance(exit_on_timeout, bool) - return exit_on_timeout - - -@pytest.hookimpl(wrapper=True, trylast=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: +@pytest.hookimpl(hookwrapper=True, trylast=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: timeout = get_timeout_config_value(item.config) - exit_on_timeout = get_exit_on_timeout_config_value(item.config) if timeout > 0: import faulthandler stderr = item.config.stash[fault_handler_stderr_fd_key] - faulthandler.dump_traceback_later(timeout, file=stderr, exit=exit_on_timeout) + faulthandler.dump_traceback_later(timeout, file=stderr) try: - return (yield) + yield finally: faulthandler.cancel_dump_traceback_later() else: - return (yield) + yield @pytest.hookimpl(tryfirst=True) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/fixtures.py b/Backend/venv/lib/python3.12/site-packages/_pytest/fixtures.py index 27846db1..0462504e 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/fixtures.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/fixtures.py @@ -1,61 +1,59 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import abc -from collections import defaultdict -from collections import deque -from collections import OrderedDict -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import MutableMapping -from collections.abc import Sequence -from collections.abc import Set as AbstractSet import dataclasses import functools import inspect import os -from pathlib import Path import sys -import types +import warnings +from collections import defaultdict +from collections import deque +from contextlib import suppress +from pathlib import Path +from types import TracebackType from typing import Any +from typing import Callable from typing import cast -from typing import Final -from typing import final +from typing import Dict +from typing import Generator from typing import Generic +from typing import Iterable +from typing import Iterator +from typing import List +from typing import MutableMapping from typing import NoReturn -from typing import overload +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -import warnings +from typing import Union import _pytest from _pytest import nodes from _pytest._code import getfslineno -from _pytest._code import Source from _pytest._code.code import FormattedExcinfo from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter +from _pytest.compat import _format_args +from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never +from _pytest.compat import final from _pytest.compat import get_real_func +from _pytest.compat import get_real_method from _pytest.compat import getfuncargnames from _pytest.compat import getimfunc from _pytest.compat import getlocation +from _pytest.compat import is_generator from _pytest.compat import NOTSET from _pytest.compat import NotSetType +from _pytest.compat import overload from _pytest.compat import safe_getattr -from _pytest.compat import safe_isclass -from _pytest.compat import signature from _pytest.config import _PluggyPlugin from _pytest.config import Config -from _pytest.config import ExitCode from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import MARKED_FIXTURE from _pytest.deprecated import YIELD_FIXTURE -from _pytest.main import Session from _pytest.mark import Mark from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator @@ -64,77 +62,81 @@ from _pytest.outcomes import skip from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath -from _pytest.scope import _ScopeName from _pytest.scope import HIGH_SCOPES from _pytest.scope import Scope -from _pytest.warning_types import PytestRemovedIn9Warning -from _pytest.warning_types import PytestWarning - - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup +from _pytest.stash import StashKey if TYPE_CHECKING: + from typing import Deque + + from _pytest.scope import _ScopeName + from _pytest.main import Session from _pytest.python import CallSpec2 - from _pytest.python import Function from _pytest.python import Metafunc # The value of the fixture -- return/yield of the fixture function (type variable). -FixtureValue = TypeVar("FixtureValue", covariant=True) +FixtureValue = TypeVar("FixtureValue") # The type of the fixture function (type variable). FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object]) # The type of a fixture function (type alias generic in fixture value). -_FixtureFunc = Callable[..., FixtureValue] | Callable[..., Generator[FixtureValue]] +_FixtureFunc = Union[ + Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]] +] # The type of FixtureDef.cached_result (type alias generic in fixture value). -_FixtureCachedResult = ( - tuple[ +_FixtureCachedResult = Union[ + Tuple[ # The result. FixtureValue, # Cache key. object, None, - ] - | tuple[ + ], + Tuple[ None, # Cache key. object, - # The exception and the original traceback. - tuple[BaseException, types.TracebackType | None], - ] -) + # Exc info if raised. + Tuple[Type[BaseException], BaseException, TracebackType], + ], +] -def pytest_sessionstart(session: Session) -> None: +@dataclasses.dataclass(frozen=True) +class PseudoFixtureDef(Generic[FixtureValue]): + cached_result: "_FixtureCachedResult[FixtureValue]" + _scope: Scope + + +def pytest_sessionstart(session: "Session") -> None: session._fixturemanager = FixtureManager(session) def get_scope_package( node: nodes.Item, - fixturedef: FixtureDef[object], -) -> nodes.Node | None: + fixturedef: "FixtureDef[object]", +) -> Optional[Union[nodes.Item, nodes.Collector]]: from _pytest.python import Package - for parent in node.iter_parents(): - if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid: - return parent - return node.session + current: Optional[Union[nodes.Item, nodes.Collector]] = node + fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py") + while current and ( + not isinstance(current, Package) or fixture_package_name != current.nodeid + ): + current = current.parent # type: ignore[assignment] + if current is None: + return node.session + return current -def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: - """Get the closest parent node (including self) which matches the given - scope. - - If there is no parent node for the scope (e.g. asking for class scope on a - Module, or on a Function when not defined in a class), returns None. - """ +def get_scope_node( + node: nodes.Node, scope: Scope +) -> Optional[Union[nodes.Item, nodes.Collector]]: import _pytest.python if scope is Scope.Function: - # Type ignored because this is actually safe, see: - # https://github.com/python/mypy/issues/4717 - return node.getparent(nodes.Item) # type: ignore[type-abstract] + return node.getparent(nodes.Item) elif scope is Scope.Class: return node.getparent(_pytest.python.Class) elif scope is Scope.Module: @@ -147,12 +149,121 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: assert_never(scope) -# TODO: Try to use FixtureFunctionDefinition instead of the marker -def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: - """Return fixturemarker or None if it doesn't exist""" - if isinstance(obj, FixtureFunctionDefinition): - return obj._fixture_function_marker - return None +# Used for storing artificial fixturedefs for direct parametrization. +name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]() + + +def add_funcarg_pseudo_fixture_def( + collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager" +) -> None: + # This function will transform all collected calls to functions + # if they use direct funcargs (i.e. direct parametrization) + # because we want later test execution to be able to rely on + # an existing FixtureDef structure for all arguments. + # XXX we can probably avoid this algorithm if we modify CallSpec2 + # to directly care for creating the fixturedefs within its methods. + if not metafunc._calls[0].funcargs: + # This function call does not have direct parametrization. + return + # Collect funcargs of all callspecs into a list of values. + arg2params: Dict[str, List[object]] = {} + arg2scope: Dict[str, Scope] = {} + for callspec in metafunc._calls: + for argname, argvalue in callspec.funcargs.items(): + assert argname not in callspec.params + callspec.params[argname] = argvalue + arg2params_list = arg2params.setdefault(argname, []) + callspec.indices[argname] = len(arg2params_list) + arg2params_list.append(argvalue) + if argname not in arg2scope: + scope = callspec._arg2scope.get(argname, Scope.Function) + arg2scope[argname] = scope + callspec.funcargs.clear() + + # Register artificial FixtureDef's so that later at test execution + # time we can rely on a proper FixtureDef to exist for fixture setup. + arg2fixturedefs = metafunc._arg2fixturedefs + for argname, valuelist in arg2params.items(): + # If we have a scope that is higher than function, we need + # to make sure we only ever create an according fixturedef on + # a per-scope basis. We thus store and cache the fixturedef on the + # node related to the scope. + scope = arg2scope[argname] + node = None + if scope is not Scope.Function: + node = get_scope_node(collector, scope) + if node is None: + assert scope is Scope.Class and isinstance( + collector, _pytest.python.Module + ) + # Use module-level collector for class-scope (for now). + node = collector + if node is None: + name2pseudofixturedef = None + else: + default: Dict[str, FixtureDef[Any]] = {} + name2pseudofixturedef = node.stash.setdefault( + name2pseudofixturedef_key, default + ) + if name2pseudofixturedef is not None and argname in name2pseudofixturedef: + arg2fixturedefs[argname] = [name2pseudofixturedef[argname]] + else: + fixturedef = FixtureDef( + fixturemanager=fixturemanager, + baseid="", + argname=argname, + func=get_direct_param_fixture_func, + scope=arg2scope[argname], + params=valuelist, + unittest=False, + ids=None, + ) + arg2fixturedefs[argname] = [fixturedef] + if name2pseudofixturedef is not None: + name2pseudofixturedef[argname] = fixturedef + + +def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: + """Return fixturemarker or None if it doesn't exist or raised + exceptions.""" + return cast( + Optional[FixtureFunctionMarker], + safe_getattr(obj, "_pytestfixturefunction", None), + ) + + +# Parametrized fixture key, helper alias for code below. +_Key = Tuple[object, ...] + + +def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]: + """Return list of keys for all parametrized arguments which match + the specified scope.""" + assert scope is not Scope.Function + try: + callspec = item.callspec # type: ignore[attr-defined] + except AttributeError: + pass + else: + cs: CallSpec2 = callspec + # cs.indices.items() is random order of argnames. Need to + # sort this so that different calls to + # get_parametrized_fixture_keys will be deterministic. + for argname, param_index in sorted(cs.indices.items()): + if cs._arg2scope[argname] != scope: + continue + if scope is Scope.Session: + key: _Key = (argname, param_index) + elif scope is Scope.Package: + key = (argname, param_index, item.path.parent) + elif scope is Scope.Module: + key = (argname, param_index, item.path) + elif scope is Scope.Class: + item_cls = item.cls # type: ignore[attr-defined] + key = (argname, param_index, item.path, item_cls) + else: + assert_never(scope) + yield key # Algorithm for sorting on a per-parametrized resource setup basis. @@ -161,109 +272,61 @@ def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: # setups and teardowns. -@dataclasses.dataclass(frozen=True) -class ParamArgKey: - """A key for a high-scoped parameter used by an item. - - For use as a hashable key in `reorder_items`. The combination of fields - is meant to uniquely identify a particular "instance" of a param, - potentially shared by multiple items in a scope. - """ - - #: The param name. - argname: str - param_index: int - #: For scopes Package, Module, Class, the path to the file (directory in - #: Package's case) of the package/module/class where the item is defined. - scoped_item_path: Path | None - #: For Class scope, the class where the item is defined. - item_cls: type | None - - -_V = TypeVar("_V") -OrderedSet = dict[_V, None] - - -def get_param_argkeys(item: nodes.Item, scope: Scope) -> Iterator[ParamArgKey]: - """Return all ParamArgKeys for item matching the specified high scope.""" - assert scope is not Scope.Function - - try: - callspec: CallSpec2 = item.callspec # type: ignore[attr-defined] - except AttributeError: - return - - item_cls = None - if scope is Scope.Session: - scoped_item_path = None - elif scope is Scope.Package: - # Package key = module's directory. - scoped_item_path = item.path.parent - elif scope is Scope.Module: - scoped_item_path = item.path - elif scope is Scope.Class: - scoped_item_path = item.path - item_cls = item.cls # type: ignore[attr-defined] - else: - assert_never(scope) - - for argname in callspec.indices: - if callspec._arg2scope[argname] != scope: - continue - param_index = callspec.indices[argname] - yield ParamArgKey(argname, param_index, scoped_item_path, item_cls) - - -def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: - argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[ParamArgKey]]] = {} - items_by_argkey: dict[Scope, dict[ParamArgKey, OrderedDict[nodes.Item, None]]] = {} +def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {} + items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {} for scope in HIGH_SCOPES: - scoped_argkeys_by_item = argkeys_by_item[scope] = {} - scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict) + d: Dict[nodes.Item, Dict[_Key, None]] = {} + argkeys_cache[scope] = d + item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque) + items_by_argkey[scope] = item_d for item in items: - argkeys = dict.fromkeys(get_param_argkeys(item, scope)) - if argkeys: - scoped_argkeys_by_item[item] = argkeys - for argkey in argkeys: - scoped_items_by_argkey[argkey][item] = None - - items_set = dict.fromkeys(items) + keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None) + if keys: + d[item] = keys + for key in keys: + item_d[key].append(item) + items_dict = dict.fromkeys(items, None) return list( - reorder_items_atscope( - items_set, argkeys_by_item, items_by_argkey, Scope.Session - ) + reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session) ) +def fix_cache_order( + item: nodes.Item, + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], + items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], +) -> None: + for scope in HIGH_SCOPES: + for key in argkeys_cache[scope].get(item, []): + items_by_argkey[scope][key].appendleft(item) + + def reorder_items_atscope( - items: OrderedSet[nodes.Item], - argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[ParamArgKey]]], - items_by_argkey: Mapping[ - Scope, Mapping[ParamArgKey, OrderedDict[nodes.Item, None]] - ], + items: Dict[nodes.Item, None], + argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]], + items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]], scope: Scope, -) -> OrderedSet[nodes.Item]: +) -> Dict[nodes.Item, None]: if scope is Scope.Function or len(items) < 3: return items - - scoped_items_by_argkey = items_by_argkey[scope] - scoped_argkeys_by_item = argkeys_by_item[scope] - - ignore: set[ParamArgKey] = set() + ignore: Set[Optional[_Key]] = set() items_deque = deque(items) - items_done: OrderedSet[nodes.Item] = {} + items_done: Dict[nodes.Item, None] = {} + scoped_items_by_argkey = items_by_argkey[scope] + scoped_argkeys_cache = argkeys_cache[scope] while items_deque: - no_argkey_items: OrderedSet[nodes.Item] = {} + no_argkey_group: Dict[nodes.Item, None] = {} slicing_argkey = None while items_deque: item = items_deque.popleft() - if item in items_done or item in no_argkey_items: + if item in items_done or item in no_argkey_group: continue argkeys = dict.fromkeys( - k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore + (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None ) if not argkeys: - no_argkey_items[item] = None + no_argkey_group[item] = None else: slicing_argkey, _ = argkeys.popitem() # We don't have to remove relevant items from later in the @@ -272,64 +335,35 @@ def reorder_items_atscope( i for i in scoped_items_by_argkey[slicing_argkey] if i in items ] for i in reversed(matching_items): + fix_cache_order(i, argkeys_cache, items_by_argkey) items_deque.appendleft(i) - # Fix items_by_argkey order. - for other_scope in HIGH_SCOPES: - other_scoped_items_by_argkey = items_by_argkey[other_scope] - for argkey in argkeys_by_item[other_scope].get(i, ()): - argkey_dict = other_scoped_items_by_argkey[argkey] - if not hasattr(sys, "pypy_version_info"): - argkey_dict[i] = None - argkey_dict.move_to_end(i, last=False) - else: - # Work around a bug in PyPy: - # https://github.com/pypy/pypy/issues/5257 - # https://github.com/pytest-dev/pytest/issues/13312 - bkp = argkey_dict.copy() - argkey_dict.clear() - argkey_dict[i] = None - argkey_dict.update(bkp) break - if no_argkey_items: - reordered_no_argkey_items = reorder_items_atscope( - no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower() + if no_argkey_group: + no_argkey_group = reorder_items_atscope( + no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower() ) - items_done.update(reordered_no_argkey_items) - if slicing_argkey is not None: - ignore.add(slicing_argkey) + for item in no_argkey_group: + items_done[item] = None + ignore.add(slicing_argkey) return items_done -@dataclasses.dataclass(frozen=True) +def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: + return request.param + + +@dataclasses.dataclass class FuncFixtureInfo: - """Fixture-related information for a fixture-requesting item (e.g. test - function). + __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") - This is used to examine the fixtures which an item requests statically - (known during collection). This includes autouse fixtures, fixtures - requested by the `usefixtures` marker, fixtures requested in the function - parameters, and the transitive closure of these. - - An item may also request fixtures dynamically (using `request.getfixturevalue`); - these are not reflected here. - """ - - __slots__ = ("argnames", "initialnames", "name2fixturedefs", "names_closure") - - # Fixture names that the item requests directly by function parameters. - argnames: tuple[str, ...] - # Fixture names that the item immediately requires. These include - # argnames + fixture names specified via usefixtures and via autouse=True in - # fixture definitions. - initialnames: tuple[str, ...] - # The transitive closure of the fixture names that the item requires. - # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). - names_closure: list[str] - # A map from a fixture name in the transitive closure to the FixtureDefs - # matching the name which are applicable to this function. - # There may be multiple overriding fixtures with the same name. The - # sequence is ordered from furthest to closes to the function. - name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] + # Original function argument names. + argnames: Tuple[str, ...] + # Argnames that function immediately requires. These include argnames + + # fixture names specified via usefixtures and via autouse=True in fixture + # definitions. + initialnames: Tuple[str, ...] + names_closure: List[str] + name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] def prune_dependency_tree(self) -> None: """Recompute names_closure from initialnames and name2fixturedefs. @@ -342,11 +376,11 @@ class FuncFixtureInfo: tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure: set[str] = set() + closure: Set[str] = set() working_set = set(self.initialnames) while working_set: argname = working_set.pop() - # Argname may be something not included in the original names_closure, + # Argname may be smth not included in the original names_closure, # in which case we ignore it. This currently happens with pseudo # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'. # So they introduce the new dependency 'request' which might have @@ -359,34 +393,25 @@ class FuncFixtureInfo: self.names_closure[:] = sorted(closure, key=self.names_closure.index) -class FixtureRequest(abc.ABC): - """The type of the ``request`` fixture. +class FixtureRequest: + """A request for a fixture from a test or fixture function. - A request object gives access to the requesting test context and has a - ``param`` attribute in case the fixture is parametrized. + A request object gives access to the requesting test context and has + an optional ``param`` attribute in case the fixture is parametrized + indirectly. """ - def __init__( - self, - pyfuncitem: Function, - fixturename: str | None, - arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], - fixture_defs: dict[str, FixtureDef[Any]], - *, - _ispytest: bool = False, - ) -> None: + def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) + self._pyfuncitem = pyfuncitem #: Fixture for which this request is being performed. - self.fixturename: Final = fixturename - self._pyfuncitem: Final = pyfuncitem - # The FixtureDefs for each fixture name requested by this item. - # Starts from the statically-known fixturedefs resolved during - # collection. Dynamically requested fixtures (using - # `request.getfixturevalue("foo")`) are added dynamically. - self._arg2fixturedefs: Final = arg2fixturedefs - # The evaluated argnames so far, mapping to the FixtureDef they resolved - # to. - self._fixture_defs: Final = fixture_defs + self.fixturename: Optional[str] = None + self._scope = Scope.Function + self._fixture_defs: Dict[str, FixtureDef[Any]] = {} + fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo + self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() + self._arg2index: Dict[str, int] = {} + self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager # Notes on the type of `param`: # -`request.param` is only defined in parametrized fixtures, and will raise # AttributeError otherwise. Python typing has no notion of "undefined", so @@ -398,44 +423,61 @@ class FixtureRequest(abc.ABC): self.param: Any @property - def _fixturemanager(self) -> FixtureManager: - return self._pyfuncitem.session._fixturemanager - - @property - @abc.abstractmethod - def _scope(self) -> Scope: - raise NotImplementedError() - - @property - def scope(self) -> _ScopeName: + def scope(self) -> "_ScopeName": """Scope string, one of "function", "class", "module", "package", "session".""" return self._scope.value - @abc.abstractmethod - def _check_scope( - self, - requested_fixturedef: FixtureDef[object], - requested_scope: Scope, - ) -> None: - raise NotImplementedError() - @property - def fixturenames(self) -> list[str]: + def fixturenames(self) -> List[str]: """Names of all active fixtures in this request.""" - result = list(self._pyfuncitem.fixturenames) + result = list(self._pyfuncitem._fixtureinfo.names_closure) result.extend(set(self._fixture_defs).difference(result)) return result @property - @abc.abstractmethod def node(self): """Underlying collection node (depends on current request scope).""" - raise NotImplementedError() + scope = self._scope + if scope is Scope.Function: + # This might also be a non-function Item despite its attribute name. + node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem + elif scope is Scope.Package: + # FIXME: _fixturedef is not defined on FixtureRequest (this class), + # but on FixtureRequest (a subclass). + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] + else: + node = get_scope_node(self._pyfuncitem, scope) + if node is None and scope is Scope.Class: + # Fallback to function item itself. + node = self._pyfuncitem + assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( + scope, self._pyfuncitem + ) + return node + + def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]": + fixturedefs = self._arg2fixturedefs.get(argname, None) + if fixturedefs is None: + # We arrive here because of a dynamic call to + # getfixturevalue(argname) usage which was naturally + # not known at parsing/collection time. + assert self._pyfuncitem.parent is not None + parentid = self._pyfuncitem.parent.nodeid + fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) + # TODO: Fix this type ignore. Either add assert or adjust types. + # Can this be None here? + self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] + # fixturedefs list is immutable so we maintain a decreasing index. + index = self._arg2index.get(argname, 0) - 1 + if fixturedefs is None or (-index > len(fixturedefs)): + raise FixtureLookupError(argname, self) + self._arg2index[argname] = index + return fixturedefs[index] @property def config(self) -> Config: """The pytest config object associated with this request.""" - return self._pyfuncitem.config + return self._pyfuncitem.config # type: ignore[no-any-return] @property def function(self): @@ -458,25 +500,27 @@ class FixtureRequest(abc.ABC): @property def instance(self): """Instance (can be None) on which test function was collected.""" - if self.scope != "function": - return None - return getattr(self._pyfuncitem, "instance", None) + # unittest support hack, see _pytest.unittest.TestCaseFunction. + try: + return self._pyfuncitem._testcase + except AttributeError: + function = getattr(self, "function", None) + return getattr(function, "__self__", None) @property def module(self): """Python module object where the test function was collected.""" if self.scope not in ("function", "class", "module"): raise AttributeError(f"module not available in {self.scope}-scoped context") - mod = self._pyfuncitem.getparent(_pytest.python.Module) - assert mod is not None - return mod.obj + return self._pyfuncitem.getparent(_pytest.python.Module).obj @property def path(self) -> Path: """Path where the test function was collected.""" if self.scope not in ("function", "class", "module", "package"): raise AttributeError(f"path not available in {self.scope}-scoped context") - return self._pyfuncitem.path + # TODO: Remove ignore once _pyfuncitem is properly typed. + return self._pyfuncitem.path # type: ignore @property def keywords(self) -> MutableMapping[str, Any]: @@ -485,17 +529,17 @@ class FixtureRequest(abc.ABC): return node.keywords @property - def session(self) -> Session: + def session(self) -> "Session": """Pytest session object.""" - return self._pyfuncitem.session + return self._pyfuncitem.session # type: ignore[no-any-return] - @abc.abstractmethod def addfinalizer(self, finalizer: Callable[[], object]) -> None: """Add finalizer/teardown function to be called without arguments after the last test within the requesting test context finished execution.""" - raise NotImplementedError() + # XXX usually this method is shadowed by fixturedef specific ones. + self.node.addfinalizer(finalizer) - def applymarker(self, marker: str | MarkDecorator) -> None: + def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker @@ -506,13 +550,20 @@ class FixtureRequest(abc.ABC): """ self.node.add_marker(marker) - def raiseerror(self, msg: str | None) -> NoReturn: + def raiseerror(self, msg: Optional[str]) -> NoReturn: """Raise a FixtureLookupError exception. :param msg: An optional custom error message. """ - raise FixtureLookupError(None, self, msg) + raise self._fixturemanager.FixtureLookupError(None, self, msg) + + def _fillfixtures(self) -> None: + item = self._pyfuncitem + fixturenames = getattr(item, "fixturenames", self.fixturenames) + for argname in fixturenames: + if argname not in item.funcargs: + item.funcargs[argname] = self.getfixturevalue(argname) def getfixturevalue(self, argname: str) -> Any: """Dynamically run a named fixture function. @@ -531,11 +582,6 @@ class FixtureRequest(abc.ABC): :raises pytest.FixtureLookupError: If the given fixture could not be found. """ - # Note that in addition to the use case described in the docstring, - # getfixturevalue() is also called by pytest itself during item and fixture - # setup to evaluate the fixtures that are requested statically - # (using function parameters, autouse, etc). - fixturedef = self._get_active_fixturedef(argname) assert fixturedef.cached_result is not None, ( f'The fixture value for "{argname}" is not available. ' @@ -543,287 +589,220 @@ class FixtureRequest(abc.ABC): ) return fixturedef.cached_result[0] - def _iter_chain(self) -> Iterator[SubRequest]: - """Yield all SubRequests in the chain, from self up. - - Note: does *not* yield the TopRequest. - """ - current = self - while isinstance(current, SubRequest): - yield current - current = current._parent_request - - def _get_active_fixturedef(self, argname: str) -> FixtureDef[object]: - if argname == "request": - return RequestFixtureDef(self) - - # If we already finished computing a fixture by this name in this item, - # return it. - fixturedef = self._fixture_defs.get(argname) - if fixturedef is not None: - self._check_scope(fixturedef, fixturedef._scope) - return fixturedef - - # Find the appropriate fixturedef. - fixturedefs = self._arg2fixturedefs.get(argname, None) - if fixturedefs is None: - # We arrive here because of a dynamic call to - # getfixturevalue(argname) which was naturally - # not known at parsing/collection time. - fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem) - if fixturedefs is not None: - self._arg2fixturedefs[argname] = fixturedefs - # No fixtures defined with this name. - if fixturedefs is None: - raise FixtureLookupError(argname, self) - # The are no fixtures with this name applicable for the function. - if not fixturedefs: - raise FixtureLookupError(argname, self) - - # A fixture may override another fixture with the same name, e.g. a - # fixture in a module can override a fixture in a conftest, a fixture in - # a class can override a fixture in the module, and so on. - # An overriding fixture can request its own name (possibly indirectly); - # in this case it gets the value of the fixture it overrides, one level - # up. - # Check how many `argname`s deep we are, and take the next one. - # `fixturedefs` is sorted from furthest to closest, so use negative - # indexing to go in reverse. - index = -1 - for request in self._iter_chain(): - if request.fixturename == argname: - index -= 1 - # If already consumed all of the available levels, fail. - if -index > len(fixturedefs): - raise FixtureLookupError(argname, self) - fixturedef = fixturedefs[index] - - # Prepare a SubRequest object for calling the fixture. + def _get_active_fixturedef( + self, argname: str + ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: try: - callspec = self._pyfuncitem.callspec + return self._fixture_defs[argname] + except KeyError: + try: + fixturedef = self._getnextfixturedef(argname) + except FixtureLookupError: + if argname == "request": + cached_result = (self, [0], None) + return PseudoFixtureDef(cached_result, Scope.Function) + raise + # Remove indent to prevent the python3 exception + # from leaking into the call. + self._compute_fixture_value(fixturedef) + self._fixture_defs[argname] = fixturedef + return fixturedef + + def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + current = self + values: List[FixtureDef[Any]] = [] + while isinstance(current, SubRequest): + values.append(current._fixturedef) # type: ignore[has-type] + current = current._parent_request + values.reverse() + return values + + def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: + """Create a SubRequest based on "self" and call the execute method + of the given FixtureDef object. + + This will force the FixtureDef object to throw away any previous + results and compute a new fixture value, which will be stored into + the FixtureDef object itself. + """ + # prepare a subrequest object before calling fixture function + # (latter managed by fixturedef) + argname = fixturedef.argname + funcitem = self._pyfuncitem + scope = fixturedef._scope + try: + callspec = funcitem.callspec except AttributeError: callspec = None if callspec is not None and argname in callspec.params: param = callspec.params[argname] param_index = callspec.indices[argname] - # The parametrize invocation scope overrides the fixture's scope. - scope = callspec._arg2scope[argname] + # If a parametrize invocation set a scope it will override + # the static scope defined with the fixture function. + with suppress(KeyError): + scope = callspec._arg2scope[argname] else: param = NOTSET param_index = 0 - scope = fixturedef._scope - self._check_fixturedef_without_param(fixturedef) - # The parametrize invocation scope only controls caching behavior while - # allowing wider-scoped fixtures to keep depending on the parametrized - # fixture. Scope control is enforced for parametrized fixtures - # by recreating the whole fixture tree on parameter change. - # Hence `fixturedef._scope`, not `scope`. - self._check_scope(fixturedef, fixturedef._scope) + has_params = fixturedef.params is not None + fixtures_not_supported = getattr(funcitem, "nofuncargs", False) + if has_params and fixtures_not_supported: + msg = ( + "{name} does not support fixtures, maybe unittest.TestCase subclass?\n" + "Node id: {nodeid}\n" + "Function type: {typename}" + ).format( + name=funcitem.name, + nodeid=funcitem.nodeid, + typename=type(funcitem).__name__, + ) + fail(msg, pytrace=False) + if has_params: + frame = inspect.stack()[3] + frameinfo = inspect.getframeinfo(frame[0]) + source_path = absolutepath(frameinfo.filename) + source_lineno = frameinfo.lineno + try: + source_path_str = str( + source_path.relative_to(funcitem.config.rootpath) + ) + except ValueError: + source_path_str = str(source_path) + msg = ( + "The requested fixture has no parameter defined for test:\n" + " {}\n\n" + "Requested fixture '{}' defined in:\n{}" + "\n\nRequested here:\n{}:{}".format( + funcitem.nodeid, + fixturedef.argname, + getlocation(fixturedef.func, funcitem.config.rootpath), + source_path_str, + source_lineno, + ) + ) + fail(msg, pytrace=False) + subrequest = SubRequest( self, scope, param, param_index, fixturedef, _ispytest=True ) - # Make sure the fixture value is cached, running it if it isn't - fixturedef.execute(request=subrequest) + # Check if a higher-level scoped fixture accesses a lower level one. + subrequest._check_scope(argname, self._scope, scope) + try: + # Call the fixture function. + fixturedef.execute(request=subrequest) + finally: + self._schedule_finalizers(fixturedef, subrequest) - self._fixture_defs[argname] = fixturedef - return fixturedef - - def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: - """Check that this request is allowed to execute this fixturedef without - a param.""" - funcitem = self._pyfuncitem - has_params = fixturedef.params is not None - fixtures_not_supported = getattr(funcitem, "nofuncargs", False) - if has_params and fixtures_not_supported: - msg = ( - f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n" - f"Node id: {funcitem.nodeid}\n" - f"Function type: {type(funcitem).__name__}" - ) - fail(msg, pytrace=False) - if has_params: - frame = inspect.stack()[3] - frameinfo = inspect.getframeinfo(frame[0]) - source_path = absolutepath(frameinfo.filename) - source_lineno = frameinfo.lineno - try: - source_path_str = str(source_path.relative_to(funcitem.config.rootpath)) - except ValueError: - source_path_str = str(source_path) - location = getlocation(fixturedef.func, funcitem.config.rootpath) - msg = ( - "The requested fixture has no parameter defined for test:\n" - f" {funcitem.nodeid}\n\n" - f"Requested fixture '{fixturedef.argname}' defined in:\n" - f"{location}\n\n" - f"Requested here:\n" - f"{source_path_str}:{source_lineno}" - ) - fail(msg, pytrace=False) - - def _get_fixturestack(self) -> list[FixtureDef[Any]]: - values = [request._fixturedef for request in self._iter_chain()] - values.reverse() - return values - - -@final -class TopRequest(FixtureRequest): - """The type of the ``request`` fixture in a test function.""" - - def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: - super().__init__( - fixturename=None, - pyfuncitem=pyfuncitem, - arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(), - fixture_defs={}, - _ispytest=_ispytest, - ) - - @property - def _scope(self) -> Scope: - return Scope.Function + def _schedule_finalizers( + self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + ) -> None: + # If fixture function failed it might have registered finalizers. + subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest)) def _check_scope( self, - requested_fixturedef: FixtureDef[object], + argname: str, + invoking_scope: Scope, requested_scope: Scope, ) -> None: - # TopRequest always has function scope so always valid. - pass + if argname == "request": + return + if invoking_scope > requested_scope: + # Try to report something helpful. + text = "\n".join(self._factorytraceback()) + fail( + f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " + f"fixture {argname} with a {invoking_scope.value} scoped request object, " + f"involved factories:\n{text}", + pytrace=False, + ) - @property - def node(self): - return self._pyfuncitem + def _factorytraceback(self) -> List[str]: + lines = [] + for fixturedef in self._get_fixturestack(): + factory = fixturedef.func + fs, lineno = getfslineno(factory) + if isinstance(fs, Path): + session: Session = self._pyfuncitem.session + p = bestrelpath(session.path, fs) + else: + p = fs + args = _format_args(factory) + lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) + return lines def __repr__(self) -> str: - return f"" - - def _fillfixtures(self) -> None: - item = self._pyfuncitem - for argname in item.fixturenames: - if argname not in item.funcargs: - item.funcargs[argname] = self.getfixturevalue(argname) - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - self.node.addfinalizer(finalizer) + return "" % (self.node) @final class SubRequest(FixtureRequest): - """The type of the ``request`` fixture in a fixture function requested - (transitively) by a test function.""" + """A sub request for handling getting a fixture from a test function/fixture.""" def __init__( self, - request: FixtureRequest, + request: "FixtureRequest", scope: Scope, param: Any, param_index: int, - fixturedef: FixtureDef[object], + fixturedef: "FixtureDef[object]", *, _ispytest: bool = False, ) -> None: - super().__init__( - pyfuncitem=request._pyfuncitem, - fixturename=fixturedef.argname, - fixture_defs=request._fixture_defs, - arg2fixturedefs=request._arg2fixturedefs, - _ispytest=_ispytest, - ) - self._parent_request: Final[FixtureRequest] = request - self._scope_field: Final = scope - self._fixturedef: Final[FixtureDef[object]] = fixturedef + check_ispytest(_ispytest) + self._parent_request = request + self.fixturename = fixturedef.argname if param is not NOTSET: self.param = param - self.param_index: Final = param_index + self.param_index = param_index + self._scope = scope + self._fixturedef = fixturedef + self._pyfuncitem = request._pyfuncitem + self._fixture_defs = request._fixture_defs + self._arg2fixturedefs = request._arg2fixturedefs + self._arg2index = request._arg2index + self._fixturemanager = request._fixturemanager def __repr__(self) -> str: return f"" - @property - def _scope(self) -> Scope: - return self._scope_field - - @property - def node(self): - scope = self._scope - if scope is Scope.Function: - # This might also be a non-function Item despite its attribute name. - node: nodes.Node | None = self._pyfuncitem - elif scope is Scope.Package: - node = get_scope_package(self._pyfuncitem, self._fixturedef) - else: - node = get_scope_node(self._pyfuncitem, scope) - if node is None and scope is Scope.Class: - # Fallback to function item itself. - node = self._pyfuncitem - assert node, ( - f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}' - ) - return node - - def _check_scope( - self, - requested_fixturedef: FixtureDef[object], - requested_scope: Scope, - ) -> None: - if self._scope > requested_scope: - # Try to report something helpful. - argname = requested_fixturedef.argname - fixture_stack = "\n".join( - self._format_fixturedef_line(fixturedef) - for fixturedef in self._get_fixturestack() - ) - requested_fixture = self._format_fixturedef_line(requested_fixturedef) - fail( - f"ScopeMismatch: You tried to access the {requested_scope.value} scoped " - f"fixture {argname} with a {self._scope.value} scoped request object. " - f"Requesting fixture stack:\n{fixture_stack}\n" - f"Requested fixture:\n{requested_fixture}", - pytrace=False, - ) - - def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: - factory = fixturedef.func - path, lineno = getfslineno(factory) - if isinstance(path, Path): - path = bestrelpath(self._pyfuncitem.session.path, path) - sig = signature(factory) - return f"{path}:{lineno + 1}: def {factory.__name__}{sig}" - def addfinalizer(self, finalizer: Callable[[], object]) -> None: + """Add finalizer/teardown function to be called without arguments after + the last test within the requesting test context finished execution.""" self._fixturedef.addfinalizer(finalizer) + def _schedule_finalizers( + self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + ) -> None: + # If the executing fixturedef was not explicitly requested in the argument list (via + # getfixturevalue inside the fixture call) then ensure this fixture def will be finished + # first. + if fixturedef.argname not in self.fixturenames: + fixturedef.addfinalizer( + functools.partial(self._fixturedef.finish, request=self) + ) + super()._schedule_finalizers(fixturedef, subrequest) + @final class FixtureLookupError(LookupError): """Could not return a requested fixture (missing or invalid).""" def __init__( - self, argname: str | None, request: FixtureRequest, msg: str | None = None + self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() self.msg = msg - def formatrepr(self) -> FixtureLookupErrorRepr: - tblines: list[str] = [] + def formatrepr(self) -> "FixtureLookupErrorRepr": + tblines: List[str] = [] addline = tblines.append stack = [self.request._pyfuncitem.obj] stack.extend(map(lambda x: x.func, self.fixturestack)) msg = self.msg - # This function currently makes an assumption that a non-None msg means we - # have a non-empty `self.fixturestack`. This is currently true, but if - # somebody at some point want to extend the use of FixtureLookupError to - # new cases it might break. - # Add the assert to make it clearer to developer that this will fail, otherwise - # it crashes because `fspath` does not get set due to `stack` being empty. - assert self.msg is None or self.fixturestack, ( - "formatrepr assumptions broken, rewrite it to handle it" - ) if msg is not None: # The last fixture raise an error, let's present # it at the requesting side. @@ -846,15 +825,14 @@ class FixtureLookupError(LookupError): if msg is None: fm = self.request._fixturemanager available = set() - parent = self.request._pyfuncitem.parent - assert parent is not None + parentid = self.request._pyfuncitem.parent.nodeid for name, fixturedefs in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedefs, parent)) + faclist = list(fm._matchfactories(fixturedefs, parentid)) if faclist: available.add(name) if self.argname in available: - msg = ( - f" recursive dependency involving fixture '{self.argname}' detected" + msg = " recursive dependency involving fixture '{}' detected".format( + self.argname ) else: msg = f"fixture '{self.argname}' not found" @@ -867,11 +845,11 @@ class FixtureLookupError(LookupError): class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: str | os.PathLike[str], + filename: Union[str, "os.PathLike[str]"], firstlineno: int, tblines: Sequence[str], errorstring: str, - argname: str | None, + argname: Optional[str], ) -> None: self.tblines = tblines self.errorstring = errorstring @@ -895,14 +873,23 @@ class FixtureLookupErrorRepr(TerminalRepr): red=True, ) tw.line() - tw.line(f"{os.fspath(self.filename)}:{self.firstlineno + 1}") + tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1)) + + +def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn: + fs, lineno = getfslineno(fixturefunc) + location = f"{fs}:{lineno + 1}" + source = _pytest._code.Source(fixturefunc) + fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) def call_fixture_func( - fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs + fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs ) -> FixtureValue: - if inspect.isgeneratorfunction(fixturefunc): - fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc) + if is_generator(fixturefunc): + fixturefunc = cast( + Callable[..., Generator[FixtureValue, None, None]], fixturefunc + ) generator = fixturefunc(**kwargs) try: fixture_result = next(generator) @@ -925,65 +912,58 @@ def _teardown_yield_fixture(fixturefunc, it) -> None: except StopIteration: pass else: - fs, lineno = getfslineno(fixturefunc) - fail( - f"fixture function has more than one 'yield':\n\n" - f"{Source(fixturefunc).indent()}\n" - f"{fs}:{lineno + 1}", - pytrace=False, - ) + fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'") def _eval_scope_callable( - scope_callable: Callable[[str, Config], _ScopeName], + scope_callable: "Callable[[str, Config], _ScopeName]", fixture_name: str, config: Config, -) -> _ScopeName: +) -> "_ScopeName": try: # Type ignored because there is no typing mechanism to specify # keyword arguments, currently. result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] except Exception as e: raise TypeError( - f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n" - "Expected a function with the signature (*, fixture_name, config)" + "Error evaluating {} while defining fixture '{}'.\n" + "Expected a function with the signature (*, fixture_name, config)".format( + scope_callable, fixture_name + ) ) from e if not isinstance(result, str): fail( - f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n" - f"{result!r}", + "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n" + "{!r}".format(scope_callable, fixture_name, result), pytrace=False, ) return result +@final class FixtureDef(Generic[FixtureValue]): - """A container for a fixture definition. - - Note: At this time, only explicitly documented fields and methods are - considered public stable API. - """ + """A container for a fixture definition.""" def __init__( self, - config: Config, - baseid: str | None, + fixturemanager: "FixtureManager", + baseid: Optional[str], argname: str, - func: _FixtureFunc[FixtureValue], - scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, - params: Sequence[object] | None, - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, - *, - _ispytest: bool = False, - # only used in a deprecationwarning msg, can be removed in pytest9 - _autouse: bool = False, + func: "_FixtureFunc[FixtureValue]", + scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None], + params: Optional[Sequence[object]], + unittest: bool = False, + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None, ) -> None: - check_ispytest(_ispytest) + self._fixturemanager = fixturemanager # The "base" node ID for the fixture. # # This is a node ID prefix. A fixture is only available to a node (e.g. - # a `Function` item) if the fixture's baseid is a nodeid of a parent of - # node. + # a `Function` item) if the fixture's baseid is a parent of the node's + # nodeid (see the `iterparentnodeids` function for what constitutes a + # "parent" and a "prefix" in this context). # # For a fixture found in a Collector's object (e.g. a `Module`s module, # a `Class`'s class), the baseid is the Collector's nodeid. @@ -992,42 +972,43 @@ class FixtureDef(Generic[FixtureValue]): # directory path relative to the rootdir. # # For other plugins, the baseid is the empty string (always matches). - self.baseid: Final = baseid or "" + self.baseid = baseid or "" # Whether the fixture was found from a node or a conftest in the # collection tree. Will be false for fixtures defined in non-conftest # plugins. - self.has_location: Final = baseid is not None + self.has_location = baseid is not None # The fixture factory function. - self.func: Final = func + self.func = func # The name by which the fixture may be requested. - self.argname: Final = argname + self.argname = argname if scope is None: scope = Scope.Function elif callable(scope): - scope = _eval_scope_callable(scope, argname, config) + scope = _eval_scope_callable(scope, argname, fixturemanager.config) if isinstance(scope, str): scope = Scope.from_user( scope, descr=f"Fixture '{func.__name__}'", where=baseid ) - self._scope: Final = scope + self._scope = scope # If the fixture is directly parametrized, the parameter values. - self.params: Final = params + self.params: Optional[Sequence[object]] = params # If the fixture is directly parametrized, a tuple of explicit IDs to # assign to the parameter values, or a callable to generate an ID given # a parameter value. - self.ids: Final = ids + self.ids = ids # The names requested by the fixtures. - self.argnames: Final = getfuncargnames(func, name=argname) + self.argnames = getfuncargnames(func, name=argname, is_method=unittest) + # Whether the fixture was collected from a unittest TestCase class. + # Note that it really only makes sense to define autouse fixtures in + # unittest TestCases. + self.unittest = unittest # If the fixture was executed, the current value of the fixture. # Can change if the fixture is executed with different parameters. - self.cached_result: _FixtureCachedResult[FixtureValue] | None = None - self._finalizers: Final[list[Callable[[], object]]] = [] - - # only used to emit a deprecationwarning, can be removed in pytest9 - self._autouse = _autouse + self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None + self._finalizers: List[Callable[[], object]] = [] @property - def scope(self) -> _ScopeName: + def scope(self) -> "_ScopeName": """Scope string, one of "function", "class", "module", "package", "session".""" return self._scope.value @@ -1035,137 +1016,92 @@ class FixtureDef(Generic[FixtureValue]): self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - exceptions: list[BaseException] = [] - while self._finalizers: - fin = self._finalizers.pop() - try: - fin() - except BaseException as e: - exceptions.append(e) - node = request.node - node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) - # Even if finalization fails, we invalidate the cached fixture - # value and remove all finalizers because they may be bound methods - # which will keep instances alive. - self.cached_result = None - self._finalizers.clear() - if len(exceptions) == 1: - raise exceptions[0] - elif len(exceptions) > 1: - msg = f'errors while tearing down fixture "{self.argname}" of {node}' - raise BaseExceptionGroup(msg, exceptions[::-1]) + exc = None + try: + while self._finalizers: + try: + func = self._finalizers.pop() + func() + except BaseException as e: + # XXX Only first exception will be seen by user, + # ideally all should be reported. + if exc is None: + exc = e + if exc: + raise exc + finally: + ihook = request.node.ihook + ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request) + # Even if finalization fails, we invalidate the cached fixture + # value and remove all finalizers because they may be bound methods + # which will keep instances alive. + self.cached_result = None + self._finalizers = [] def execute(self, request: SubRequest) -> FixtureValue: - """Return the value of this fixture, executing it if not cached.""" - # Ensure that the dependent fixtures requested by this fixture are loaded. - # This needs to be done before checking if we have a cached value, since - # if a dependent fixture has their cache invalidated, e.g. due to - # parametrization, they finalize themselves and fixtures depending on it - # (which will likely include this fixture) setting `self.cached_result = None`. - # See #4871 - requested_fixtures_that_should_finalize_us = [] + # Get required arguments and register our own finish() + # with their finalization. for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) - # Saves requested fixtures in a list so we later can add our finalizer - # to them, ensuring that if a requested fixture gets torn down we get torn - # down first. This is generally handled by SetupState, but still currently - # needed when this fixture is not parametrized but depends on a parametrized - # fixture. - requested_fixtures_that_should_finalize_us.append(fixturedef) + if argname != "request": + # PseudoFixtureDef is only for "request". + assert isinstance(fixturedef, FixtureDef) + fixturedef.addfinalizer(functools.partial(self.finish, request=request)) - # Check for (and return) cached value/exception. + my_cache_key = self.cache_key(request) if self.cached_result is not None: - request_cache_key = self.cache_key(request) + # note: comparison with `==` can fail (or be expensive) for e.g. + # numpy arrays (#6497). cache_key = self.cached_result[1] - try: - # Attempt to make a normal == check: this might fail for objects - # which do not implement the standard comparison (like numpy arrays -- #6497). - cache_hit = bool(request_cache_key == cache_key) - except (ValueError, RuntimeError): - # If the comparison raises, use 'is' as fallback. - cache_hit = request_cache_key is cache_key - - if cache_hit: + if my_cache_key is cache_key: if self.cached_result[2] is not None: - exc, exc_tb = self.cached_result[2] - raise exc.with_traceback(exc_tb) + _, val, tb = self.cached_result[2] + raise val.with_traceback(tb) else: - return self.cached_result[0] + result = self.cached_result[0] + return result # We have a previous but differently parametrized fixture instance # so we need to tear it down before creating a new one. self.finish(request) assert self.cached_result is None - # Add finalizer to requested fixtures we saved previously. - # We make sure to do this after checking for cached value to avoid - # adding our finalizer multiple times. (#12135) - finalizer = functools.partial(self.finish, request=request) - for parent_fixture in requested_fixtures_that_should_finalize_us: - parent_fixture.addfinalizer(finalizer) - ihook = request.node.ihook - try: - # Setup the fixture, run the code in it, and cache the value - # in self.cached_result. - result: FixtureValue = ihook.pytest_fixture_setup( - fixturedef=self, request=request - ) - finally: - # Schedule our finalizer, even if the setup failed. - request.node.addfinalizer(finalizer) - + result = ihook.pytest_fixture_setup(fixturedef=self, request=request) return result def cache_key(self, request: SubRequest) -> object: - return getattr(request, "param", None) + return request.param_index if not hasattr(request, "param") else request.param def __repr__(self) -> str: - return f"" - - -class RequestFixtureDef(FixtureDef[FixtureRequest]): - """A custom FixtureDef for the special "request" fixture. - - A new one is generated on-demand whenever "request" is requested. - """ - - def __init__(self, request: FixtureRequest) -> None: - super().__init__( - config=request.config, - baseid=None, - argname="request", - func=lambda: request, - scope=Scope.Function, - params=None, - _ispytest=True, + return "".format( + self.argname, self.scope, self.baseid ) - self.cached_result = (request, [0], None) - - def addfinalizer(self, finalizer: Callable[[], object]) -> None: - pass def resolve_fixture_function( fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> _FixtureFunc[FixtureValue]: +) -> "_FixtureFunc[FixtureValue]": """Get the actual callable that can be called to obtain the fixture - value.""" + value, dealing with unittest-specific instances and bound methods.""" fixturefunc = fixturedef.func - # The fixture function needs to be bound to the actual - # request.instance so that code working with "fixturedef" behaves - # as expected. - instance = request.instance - if instance is not None: - # Handle the case where fixture is defined not in a test class, but some other class - # (for example a plugin class with a fixture), see #2270. - if hasattr(fixturefunc, "__self__") and not isinstance( - instance, - fixturefunc.__self__.__class__, - ): - return fixturefunc - fixturefunc = getimfunc(fixturedef.func) - if fixturefunc != fixturedef.func: - fixturefunc = fixturefunc.__get__(instance) + if fixturedef.unittest: + if request.instance is not None: + # Bind the unbound method to the TestCase instance. + fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] + else: + # The fixture function needs to be bound to the actual + # request.instance so that code working with "fixturedef" behaves + # as expected. + if request.instance is not None: + # Handle the case where fixture is defined not in a test class, but some other class + # (for example a plugin class with a fixture), see #2270. + if hasattr(fixturefunc, "__self__") and not isinstance( + request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] + ): + return fixturefunc + fixturefunc = getimfunc(fixturedef.func) + if fixturefunc != fixturedef.func: + fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] return fixturefunc @@ -1175,166 +1111,152 @@ def pytest_fixture_setup( """Execution of fixture setup.""" kwargs = {} for argname in fixturedef.argnames: - kwargs[argname] = request.getfixturevalue(argname) + fixdef = request._get_active_fixturedef(argname) + assert fixdef.cached_result is not None + result, arg_cache_key, exc = fixdef.cached_result + request._check_scope(argname, request._scope, fixdef._scope) + kwargs[argname] = result fixturefunc = resolve_fixture_function(fixturedef, request) my_cache_key = fixturedef.cache_key(request) - - if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction( - fixturefunc - ): - auto_str = " with autouse=True" if fixturedef._autouse else "" - - warnings.warn( - PytestRemovedIn9Warning( - f"{request.node.name!r} requested an async fixture " - f"{request.fixturename!r}{auto_str}, with no plugin or hook that " - "handled it. This is usually an error, as pytest does not natively " - "support it. " - "This will turn into an error in pytest 9.\n" - "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture" - ), - # no stacklevel will point at users code, so we just point here - stacklevel=1, - ) - try: result = call_fixture_func(fixturefunc, request, kwargs) - except TEST_OUTCOME as e: - if isinstance(e, skip.Exception): - # The test requested a fixture which caused a skip. - # Don't show the fixture as the skip location, as then the user - # wouldn't know which test skipped. - e._use_item_location = True - fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__)) + except TEST_OUTCOME: + exc_info = sys.exc_info() + assert exc_info[0] is not None + if isinstance( + exc_info[1], skip.Exception + ) and not fixturefunc.__name__.startswith("xunit_setup"): + exc_info[1]._use_item_location = True # type: ignore[attr-defined] + fixturedef.cached_result = (None, my_cache_key, exc_info) raise fixturedef.cached_result = (result, my_cache_key, None) return result +def _ensure_immutable_ids( + ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]] +) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]: + if ids is None: + return None + if callable(ids): + return ids + return tuple(ids) + + +def _params_converter( + params: Optional[Iterable[object]], +) -> Optional[Tuple[object, ...]]: + return tuple(params) if params is not None else None + + +def wrap_function_to_error_out_if_called_directly( + function: FixtureFunction, + fixture_marker: "FixtureFunctionMarker", +) -> FixtureFunction: + """Wrap the given fixture function so we can raise an error about it being called directly, + instead of used as an argument in a test function.""" + message = ( + 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' + "but are created automatically when test functions request them as parameters.\n" + "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n" + "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code." + ).format(name=fixture_marker.name or function.__name__) + + @functools.wraps(function) + def result(*args, **kwargs): + fail(message, pytrace=False) + + # Keep reference to the original function in our own custom attribute so we don't unwrap + # further than this point and lose useful wrappings like @mock.patch (#3774). + result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] + + return cast(FixtureFunction, result) + + @final @dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: - scope: _ScopeName | Callable[[str, Config], _ScopeName] - params: tuple[object, ...] | None + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" + params: Optional[Tuple[object, ...]] autouse: bool = False - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None - name: str | None = None + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None + name: Optional[str] = None _ispytest: dataclasses.InitVar[bool] = False def __post_init__(self, _ispytest: bool) -> None: check_ispytest(_ispytest) - def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition: + def __call__(self, function: FixtureFunction) -> FixtureFunction: if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") - if isinstance(function, FixtureFunctionDefinition): + if getattr(function, "_pytestfixturefunction", False): raise ValueError( - f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}" + "fixture is being applied more than once to the same function" ) - if hasattr(function, "pytestmark"): - warnings.warn(MARKED_FIXTURE, stacklevel=2) - - fixture_definition = FixtureFunctionDefinition( - function=function, fixture_function_marker=self, _ispytest=True - ) + function = wrap_function_to_error_out_if_called_directly(function, self) name = self.name or function.__name__ if name == "request": location = getlocation(function) fail( - f"'request' is a reserved word for fixtures, use another name:\n {location}", + "'request' is a reserved word for fixtures, use another name:\n {}".format( + location + ), pytrace=False, ) - return fixture_definition - - -# TODO: paramspec/return type annotation tracking and storing -class FixtureFunctionDefinition: - def __init__( - self, - *, - function: Callable[..., Any], - fixture_function_marker: FixtureFunctionMarker, - instance: object | None = None, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self.name = fixture_function_marker.name or function.__name__ - # In order to show the function that this fixture contains in messages. - # Set the __name__ to be same as the function __name__ or the given fixture name. - self.__name__ = self.name - self._fixture_function_marker = fixture_function_marker - if instance is not None: - self._fixture_function = cast( - Callable[..., Any], function.__get__(instance) - ) - else: - self._fixture_function = function - functools.update_wrapper(self, function) - - def __repr__(self) -> str: - return f"" - - def __get__(self, instance, owner=None): - """Behave like a method if the function it was applied to was a method.""" - return FixtureFunctionDefinition( - function=self._fixture_function, - fixture_function_marker=self._fixture_function_marker, - instance=instance, - _ispytest=True, - ) - - def __call__(self, *args: Any, **kwds: Any) -> Any: - message = ( - f'Fixture "{self.name}" called directly. Fixtures are not meant to be called directly,\n' - "but are created automatically when test functions request them as parameters.\n" - "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n" - "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly" - ) - fail(message, pytrace=False) - - def _get_wrapped_function(self) -> Callable[..., Any]: - return self._fixture_function + # Type ignored because https://github.com/python/mypy/issues/2087. + function._pytestfixturefunction = self # type: ignore[attr-defined] + return function @overload def fixture( - fixture_function: Callable[..., object], + fixture_function: FixtureFunction, *, - scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., - params: Iterable[object] | None = ..., + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., autouse: bool = ..., - ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., - name: str | None = ..., -) -> FixtureFunctionDefinition: ... + ids: Optional[ + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] + ] = ..., + name: Optional[str] = ..., +) -> FixtureFunction: + ... @overload -def fixture( +def fixture( # noqa: F811 fixture_function: None = ..., *, - scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., - params: Iterable[object] | None = ..., + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., autouse: bool = ..., - ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., - name: str | None = None, -) -> FixtureFunctionMarker: ... + ids: Optional[ + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] + ] = ..., + name: Optional[str] = None, +) -> FixtureFunctionMarker: + ... -def fixture( - fixture_function: FixtureFunction | None = None, +def fixture( # noqa: F811 + fixture_function: Optional[FixtureFunction] = None, *, - scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", - params: Iterable[object] | None = None, + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", + params: Optional[Iterable[object]] = None, autouse: bool = False, - ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, - name: str | None = None, -) -> FixtureFunctionMarker | FixtureFunctionDefinition: + ids: Optional[ + Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] + ] = None, + name: Optional[str] = None, +) -> Union[FixtureFunctionMarker, FixtureFunction]: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1435,7 +1357,7 @@ def pytestconfig(request: FixtureRequest) -> Config: Example:: def test_foo(pytestconfig): - if pytestconfig.get_verbosity() > 0: + if pytestconfig.getoption("verbose") > 0: ... """ @@ -1449,58 +1371,6 @@ def pytest_addoption(parser: Parser) -> None: default=[], help="List of default fixtures to be used with this project", ) - group = parser.getgroup("general") - group.addoption( - "--fixtures", - "--funcargs", - action="store_true", - dest="showfixtures", - default=False, - help="Show available fixtures, sorted by plugin appearance " - "(fixtures with leading '_' are only shown with '-v')", - ) - group.addoption( - "--fixtures-per-test", - action="store_true", - dest="show_fixtures_per_test", - default=False, - help="Show fixtures per test", - ) - - -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - if config.option.showfixtures: - showfixtures(config) - return 0 - if config.option.show_fixtures_per_test: - show_fixtures_per_test(config) - return 0 - return None - - -def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: - """Return all direct parametrization arguments of a node, so we don't - mistake them for fixtures. - - Check https://github.com/pytest-dev/pytest/issues/5036. - - These things are done later as well when dealing with parametrization - so this could be improved. - """ - parametrize_argnames: set[str] = set() - for marker in node.iter_markers(name="parametrize"): - if not marker.kwargs.get("indirect", False): - p_argnames, _ = ParameterSet._parse_parametrize_args( - *marker.args, **marker.kwargs - ) - parametrize_argnames.update(p_argnames) - return parametrize_argnames - - -def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: - """De-duplicate the sequence of names while keeping the original order.""" - # Ideally we would use a set, but it does not preserve insertion order. - return tuple(dict.fromkeys(name for seq in seqs for name in seq)) class FixtureManager: @@ -1520,7 +1390,7 @@ class FixtureManager: relevant for a particular function. An initial list of fixtures is assembled like this: - - config-defined usefixtures + - ini-defined usefixtures - autouse-marked fixtures along the collection chain up from the function - usefixtures markers at module/class/function level - test function funcargs @@ -1534,105 +1404,92 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - def __init__(self, session: Session) -> None: + FixtureLookupError = FixtureLookupError + FixtureLookupErrorRepr = FixtureLookupErrorRepr + + def __init__(self, session: "Session") -> None: self.session = session self.config: Config = session.config - # Maps a fixture name (argname) to all of the FixtureDefs in the test - # suite/plugins defined with this name. Populated by parsefactories(). - # TODO: The order of the FixtureDefs list of each arg is significant, - # explain. - self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} - self._holderobjseen: Final[set[object]] = set() + self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {} + self._holderobjseen: Set[object] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Final[dict[str, list[str]]] = { + self._nodeid_autousenames: Dict[str, List[str]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") - def getfixtureinfo( - self, - node: nodes.Item, - func: Callable[..., object] | None, - cls: type | None, - ) -> FuncFixtureInfo: - """Calculate the :class:`FuncFixtureInfo` for an item. + def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]: + """Return all direct parametrization arguments of a node, so we don't + mistake them for fixtures. - If ``func`` is None, or if the item sets an attribute - ``nofuncargs = True``, then ``func`` is not examined at all. + Check https://github.com/pytest-dev/pytest/issues/5036. - :param node: - The item requesting the fixtures. - :param func: - The item's function. - :param cls: - If the function is a method, the method's class. + These things are done later as well when dealing with parametrization + so this could be improved. """ - if func is not None and not getattr(node, "nofuncargs", False): + parametrize_argnames: List[str] = [] + for marker in node.iter_markers(name="parametrize"): + if not marker.kwargs.get("indirect", False): + p_argnames, _ = ParameterSet._parse_parametrize_args( + *marker.args, **marker.kwargs + ) + parametrize_argnames.extend(p_argnames) + + return parametrize_argnames + + def getfixtureinfo( + self, node: nodes.Node, func, cls, funcargs: bool = True + ) -> FuncFixtureInfo: + if funcargs and not getattr(node, "nofuncargs", False): argnames = getfuncargnames(func, name=node.name, cls=cls) else: argnames = () - usefixturesnames = self._getusefixturesnames(node) - autousenames = self._getautousenames(node) - initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) - direct_parametrize_args = _get_direct_parametrize_args(node) - - names_closure, arg2fixturedefs = self.getfixtureclosure( - parentnode=node, - initialnames=initialnames, - ignore_args=direct_parametrize_args, + usefixtures = tuple( + arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args + ) + initialnames = usefixtures + argnames + fm = node.session._fixturemanager + initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( + initialnames, node, ignore_args=self._get_direct_parametrize_args(node) ) - return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) - def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None: - # Fixtures defined in conftest plugins are only visible to within the - # conftest's directory. This is unlike fixtures in non-conftest plugins - # which have global visibility. So for conftests, construct the base - # nodeid from the plugin name (which is the conftest path). - if plugin_name and plugin_name.endswith("conftest.py"): - # Note: we explicitly do *not* use `plugin.__file__` here -- The - # difference is that plugin_name has the correct capitalization on - # case-insensitive systems (Windows) and other normalization issues - # (issue #11816). - conftestpath = absolutepath(plugin_name) - try: - nodeid = str(conftestpath.parent.relative_to(self.config.rootpath)) - except ValueError: - nodeid = "" - if nodeid == ".": - nodeid = "" - if os.sep != nodes.SEP: - nodeid = nodeid.replace(os.sep, nodes.SEP) + def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: + nodeid = None + try: + p = absolutepath(plugin.__file__) # type: ignore[attr-defined] + except AttributeError: + pass else: - nodeid = None + # Construct the base nodeid which is later used to check + # what fixtures are visible for particular tests (as denoted + # by their test id). + if p.name.startswith("conftest.py"): + try: + nodeid = str(p.parent.relative_to(self.config.rootpath)) + except ValueError: + nodeid = "" + if nodeid == ".": + nodeid = "" + if os.sep != nodes.SEP: + nodeid = nodeid.replace(os.sep, nodes.SEP) self.parsefactories(plugin, nodeid) - def _getautousenames(self, node: nodes.Node) -> Iterator[str]: - """Return the names of autouse fixtures applicable to node.""" - for parentnode in node.listchain(): - basenames = self._nodeid_autousenames.get(parentnode.nodeid) + def _getautousenames(self, nodeid: str) -> Iterator[str]: + """Return the names of autouse fixtures applicable to nodeid.""" + for parentnodeid in nodes.iterparentnodeids(nodeid): + basenames = self._nodeid_autousenames.get(parentnodeid) if basenames: yield from basenames - def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: - """Return the names of usefixtures fixtures applicable to node.""" - for marker_node, mark in node.iter_markers_with_node(name="usefixtures"): - if not mark.args: - marker_node.warn( - PytestWarning( - f"usefixtures() in {node.nodeid} without arguments has no effect" - ) - ) - yield from mark.args - def getfixtureclosure( self, + fixturenames: Tuple[str, ...], parentnode: nodes.Node, - initialnames: tuple[str, ...], - ignore_args: AbstractSet[str], - ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: + ignore_args: Sequence[str] = (), + ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]: # Collect the closure of all fixtures, starting with the given # fixturenames as the initial set. As we have to visit all # factory definitions anyway, we also return an arg2fixturedefs @@ -1640,47 +1497,34 @@ class FixtureManager: # to re-discover fixturedefs again for each fixturename # (discovering matching fixtures for a given name/node is expensive). - fixturenames_closure = list(initialnames) + parentid = parentnode.nodeid + fixturenames_closure = list(self._getautousenames(parentid)) - arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} + def merge(otherlist: Iterable[str]) -> None: + for arg in otherlist: + if arg not in fixturenames_closure: + fixturenames_closure.append(arg) - # Track the index for each fixture name in the simulated stack. - # Needed for handling override chains correctly, similar to _get_active_fixturedef. - # Using negative indices: -1 is the most specific (last), -2 is second to last, etc. - current_indices: dict[str, int] = {} + merge(fixturenames) - def process_argname(argname: str) -> None: - # Optimization: already processed this argname. - if current_indices.get(argname) == -1: - return + # At this point, fixturenames_closure contains what we call "initialnames", + # which is a set of fixturenames the function immediately requests. We + # need to return it as well, so save this. + initialnames = tuple(fixturenames_closure) - if argname not in fixturenames_closure: - fixturenames_closure.append(argname) - - if argname in ignore_args: - return - - fixturedefs = arg2fixturedefs.get(argname) - if not fixturedefs: - fixturedefs = self.getfixturedefs(argname, parentnode) - if not fixturedefs: - # Fixture not defined or not visible (will error during runtest). - return - arg2fixturedefs[argname] = fixturedefs - - index = current_indices.get(argname, -1) - if -index > len(fixturedefs): - # Exhausted the override chain (will error during runtest). - return - fixturedef = fixturedefs[index] - - current_indices[argname] = index - 1 - for dep in fixturedef.argnames: - process_argname(dep) - current_indices[argname] = index - - for name in initialnames: - process_argname(name) + arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} + lastlen = -1 + while lastlen != len(fixturenames_closure): + lastlen = len(fixturenames_closure) + for argname in fixturenames_closure: + if argname in ignore_args: + continue + if argname in arg2fixturedefs: + continue + fixturedefs = self.getfixturedefs(argname, parentid) + if fixturedefs: + arg2fixturedefs[argname] = fixturedefs + merge(fixturedefs[-1].argnames) def sort_by_scope(arg_name: str) -> Scope: try: @@ -1691,9 +1535,9 @@ class FixtureManager: return fixturedefs[-1]._scope fixturenames_closure.sort(key=sort_by_scope, reverse=True) - return fixturenames_closure, arg2fixturedefs + return initialnames, fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc: Metafunc) -> None: + def pytest_generate_tests(self, metafunc: "Metafunc") -> None: """Generate new tests based on parametrized fixtures used by the given metafunc""" def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: @@ -1738,85 +1582,35 @@ class FixtureManager: # Try next super fixture, if any. - def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: + def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: # Separate parametrized setups. items[:] = reorder_items(items) - def _register_fixture( - self, - *, - name: str, - func: _FixtureFunc[object], - nodeid: str | None, - scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function", - params: Sequence[object] | None = None, - ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, - autouse: bool = False, - ) -> None: - """Register a fixture - - :param name: - The fixture's name. - :param func: - The fixture's implementation function. - :param nodeid: - The visibility of the fixture. The fixture will be available to the - node with this nodeid and its children in the collection tree. - None means that the fixture is visible to the entire collection tree, - e.g. a fixture defined for general use in a plugin. - :param scope: - The fixture's scope. - :param params: - The fixture's parametrization params. - :param ids: - The fixture's IDs. - :param autouse: - Whether this is an autouse fixture. - """ - fixture_def = FixtureDef( - config=self.config, - baseid=nodeid, - argname=name, - func=func, - scope=scope, - params=params, - ids=ids, - _ispytest=True, - _autouse=autouse, - ) - - faclist = self._arg2fixturedefs.setdefault(name, []) - if fixture_def.has_location: - faclist.append(fixture_def) - else: - # fixturedefs with no location are at the front - # so this inserts the current fixturedef after the - # existing fixturedefs from external plugins but - # before the fixturedefs provided in conftests. - i = len([f for f in faclist if not f.has_location]) - faclist.insert(i, fixture_def) - if autouse: - self._nodeid_autousenames.setdefault(nodeid or "", []).append(name) - @overload def parsefactories( self, node_or_obj: nodes.Node, + *, + unittest: bool = ..., ) -> None: raise NotImplementedError() @overload - def parsefactories( + def parsefactories( # noqa: F811 self, node_or_obj: object, - nodeid: str | None, + nodeid: Optional[str], + *, + unittest: bool = ..., ) -> None: raise NotImplementedError() - def parsefactories( + def parsefactories( # noqa: F811 self, - node_or_obj: nodes.Node | object, - nodeid: str | NotSetType | None = NOTSET, + node_or_obj: Union[nodes.Node, object], + nodeid: Union[str, NotSetType, None] = NOTSET, + *, + unittest: bool = False, ) -> None: """Collect fixtures from a collection node or object. @@ -1824,7 +1618,7 @@ class FixtureManager: If `node_or_object` is a collection node (with an underlying Python object), the node's object is traversed and the node's nodeid is used to - determine the fixtures' visibility. `nodeid` must not be specified in + determine the fixtures' visibilty. `nodeid` must not be specified in this case. If `node_or_object` is an object (e.g. a plugin), the object is @@ -1842,206 +1636,78 @@ class FixtureManager: if holderobj in self._holderobjseen: return - # Avoid accessing `@property` (and other descriptors) when iterating fixtures. - if not safe_isclass(holderobj) and not isinstance(holderobj, types.ModuleType): - holderobj_tp: object = type(holderobj) - else: - holderobj_tp = holderobj - self._holderobjseen.add(holderobj) + autousenames = [] for name in dir(holderobj): + # ugly workaround for one of the fspath deprecated property of node + # todo: safely generalize + if isinstance(holderobj, nodes.Node) and name == "fspath": + continue + # The attribute can be an arbitrary descriptor, so the attribute - # access below can raise. safe_getattr() ignores such exceptions. - obj_ub = safe_getattr(holderobj_tp, name, None) - if type(obj_ub) is FixtureFunctionDefinition: - marker = obj_ub._fixture_function_marker - if marker.name: - fixture_name = marker.name - else: - fixture_name = name + # access below can raise. safe_getatt() ignores such exceptions. + obj = safe_getattr(holderobj, name, None) + marker = getfixturemarker(obj) + if not isinstance(marker, FixtureFunctionMarker): + # Magic globals with __getattr__ might have got us a wrong + # fixture attribute. + continue - # OK we know it is a fixture -- now safe to look up on the _instance_. - try: - obj = getattr(holderobj, name) - # if the fixture is named in the decorator we cannot find it in the module - except AttributeError: - obj = obj_ub + if marker.name: + name = marker.name - func = obj._get_wrapped_function() + # During fixture definition we wrap the original fixture function + # to issue a warning if called directly, so here we unwrap it in + # order to not emit the warning when pytest itself calls the + # fixture function. + obj = get_real_method(obj, holderobj) - self._register_fixture( - name=fixture_name, - nodeid=nodeid, - func=func, - scope=marker.scope, - params=marker.params, - ids=marker.ids, - autouse=marker.autouse, - ) + fixture_def = FixtureDef( + fixturemanager=self, + baseid=nodeid, + argname=name, + func=obj, + scope=marker.scope, + params=marker.params, + unittest=unittest, + ids=marker.ids, + ) + + faclist = self._arg2fixturedefs.setdefault(name, []) + if fixture_def.has_location: + faclist.append(fixture_def) + else: + # fixturedefs with no location are at the front + # so this inserts the current fixturedef after the + # existing fixturedefs from external plugins but + # before the fixturedefs provided in conftests. + i = len([f for f in faclist if not f.has_location]) + faclist.insert(i, fixture_def) + if marker.autouse: + autousenames.append(name) + + if autousenames: + self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames) def getfixturedefs( - self, argname: str, node: nodes.Node - ) -> Sequence[FixtureDef[Any]] | None: - """Get FixtureDefs for a fixture name which are applicable - to a given node. + self, argname: str, nodeid: str + ) -> Optional[Sequence[FixtureDef[Any]]]: + """Get a list of fixtures which are applicable to the given node id. - Returns None if there are no fixtures at all defined with the given - name. (This is different from the case in which there are fixtures - with the given name, but none applicable to the node. In this case, - an empty result is returned). - - :param argname: Name of the fixture to search for. - :param node: The requesting Node. + :param str argname: Name of the fixture to search for. + :param str nodeid: Full node id of the requesting test. + :rtype: Sequence[FixtureDef] """ try: fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None - return tuple(self._matchfactories(fixturedefs, node)) + return tuple(self._matchfactories(fixturedefs, nodeid)) def _matchfactories( - self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node + self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str ) -> Iterator[FixtureDef[Any]]: - parentnodeids = {n.nodeid for n in node.iter_parents()} + parentnodeids = set(nodes.iterparentnodeids(nodeid)) for fixturedef in fixturedefs: if fixturedef.baseid in parentnodeids: yield fixturedef - - -def show_fixtures_per_test(config: Config) -> int | ExitCode: - from _pytest.main import wrap_session - - return wrap_session(config, _show_fixtures_per_test) - - -_PYTEST_DIR = Path(_pytest.__file__).parent - - -def _pretty_fixture_path(invocation_dir: Path, func) -> str: - loc = Path(getlocation(func, invocation_dir)) - prefix = Path("...", "_pytest") - try: - return str(prefix / loc.relative_to(_PYTEST_DIR)) - except ValueError: - return bestrelpath(invocation_dir, loc) - - -def _show_fixtures_per_test(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - invocation_dir = config.invocation_params.dir - tw = _pytest.config.create_terminal_writer(config) - verbose = config.get_verbosity() - - def get_best_relpath(func) -> str: - loc = getlocation(func, invocation_dir) - return bestrelpath(invocation_dir, Path(loc)) - - def write_fixture(fixture_def: FixtureDef[object]) -> None: - argname = fixture_def.argname - if verbose <= 0 and argname.startswith("_"): - return - prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func) - tw.write(f"{argname}", green=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - fixture_doc = inspect.getdoc(fixture_def.func) - if fixture_doc: - write_docstring( - tw, - fixture_doc.split("\n\n", maxsplit=1)[0] - if verbose <= 0 - else fixture_doc, - ) - else: - tw.line(" no docstring available", red=True) - - def write_item(item: nodes.Item) -> None: - # Not all items have _fixtureinfo attribute. - info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None) - if info is None or not info.name2fixturedefs: - # This test item does not use any fixtures. - return - tw.line() - tw.sep("-", f"fixtures used by {item.name}") - # TODO: Fix this type ignore. - tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined] - # dict key not used in loop but needed for sorting. - for _, fixturedefs in sorted(info.name2fixturedefs.items()): - assert fixturedefs is not None - if not fixturedefs: - continue - # Last item is expected to be the one used by the test item. - write_fixture(fixturedefs[-1]) - - for session_item in session.items: - write_item(session_item) - - -def showfixtures(config: Config) -> int | ExitCode: - from _pytest.main import wrap_session - - return wrap_session(config, _showfixtures_main) - - -def _showfixtures_main(config: Config, session: Session) -> None: - import _pytest.config - - session.perform_collect() - invocation_dir = config.invocation_params.dir - tw = _pytest.config.create_terminal_writer(config) - verbose = config.get_verbosity() - - fm = session._fixturemanager - - available = [] - seen: set[tuple[str, str]] = set() - - for argname, fixturedefs in fm._arg2fixturedefs.items(): - assert fixturedefs is not None - if not fixturedefs: - continue - for fixturedef in fixturedefs: - loc = getlocation(fixturedef.func, invocation_dir) - if (fixturedef.argname, loc) in seen: - continue - seen.add((fixturedef.argname, loc)) - available.append( - ( - len(fixturedef.baseid), - fixturedef.func.__module__, - _pretty_fixture_path(invocation_dir, fixturedef.func), - fixturedef.argname, - fixturedef, - ) - ) - - available.sort() - currentmodule = None - for baseid, module, prettypath, argname, fixturedef in available: - if currentmodule != module: - if not module.startswith("_pytest."): - tw.line() - tw.sep("-", f"fixtures defined from {module}") - currentmodule = module - if verbose <= 0 and argname.startswith("_"): - continue - tw.write(f"{argname}", green=True) - if fixturedef.scope != "function": - tw.write(f" [{fixturedef.scope} scope]", cyan=True) - tw.write(f" -- {prettypath}", yellow=True) - tw.write("\n") - doc = inspect.getdoc(fixturedef.func) - if doc: - write_docstring( - tw, doc.split("\n\n", maxsplit=1)[0] if verbose <= 0 else doc - ) - else: - tw.line(" no docstring available", red=True) - tw.line() - - -def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: - for line in doc.split("\n"): - tw.line(indent + line) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/freeze_support.py b/Backend/venv/lib/python3.12/site-packages/_pytest/freeze_support.py index 959ff071..9f8ea231 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/freeze_support.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/freeze_support.py @@ -1,13 +1,12 @@ """Provides a function to report all internal modules for using freezing tools.""" - -from __future__ import annotations - -from collections.abc import Iterator import types +from typing import Iterator +from typing import List +from typing import Union -def freeze_includes() -> list[str]: +def freeze_includes() -> List[str]: """Return a list of module names used by pytest that should be included by cx_freeze.""" import _pytest @@ -17,7 +16,7 @@ def freeze_includes() -> list[str]: def _iter_all_modules( - package: str | types.ModuleType, + package: Union[str, types.ModuleType], prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given @@ -35,7 +34,7 @@ def _iter_all_modules( else: # Type ignored because typeshed doesn't define ModuleType.__path__ # (only defined on packages). - package_path = package.__path__ + package_path = package.__path__ # type: ignore[attr-defined] path, prefix = package_path[0], package.__name__ + "." for _, name, is_package in pkgutil.iter_modules([path]): if is_package: diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/helpconfig.py b/Backend/venv/lib/python3.12/site-packages/_pytest/helpconfig.py index 6a22c9f5..ea16c438 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/helpconfig.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/helpconfig.py @@ -1,58 +1,44 @@ -# mypy: allow-untyped-defs """Version info, help messages, tracing configuration.""" - -from __future__ import annotations - -import argparse -from collections.abc import Generator -from collections.abc import Sequence import os import sys -from typing import Any +from argparse import Action +from typing import List +from typing import Optional +from typing import Union +import pytest from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PrintHelp from _pytest.config.argparsing import Parser from _pytest.terminal import TerminalReporter -import pytest -class HelpAction(argparse.Action): - """An argparse Action that will raise a PrintHelp exception in order to skip - the rest of the argument parsing when --help is passed. +class HelpAction(Action): + """An argparse Action that will raise an exception in order to skip the + rest of the argument parsing when --help is passed. - This prevents argparse from raising UsageError when `--help` is used along - with missing required arguments when any are defined, for example by - ``pytest_addoption``. This is similar to the way that the builtin argparse - --help option is implemented by raising SystemExit. - - To opt in to this behavior, the parse caller must set - `namespace._raise_print_help = True`. Otherwise it just sets the option. + This prevents argparse from quitting due to missing required arguments + when any are defined, for example by ``pytest_addoption``. + This is similar to the way that the builtin argparse --help option is + implemented by raising SystemExit. """ - def __init__( - self, option_strings: Sequence[str], dest: str, *, help: str | None = None - ) -> None: + def __init__(self, option_strings, dest=None, default=False, help=None): super().__init__( option_strings=option_strings, dest=dest, - nargs=0, const=True, - default=False, + default=default, + nargs=0, help=help, ) - def __call__( - self, - parser: argparse.ArgumentParser, - namespace: argparse.Namespace, - values: str | Sequence[Any] | None, - option_string: str | None = None, - ) -> None: + def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) - if getattr(namespace, "_raise_print_help", False): + # We should only skip the rest of the parsing after preparse is done. + if getattr(parser._parser, "after_preparse", False): raise PrintHelp @@ -67,14 +53,14 @@ def pytest_addoption(parser: Parser) -> None: help="Display pytest version and information about plugins. " "When given twice, also display information about plugins.", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-h", "--help", action=HelpAction, dest="help", help="Show help message and configuration info", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-p", action="append", dest="plugins", @@ -82,14 +68,7 @@ def pytest_addoption(parser: Parser) -> None: metavar="name", help="Early-load given plugin module name or entry point (multi-allowed). " "To avoid loading of plugins, use the `no:` prefix, e.g. " - "`no:doctest`. See also --disable-plugin-autoload.", - ) - group.addoption( - "--disable-plugin-autoload", - action="store_true", - default=False, - help="Disable plugin auto-loading through entry point packaging metadata. " - "Only plugins explicitly specified in -p or env var PYTEST_PLUGINS will be loaded.", + "`no:doctest`.", ) group.addoption( "--traceconfig", @@ -109,78 +88,79 @@ def pytest_addoption(parser: Parser) -> None: "This file is opened with 'w' and truncated as a result, care advised. " "Default: pytestdebug.log.", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-o", "--override-ini", dest="override_ini", action="append", - help='Override configuration option with "option=value" style, ' - "e.g. `-o strict_xfail=True -o cache_dir=cache`.", + help='Override ini option with "option=value" style, ' + "e.g. `-o xfail_strict=True -o cache_dir=cache`.", ) -@pytest.hookimpl(wrapper=True) -def pytest_cmdline_parse() -> Generator[None, Config, Config]: - config = yield +@pytest.hookimpl(hookwrapper=True) +def pytest_cmdline_parse(): + outcome = yield + config: Config = outcome.get_result() if config.option.debug: # --debug | --debug was provided. path = config.option.debug debugfile = open(path, "w", encoding="utf-8") debugfile.write( - "versions pytest-{}, " - "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format( + "versions pytest-%s, " + "python-%s\ncwd=%s\nargs=%s\n\n" + % ( pytest.__version__, ".".join(map(str, sys.version_info)), - config.invocation_params.dir, os.getcwd(), config.invocation_params.args, ) ) config.trace.root.setwriter(debugfile.write) undo_tracing = config.pluginmanager.enable_tracing() - sys.stderr.write(f"writing pytest debug information to {path}\n") + sys.stderr.write("writing pytest debug information to %s\n" % path) def unset_tracing() -> None: debugfile.close() - sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n") + sys.stderr.write("wrote pytest debug information to %s\n" % debugfile.name) config.trace.root.setwriter(None) undo_tracing() config.add_cleanup(unset_tracing) - return config - -def show_version_verbose(config: Config) -> None: - """Show verbose pytest version installation, including plugins.""" - sys.stdout.write( - f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n" - ) - plugininfo = getpluginversioninfo(config) - if plugininfo: - for line in plugininfo: - sys.stdout.write(line + "\n") - - -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: - # Note: a single `--version` argument is handled directly by `Config.main()` to avoid starting up the entire - # pytest infrastructure just to display the version (#13574). +def showversion(config: Config) -> None: if config.option.version > 1: - show_version_verbose(config) - return ExitCode.OK + sys.stdout.write( + "This is pytest version {}, imported from {}\n".format( + pytest.__version__, pytest.__file__ + ) + ) + plugininfo = getpluginversioninfo(config) + if plugininfo: + for line in plugininfo: + sys.stdout.write(line + "\n") + else: + sys.stdout.write(f"pytest {pytest.__version__}\n") + + +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: + if config.option.version > 0: + showversion(config) + return 0 elif config.option.help: config._do_configure() showhelp(config) config._ensure_unconfigure() - return ExitCode.OK + return 0 return None def showhelp(config: Config) -> None: import textwrap - reporter: TerminalReporter | None = config.pluginmanager.get_plugin( + reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( "terminalreporter" ) assert reporter is not None @@ -188,20 +168,22 @@ def showhelp(config: Config) -> None: tw.write(config._parser.optparser.format_help()) tw.line() tw.line( - "[pytest] configuration options in the first " - "pytest.toml|pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" + "[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" ) tw.line() columns = tw.fullwidth # costly call indent_len = 24 # based on argparse's max_help_position=24 indent = " " * indent_len - for name in config._parser._inidict: - help, type, _default = config._parser._inidict[name] + for name in config._parser._ininames: + help, type, default = config._parser._inidict[name] + if type is None: + type = "string" if help is None: raise TypeError(f"help argument cannot be None for {name}") spec = f"{name} ({type}):" - tw.write(f" {spec}") + tw.write(" %s" % spec) spec_len = len(spec) if spec_len > (indent_len - 3): # Display help starting at a new line. @@ -229,19 +211,10 @@ def showhelp(config: Config) -> None: tw.line() tw.line("Environment variables:") vars = [ - ( - "CI", - "When set to a non-empty value, pytest knows it is running in a " - "CI process and does not truncate summary info", - ), - ("BUILD_NUMBER", "Equivalent to CI"), ("PYTEST_ADDOPTS", "Extra command line options"), ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"), - ("PYTEST_DEBUG_TEMPROOT", "Override the system temporary directory"), - ("PYTEST_THEME", "The Pygments style to use for code output"), - ("PYTEST_THEME_MODE", "Set the PYTEST_THEME to be either 'dark' or 'light'"), ] for name, help in vars: tw.line(f" {name:<24} {help}") @@ -258,13 +231,17 @@ def showhelp(config: Config) -> None: for warningreport in reporter.stats.get("warnings", []): tw.line("warning : " + warningreport.message, red=True) + return -def getpluginversioninfo(config: Config) -> list[str]: +conftest_options = [("pytest_plugins", "list of plugin names to load")] + + +def getpluginversioninfo(config: Config) -> List[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - lines.append("registered third-party plugins:") + lines.append("setuptools registered plugins:") for plugin, dist in plugininfo: loc = getattr(plugin, "__file__", repr(plugin)) content = f"{dist.project_name}-{dist.version} at {loc}" @@ -272,7 +249,7 @@ def getpluginversioninfo(config: Config) -> list[str]: return lines -def pytest_report_header(config: Config) -> list[str]: +def pytest_report_header(config: Config) -> List[str]: lines = [] if config.option.debug or config.option.traceconfig: lines.append(f"using: pytest-{pytest.__version__}") diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/hookspec.py b/Backend/venv/lib/python3.12/site-packages/_pytest/hookspec.py index c5bcc36a..1f7c368f 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/hookspec.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/hookspec.py @@ -1,33 +1,31 @@ -# mypy: allow-untyped-defs -# ruff: noqa: T100 """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" - -from __future__ import annotations - -from collections.abc import Mapping -from collections.abc import Sequence from pathlib import Path from typing import Any +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Tuple from typing import TYPE_CHECKING +from typing import Union from pluggy import HookspecMarker -from .deprecated import HOOK_LEGACY_PATH_ARG - +from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK if TYPE_CHECKING: import pdb - from typing import Literal import warnings + from typing_extensions import Literal - from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr - from _pytest.compat import LEGACY_PATH - from _pytest.config import _PluggyPlugin + from _pytest._code.code import ExceptionInfo from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager + from _pytest.config import _PluggyPlugin from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureDef from _pytest.fixtures import SubRequest @@ -44,6 +42,7 @@ if TYPE_CHECKING: from _pytest.runner import CallInfo from _pytest.terminal import TerminalReporter from _pytest.terminal import TestShortLogReport + from _pytest.compat import LEGACY_PATH hookspec = HookspecMarker("pytest") @@ -54,62 +53,51 @@ hookspec = HookspecMarker("pytest") @hookspec(historic=True) -def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: +def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: """Called at plugin registration time to allow adding new hooks via a call to - :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. + ``pluginmanager.add_hookspecs(module_or_class, prefix)``. - :param pluginmanager: The pytest plugin manager. + :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered. + This hook is incompatible with ``hookwrapper=True``. """ @hookspec(historic=True) def pytest_plugin_registered( - plugin: _PluggyPlugin, - plugin_name: str, - manager: PytestPluginManager, + plugin: "_PluggyPlugin", manager: "PytestPluginManager" ) -> None: """A new pytest plugin got registered. :param plugin: The plugin module or instance. - :param plugin_name: The name by which the plugin is registered. - :param manager: The pytest plugin manager. + :param pytest.PytestPluginManager manager: pytest plugin manager. .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered, once for each plugin registered thus far - (including itself!), and for all plugins thereafter when they are - registered. + This hook is incompatible with ``hookwrapper=True``. """ @hookspec(historic=True) -def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: - """Register argparse-style options and config-style config values, +def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: + """Register argparse-style options and ini-style config values, called once at the beginning of a test run. - :param parser: + .. note:: + + This function should be implemented only in plugins or ``conftest.py`` + files situated at the tests root directory due to how pytest + :ref:`discovers plugins during startup `. + + :param pytest.Parser parser: To add command line options, call :py:func:`parser.addoption(...) `. - To add config-file values call :py:func:`parser.addini(...) + To add ini-file values call :py:func:`parser.addini(...) `. - :param pluginmanager: - The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s - or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks + :param pytest.PytestPluginManager pluginmanager: + The pytest plugin manager, which can be used to install :py:func:`hookspec`'s + or :py:func:`hookimpl`'s and allow one plugin to call another plugin's hooks to change how command line options are added. Options can later be accessed through the @@ -119,39 +107,30 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None retrieve the value of a command line option. - :py:func:`config.getini(name) ` to retrieve - a value read from a configuration file. + a value read from an ini-style file. The config object is passed around on many internal objects via the ``.config`` attribute or can be retrieved as the ``pytestconfig`` fixture. .. note:: - This hook is incompatible with hook wrappers. - - Use in conftest plugins - ======================= - - If a conftest plugin implements this hook, it will be called immediately - when the conftest is registered. - - This hook is only called for :ref:`initial conftests `. + This hook is incompatible with ``hookwrapper=True``. """ @hookspec(historic=True) -def pytest_configure(config: Config) -> None: +def pytest_configure(config: "Config") -> None: """Allow plugins and conftest files to perform initial configuration. + This hook is called for every plugin and initial conftest file + after command line options have been parsed. + + After that, the hook is called for other conftest files as they are + imported. + .. note:: - This hook is incompatible with hook wrappers. + This hook is incompatible with ``hookwrapper=True``. - :param config: The pytest config object. - - Use in conftest plugins - ======================= - - This hook is called for every :ref:`initial conftest ` file - after command line options have been parsed. After that, the hook is called - for other conftest files as they are registered. + :param pytest.Config config: The pytest config object. """ @@ -163,61 +142,62 @@ def pytest_configure(config: Config) -> None: @hookspec(firstresult=True) def pytest_cmdline_parse( - pluginmanager: PytestPluginManager, args: list[str] -) -> Config | None: + pluginmanager: "PytestPluginManager", args: List[str] +) -> Optional["Config"]: """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. .. note:: - This hook is only called for plugin classes passed to the + This hook will only be called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to perform an in-process test run. :param pluginmanager: The pytest plugin manager. :param args: List of arguments passed on the command line. :returns: A pytest config object. - - Use in conftest plugins - ======================= - - This hook is not called for conftest files. """ -def pytest_load_initial_conftests( - early_config: Config, parser: Parser, args: list[str] -) -> None: - """Called to implement the loading of :ref:`initial conftest files - ` ahead of command line option parsing. +@hookspec(warn_on_impl=WARNING_CMDLINE_PREPARSE_HOOK) +def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: + """(**Deprecated**) modify command line arguments before option parsing. - :param early_config: The pytest config object. + This hook is considered deprecated and will be removed in a future pytest version. Consider + using :hook:`pytest_load_initial_conftests` instead. + + .. note:: + This hook will not be called for ``conftest.py`` files, only for setuptools plugins. + + :param config: The pytest config object. :param args: Arguments passed on the command line. - :param parser: To add command line options. - - Use in conftest plugins - ======================= - - This hook is not called for conftest files. """ @hookspec(firstresult=True) -def pytest_cmdline_main(config: Config) -> ExitCode | int | None: - """Called for performing the main command line action. - - The default implementation will invoke the configure hooks and - :hook:`pytest_runtestloop`. +def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: + """Called for performing the main command line action. The default + implementation will invoke the configure hooks and runtest_mainloop. Stops at first non-None result, see :ref:`firstresult`. :param config: The pytest config object. :returns: The exit code. + """ - Use in conftest plugins - ======================= - This hook is only called for :ref:`initial conftests `. +def pytest_load_initial_conftests( + early_config: "Config", parser: "Parser", args: List[str] +) -> None: + """Called to implement the loading of initial conftest files ahead + of command line option parsing. + + .. note:: + This hook will not be called for ``conftest.py`` files, only for setuptools plugins. + + :param early_config: The pytest config object. + :param args: Arguments passed on the command line. + :param parser: To add command line options. """ @@ -227,7 +207,7 @@ def pytest_cmdline_main(config: Config) -> ExitCode | int | None: @hookspec(firstresult=True) -def pytest_collection(session: Session) -> object | None: +def pytest_collection(session: "Session") -> Optional[object]: """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. @@ -260,65 +240,33 @@ def pytest_collection(session: Session) -> object | None: counter (and returns `None`). :param session: The pytest session object. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. """ def pytest_collection_modifyitems( - session: Session, config: Config, items: list[Item] + session: "Session", config: "Config", items: List["Item"] ) -> None: """Called after collection has been performed. May filter or re-order the items in-place. - When items are deselected (filtered out from ``items``), - the hook :hook:`pytest_deselected` must be called explicitly - with the deselected items to properly notify other plugins, - e.g. with ``config.hook.pytest_deselected(items=deselected_items)``. - :param session: The pytest session object. :param config: The pytest config object. :param items: List of item objects. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ -def pytest_collection_finish(session: Session) -> None: +def pytest_collection_finish(session: "Session") -> None: """Called after collection has been performed and modified. :param session: The pytest session object. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ -@hookspec( - firstresult=True, - warn_on_impl_args={ - "path": HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg="path", pathlib_path_arg="collection_path" - ), - }, -) +@hookspec(firstresult=True) def pytest_ignore_collect( - collection_path: Path, path: LEGACY_PATH, config: Config -) -> bool | None: - """Return ``True`` to ignore this path for collection. - - Return ``None`` to let other plugins ignore the path for collection. - - Returning ``False`` will forcefully *not* ignore this path for collection, - without giving a chance for other plugins to ignore this path. + collection_path: Path, path: "LEGACY_PATH", config: "Config" +) -> Optional[bool]: + """Return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. @@ -326,7 +274,6 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. :param collection_path: The path to analyze. - :type collection_path: pathlib.Path :param path: The path to analyze (deprecated). :param config: The pytest config object. @@ -334,151 +281,65 @@ def pytest_ignore_collect( The ``collection_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collection path, only - conftest files in parent directories of the collection path are consulted - (if the path is a directory, its own conftest file is *not* consulted - a - directory cannot ignore itself!). """ -@hookspec(firstresult=True) -def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: - """Create a :class:`~pytest.Collector` for the given directory, or None if - not relevant. - - .. versionadded:: 8.0 - - For best results, the returned collector should be a subclass of - :class:`~pytest.Directory`, but this is not required. - - The new node needs to have the specified ``parent`` as a parent. - - Stops at first non-None result, see :ref:`firstresult`. - - :param path: The path to analyze. - :type path: pathlib.Path - - See :ref:`custom directory collectors` for a simple example of use of this - hook. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collection path, only - conftest files in parent directories of the collection path are consulted - (if the path is a directory, its own conftest file is *not* consulted - a - directory cannot collect itself!). - """ - - -@hookspec( - warn_on_impl_args={ - "path": HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg="path", pathlib_path_arg="file_path" - ), - }, -) def pytest_collect_file( - file_path: Path, path: LEGACY_PATH, parent: Collector -) -> Collector | None: + file_path: Path, path: "LEGACY_PATH", parent: "Collector" +) -> "Optional[Collector]": """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. - For best results, the returned collector should be a subclass of - :class:`~pytest.File`, but this is not required. - The new node needs to have the specified ``parent`` as a parent. :param file_path: The path to analyze. - :type file_path: pathlib.Path :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 The ``file_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given file path, only - conftest files in parent directories of the file path are consulted. """ # logging hooks for collection -def pytest_collectstart(collector: Collector) -> None: +def pytest_collectstart(collector: "Collector") -> None: """Collector starts collecting. :param collector: The collector. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. """ -def pytest_itemcollected(item: Item) -> None: +def pytest_itemcollected(item: "Item") -> None: """We just collected a test item. :param item: The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_collectreport(report: CollectReport) -> None: +def pytest_collectreport(report: "CollectReport") -> None: """Collector finished collecting. :param report: The collect report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. """ -def pytest_deselected(items: Sequence[Item]) -> None: +def pytest_deselected(items: Sequence["Item"]) -> None: """Called for deselected test items, e.g. by keyword. - Note that this hook has two integration aspects for plugins: - - - it can be *implemented* to be notified of deselected items - - it must be *called* from :hook:`pytest_collection_modifyitems` - implementations when items are deselected (to properly notify other plugins). - May be called multiple times. :param items: The items. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. """ @hookspec(firstresult=True) -def pytest_make_collect_report(collector: Collector) -> CollectReport | None: +def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]": """Perform :func:`collector.collect() ` and return a :class:`~pytest.CollectReport`. @@ -486,13 +347,6 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport | None: :param collector: The collector. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories are - consulted. """ @@ -501,17 +355,10 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport | None: # ------------------------------------------------------------------------- -@hookspec( - firstresult=True, - warn_on_impl_args={ - "path": HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg="path", pathlib_path_arg="module_path" - ), - }, -) +@hookspec(firstresult=True) def pytest_pycollect_makemodule( - module_path: Path, path: LEGACY_PATH, parent -) -> Module | None: + module_path: Path, path: "LEGACY_PATH", parent +) -> Optional["Module"]: """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. @@ -521,7 +368,6 @@ def pytest_pycollect_makemodule( Stops at first non-None result, see :ref:`firstresult`. :param module_path: The path of the module to collect. - :type module_path: pathlib.Path :param path: The path of the module to collect (deprecated). .. versionchanged:: 7.0.0 @@ -529,20 +375,13 @@ def pytest_pycollect_makemodule( equivalent of the ``path`` parameter. The ``path`` parameter has been deprecated in favor of ``fspath``. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given parent collector, - only conftest files in the collector's directory and its parent directories - are consulted. """ @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> None | Item | Collector | list[Item | Collector]: + collector: Union["Module", "Class"], name: str, obj: object +) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: """Return a custom item/collector for a Python object in a module, or None. Stops at first non-None result, see :ref:`firstresult`. @@ -555,51 +394,32 @@ def pytest_pycollect_makeitem( The object. :returns: The created items/collectors. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given collector, only - conftest files in the collector's directory and its parent directories - are consulted. """ @hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: +def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. :param pyfuncitem: The function item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only - conftest files in the item's directory and its parent directories - are consulted. """ -def pytest_generate_tests(metafunc: Metafunc) -> None: +def pytest_generate_tests(metafunc: "Metafunc") -> None: """Generate (multiple) parametrized calls to a test function. :param metafunc: The :class:`~pytest.Metafunc` helper for the test function. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given function definition, - only conftest files in the functions's directory and its parent directories - are consulted. """ @hookspec(firstresult=True) -def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: +def pytest_make_parametrize_id( + config: "Config", val: object, argname: str +) -> Optional[str]: """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls, or None if the hook doesn't know about ``val``. @@ -610,12 +430,7 @@ def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str :param config: The pytest config object. :param val: The parametrized value. - :param argname: The automatic parameter name produced by pytest. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. + :param str argname: The automatic parameter name produced by pytest. """ @@ -625,7 +440,7 @@ def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str @hookspec(firstresult=True) -def pytest_runtestloop(session: Session) -> object | None: +def pytest_runtestloop(session: "Session") -> Optional[object]: """Perform the main runtest loop (after collection finished). The default hook implementation performs the runtest protocol for all items @@ -642,16 +457,13 @@ def pytest_runtestloop(session: Session) -> object | None: Stops at first non-None result, see :ref:`firstresult`. The return value is not used, but only stops further processing. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. """ @hookspec(firstresult=True) -def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: +def pytest_runtest_protocol( + item: "Item", nextitem: "Optional[Item]" +) -> Optional[object]: """Perform the runtest protocol for a single test item. The default runtest protocol is this (see individual hooks for full details): @@ -664,7 +476,7 @@ def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: - ``pytest_runtest_logreport(report)`` - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - Call phase, if the setup passed and the ``setuponly`` pytest option is not set: + - Call phase, if the the setup passed and the ``setuponly`` pytest option is not set: - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``) - ``report = pytest_runtest_makereport(item, call)`` - ``pytest_runtest_logreport(report)`` @@ -683,15 +495,12 @@ def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: Stops at first non-None result, see :ref:`firstresult`. The return value is not used, but only stops further processing. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. """ -def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: +def pytest_runtest_logstart( + nodeid: str, location: Tuple[str, Optional[int], str] +) -> None: """Called at the start of running the runtest protocol for a single item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. @@ -700,17 +509,11 @@ def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) :param location: A tuple of ``(filename, lineno, testname)`` where ``filename`` is a file path relative to ``config.rootpath`` and ``lineno`` is 0-based. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ def pytest_runtest_logfinish( - nodeid: str, location: tuple[str, int | None, str] + nodeid: str, location: Tuple[str, Optional[int], str] ) -> None: """Called at the end of running the runtest protocol for a single item. @@ -720,16 +523,10 @@ def pytest_runtest_logfinish( :param location: A tuple of ``(filename, lineno, testname)`` where ``filename`` is a file path relative to ``config.rootpath`` and ``lineno`` is 0-based. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_runtest_setup(item: Item) -> None: +def pytest_runtest_setup(item: "Item") -> None: """Called to perform the setup phase for a test item. The default implementation runs ``setup()`` on ``item`` and all of its @@ -739,32 +536,20 @@ def pytest_runtest_setup(item: Item) -> None: :param item: The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_runtest_call(item: Item) -> None: +def pytest_runtest_call(item: "Item") -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. :param item: The item. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: +def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: """Called to perform the teardown phase for a test item. The default implementation runs the finalizers and calls ``teardown()`` @@ -779,17 +564,13 @@ def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: scheduled). This argument is used to perform exact teardowns, i.e. calling just enough finalizers so that nextitem only needs to call setup functions. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ @hookspec(firstresult=True) -def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: +def pytest_runtest_makereport( + item: "Item", call: "CallInfo[None]" +) -> Optional["TestReport"]: """Called to create a :class:`~pytest.TestReport` for each of the setup, call and teardown runtest phases of a test item. @@ -799,63 +580,39 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | :param call: The :class:`~pytest.CallInfo` for the phase. Stops at first non-None result, see :ref:`firstresult`. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_runtest_logreport(report: TestReport) -> None: +def pytest_runtest_logreport(report: "TestReport") -> None: """Process the :class:`~pytest.TestReport` produced for each of the setup, call and teardown runtest phases of an item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ @hookspec(firstresult=True) def pytest_report_to_serializable( - config: Config, - report: CollectReport | TestReport, -) -> dict[str, Any] | None: + config: "Config", + report: Union["CollectReport", "TestReport"], +) -> Optional[Dict[str, Any]]: """Serialize the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON. :param config: The pytest config object. :param report: The report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. The exact details may depend - on the plugin which calls the hook. """ @hookspec(firstresult=True) def pytest_report_from_serializable( - config: Config, - data: dict[str, Any], -) -> CollectReport | TestReport | None: + config: "Config", + data: Dict[str, Any], +) -> Optional[Union["CollectReport", "TestReport"]]: """Restore a report object previously serialized with :hook:`pytest_report_to_serializable`. :param config: The pytest config object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. The exact details may depend - on the plugin which calls the hook. """ @@ -866,11 +623,11 @@ def pytest_report_from_serializable( @hookspec(firstresult=True) def pytest_fixture_setup( - fixturedef: FixtureDef[Any], request: SubRequest -) -> object | None: + fixturedef: "FixtureDef[Any]", request: "SubRequest" +) -> Optional[object]: """Perform fixture setup execution. - :param fixturedef: + :param fixturdef: The fixture definition object. :param request: The fixture request object. @@ -883,34 +640,20 @@ def pytest_fixture_setup( If the fixture function returns None, other implementations of this hook function will continue to be called, according to the behavior of the :ref:`firstresult` option. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given fixture, only - conftest files in the fixture scope's directory and its parent directories - are consulted. """ def pytest_fixture_post_finalizer( - fixturedef: FixtureDef[Any], request: SubRequest + fixturedef: "FixtureDef[Any]", request: "SubRequest" ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not ``None``). - :param fixturedef: + :param fixturdef: The fixture definition object. :param request: The fixture request object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given fixture, only - conftest files in the fixture scope's directory and its parent directories - are consulted. """ @@ -919,44 +662,29 @@ def pytest_fixture_post_finalizer( # ------------------------------------------------------------------------- -def pytest_sessionstart(session: Session) -> None: +def pytest_sessionstart(session: "Session") -> None: """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. :param session: The pytest session object. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. """ def pytest_sessionfinish( - session: Session, - exitstatus: int | ExitCode, + session: "Session", + exitstatus: Union[int, "ExitCode"], ) -> None: """Called after whole test run finished, right before returning the exit status to the system. :param session: The pytest session object. :param exitstatus: The status which pytest will return to the system. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. """ -def pytest_unconfigure(config: Config) -> None: +def pytest_unconfigure(config: "Config") -> None: """Called before test process is exited. :param config: The pytest config object. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. """ @@ -966,8 +694,8 @@ def pytest_unconfigure(config: Config) -> None: def pytest_assertrepr_compare( - config: Config, op: str, left: object, right: object -) -> list[str] | None: + config: "Config", op: str, left: object, right: object +) -> Optional[List[str]]: """Return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list @@ -979,16 +707,10 @@ def pytest_assertrepr_compare( :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`. :param left: The left operand. :param right: The right operand. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ -def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: +def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None: """Called whenever an assertion passes. .. versionadded:: 5.0 @@ -998,22 +720,13 @@ def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None and the pytest introspected assertion information is available in the `expl` string. - This hook must be explicitly enabled by the :confval:`enable_assertion_pass_hook` - configuration option: + This hook must be explicitly enabled by the ``enable_assertion_pass_hook`` + ini-file option: - .. tab:: toml + .. code-block:: ini - .. code-block:: toml - - [pytest] - enable_assertion_pass_hook = true - - .. tab:: ini - - .. code-block:: ini - - [pytest] - enable_assertion_pass_hook = true + [pytest] + enable_assertion_pass_hook=true You need to **clean the .pyc** files in your project directory and interpreter libraries when enabling this option, as assertions will require to be re-written. @@ -1022,12 +735,6 @@ def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None :param lineno: Line number of the assert statement. :param orig: String with the original assertion. :param expl: String with the assert explanation. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in the item's directory and its parent directories are consulted. """ @@ -1036,21 +743,13 @@ def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None # ------------------------------------------------------------------------- -@hookspec( - warn_on_impl_args={ - "startdir": HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg="startdir", pathlib_path_arg="start_path" - ), - }, -) def pytest_report_header( # type:ignore[empty-body] - config: Config, start_path: Path, startdir: LEGACY_PATH -) -> str | list[str]: + config: "Config", start_path: Path, startdir: "LEGACY_PATH" +) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param config: The pytest config object. :param start_path: The starting dir. - :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). .. note:: @@ -1060,31 +759,25 @@ def pytest_report_header( # type:ignore[empty-body] If you want to have your line(s) displayed first, use :ref:`trylast=True `. + .. note:: + + This function should be implemented only in plugins or ``conftest.py`` + files situated at the tests root directory due to how pytest + :ref:`discovers plugins during startup `. + .. versionchanged:: 7.0.0 The ``start_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``startdir`` parameter. The ``startdir`` parameter has been deprecated. - - Use in conftest plugins - ======================= - - This hook is only called for :ref:`initial conftests `. """ -@hookspec( - warn_on_impl_args={ - "startdir": HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg="startdir", pathlib_path_arg="start_path" - ), - }, -) def pytest_report_collectionfinish( # type:ignore[empty-body] - config: Config, + config: "Config", start_path: Path, - startdir: LEGACY_PATH, - items: Sequence[Item], -) -> str | list[str]: + startdir: "LEGACY_PATH", + items: Sequence["Item"], +) -> Union[str, List[str]]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -1094,7 +787,6 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] :param config: The pytest config object. :param start_path: The starting dir. - :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). :param items: List of pytest items that are going to be executed; this list should not be modified. @@ -1109,18 +801,13 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] The ``start_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``startdir`` parameter. The ``startdir`` parameter has been deprecated. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ @hookspec(firstresult=True) def pytest_report_teststatus( # type:ignore[empty-body] - report: CollectReport | TestReport, config: Config -) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: + report: Union["CollectReport", "TestReport"], config: "Config" +) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": """Return result-category, shortletter and verbose word for status reporting. @@ -1142,18 +829,13 @@ def pytest_report_teststatus( # type:ignore[empty-body] :returns: The test status. Stops at first non-None result, see :ref:`firstresult`. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ def pytest_terminal_summary( - terminalreporter: TerminalReporter, - exitstatus: ExitCode, - config: Config, + terminalreporter: "TerminalReporter", + exitstatus: "ExitCode", + config: "Config", ) -> None: """Add a section to terminal summary reporting. @@ -1163,26 +845,21 @@ def pytest_terminal_summary( .. versionadded:: 4.2 The ``config`` parameter. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ @hookspec(historic=True) def pytest_warning_recorded( - warning_message: warnings.WarningMessage, - when: Literal["config", "collect", "runtest"], + warning_message: "warnings.WarningMessage", + when: "Literal['config', 'collect', 'runtest']", nodeid: str, - location: tuple[str, int, str] | None, + location: Optional[Tuple[str, int, str]], ) -> None: """Process a warning captured by the internal pytest warnings plugin. :param warning_message: - The captured warning. This is the same object produced by :class:`warnings.catch_warnings`, - and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. + The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains + the same attributes as the parameters of :py:func:`warnings.showwarning`. :param when: Indicates when the warning was captured. Possible values: @@ -1192,8 +869,7 @@ def pytest_warning_recorded( * ``"runtest"``: during test execution. :param nodeid: - Full id of the item. Empty string for warnings that are not specific to - a particular node. + Full id of the item. :param location: When available, holds information about the execution context of the captured @@ -1201,13 +877,6 @@ def pytest_warning_recorded( when the execution context is at the module level. .. versionadded:: 6.0 - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. If the warning is specific to a - particular node, only conftest files in parent directories of the node are - consulted. """ @@ -1217,8 +886,8 @@ def pytest_warning_recorded( def pytest_markeval_namespace( # type:ignore[empty-body] - config: Config, -) -> dict[str, Any]: + config: "Config", +) -> Dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -1231,12 +900,6 @@ def pytest_markeval_namespace( # type:ignore[empty-body] :param config: The pytest config object. :returns: A dictionary of additional globals to add. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given item, only conftest - files in parent directories of the item are consulted. """ @@ -1246,9 +909,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body] def pytest_internalerror( - excrepr: ExceptionRepr, - excinfo: ExceptionInfo[BaseException], -) -> bool | None: + excrepr: "ExceptionRepr", + excinfo: "ExceptionInfo[BaseException]", +) -> Optional[bool]: """Called for internal errors. Return True to suppress the fallback handling of printing an @@ -1256,41 +919,31 @@ def pytest_internalerror( :param excrepr: The exception repr object. :param excinfo: The exception info. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ def pytest_keyboard_interrupt( - excinfo: ExceptionInfo[KeyboardInterrupt | Exit], + excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", ) -> None: """Called for keyboard interrupt. :param excinfo: The exception info. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ def pytest_exception_interact( - node: Item | Collector, - call: CallInfo[Any], - report: CollectReport | TestReport, + node: Union["Item", "Collector"], + call: "CallInfo[Any]", + report: Union["CollectReport", "TestReport"], ) -> None: """Called when an exception was raised which can potentially be interactively handled. May be called during collection (see :hook:`pytest_make_collect_report`), - in which case ``report`` is a :class:`~pytest.CollectReport`. + in which case ``report`` is a :class:`CollectReport`. May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), - in which case ``report`` is a :class:`~pytest.TestReport`. + in which case ``report`` is a :class:`TestReport`. This hook is not called if the exception that was raised is an internal exception like ``skip.Exception``. @@ -1301,16 +954,10 @@ def pytest_exception_interact( The call information. Contains the exception. :param report: The collection or test report. - - Use in conftest plugins - ======================= - - Any conftest file can implement this hook. For a given node, only conftest - files in parent directories of the node are consulted. """ -def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: +def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: """Called upon pdb.set_trace(). Can be used by plugins to take special action just before the python @@ -1318,15 +965,10 @@ def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: :param config: The pytest config object. :param pdb: The Pdb instance. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ -def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: +def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: """Called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python @@ -1334,9 +976,4 @@ def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: :param config: The pytest config object. :param pdb: The Pdb instance. - - Use in conftest plugins - ======================= - - Any conftest plugin can implement this hook. """ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/junitxml.py b/Backend/venv/lib/python3.12/site-packages/_pytest/junitxml.py index ae8d2b94..ed259e4c 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/junitxml.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/junitxml.py @@ -1,4 +1,3 @@ -# mypy: allow-untyped-defs """Report test results in JUnit-XML format, for use with Jenkins and build integration servers. @@ -7,16 +6,21 @@ Based on initial code from Ross Lawley. Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ - -from __future__ import annotations - -from collections.abc import Callable import functools import os import platform import re import xml.etree.ElementTree as ET +from datetime import datetime +from typing import Callable +from typing import Dict +from typing import List +from typing import Match +from typing import Optional +from typing import Tuple +from typing import Union +import pytest from _pytest import nodes from _pytest import timing from _pytest._code.code import ExceptionRepr @@ -28,7 +32,6 @@ from _pytest.fixtures import FixtureRequest from _pytest.reports import TestReport from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter -import pytest xml_key = StashKey["LogXML"]() @@ -45,18 +48,18 @@ def bin_xml_escape(arg: object) -> str: The idea is to escape visually for the user rather than for XML itself. """ - def repl(matchobj: re.Match[str]) -> str: + def repl(matchobj: Match[str]) -> str: i = ord(matchobj.group()) if i <= 0xFF: - return f"#x{i:02X}" + return "#x%02X" % i else: - return f"#x{i:04X}" + return "#x%04X" % i # The spec range of valid chars is: # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] # For an unknown(?) reason, we disallow #x7F (DEL) as well. illegal_xml_re = ( - "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]" + "[^\u0009\u000A\u000D\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]" ) return re.sub(illegal_xml_re, repl, str(arg)) @@ -71,10 +74,10 @@ def merge_family(left, right) -> None: left.update(result) -families = { # pylint: disable=dict-init-mutate - "_base": {"testcase": ["classname", "name"]}, - "_base_legacy": {"testcase": ["file", "line", "url"]}, -} +families = {} +families["_base"] = {"testcase": ["classname", "name"]} +families["_base_legacy"] = {"testcase": ["file", "line", "url"]} + # xUnit 1.x inherits legacy attributes. families["xunit1"] = families["_base"].copy() merge_family(families["xunit1"], families["_base_legacy"]) @@ -84,15 +87,15 @@ families["xunit2"] = families["_base"] class _NodeReporter: - def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: + def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: self.id = nodeid self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family self.duration = 0.0 - self.properties: list[tuple[str, str]] = [] - self.nodes: list[ET.Element] = [] - self.attrs: dict[str, str] = {} + self.properties: List[Tuple[str, str]] = [] + self.nodes: List[ET.Element] = [] + self.attrs: Dict[str, str] = {} def append(self, node: ET.Element) -> None: self.xml.add_stats(node.tag) @@ -104,7 +107,7 @@ class _NodeReporter: def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self) -> ET.Element | None: + def make_properties_node(self) -> Optional[ET.Element]: """Return a Junit node containing custom properties, if any.""" if self.properties: properties = ET.Element("properties") @@ -119,7 +122,7 @@ class _NodeReporter: classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs: dict[str, str] = { + attrs: Dict[str, str] = { "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], @@ -138,20 +141,20 @@ class _NodeReporter: # Filter out attributes not permitted by this test family. # Including custom attributes because they are not valid here. temp_attrs = {} - for key in self.attrs: + for key in self.attrs.keys(): if key in families[self.family]["testcase"]: temp_attrs[key] = self.attrs[key] self.attrs = temp_attrs def to_xml(self) -> ET.Element: - testcase = ET.Element("testcase", self.attrs, time=f"{self.duration:.3f}") + testcase = ET.Element("testcase", self.attrs, time="%.3f" % self.duration) properties = self.make_properties_node() if properties is not None: testcase.append(properties) testcase.extend(self.nodes) return testcase - def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: + def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None: node = ET.Element(tag, message=message) node.text = bin_xml_escape(data) self.append(node) @@ -196,7 +199,7 @@ class _NodeReporter: self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: assert report.longrepr is not None - reprcrash: ReprFileLocation | None = getattr( + reprcrash: Optional[ReprFileLocation] = getattr( report.longrepr, "reprcrash", None ) if reprcrash is not None: @@ -216,7 +219,9 @@ class _NodeReporter: def append_error(self, report: TestReport) -> None: assert report.longrepr is not None - reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) + reprcrash: Optional[ReprFileLocation] = getattr( + report.longrepr, "reprcrash", None + ) if reprcrash is not None: reason = reprcrash.message else: @@ -243,9 +248,7 @@ class _NodeReporter: skipreason = skipreason[9:] details = f"{filename}:{lineno}: {skipreason}" - skipped = ET.Element( - "skipped", type="pytest.skip", message=bin_xml_escape(skipreason) - ) + skipped = ET.Element("skipped", type="pytest.skip", message=skipreason) skipped.text = bin_xml_escape(details) self.append(skipped) self.write_captured_output(report) @@ -255,7 +258,7 @@ class _NodeReporter: self.__dict__.clear() # Type ignored because mypy doesn't like overriding a method. # Also the return value doesn't match... - self.to_xml = lambda: data # type: ignore[method-assign] + self.to_xml = lambda: data # type: ignore[assignment] def _warn_incompatibility_with_xunit2( @@ -268,7 +271,9 @@ def _warn_incompatibility_with_xunit2( if xml is not None and xml.family not in ("xunit1", "legacy"): request.node.warn( PytestWarning( - f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')" + "{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format( + fixture_name=fixture_name, family=xml.family + ) ) ) @@ -360,16 +365,17 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object] `pytest-xdist `__ plugin. See :issue:`7767` for details. """ + __tracebackhide__ = True def record_func(name: str, value: object) -> None: - """No-op function in case --junit-xml was not passed in the command-line.""" + """No-op function in case --junitxml was not passed in the command-line.""" __tracebackhide__ = True _check_record_param_type("name", name) xml = request.config.stash.get(xml_key, None) if xml is not None: - record_func = xml.add_global_property + record_func = xml.add_global_property # noqa return record_func @@ -444,7 +450,7 @@ def pytest_unconfigure(config: Config) -> None: config.pluginmanager.unregister(xml) -def mangle_test_address(address: str) -> list[str]: +def mangle_test_address(address: str) -> List[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") # Convert file path to dotted path. @@ -459,7 +465,7 @@ class LogXML: def __init__( self, logfile, - prefix: str | None, + prefix: Optional[str], suite_name: str = "pytest", logging: str = "no", report_duration: str = "total", @@ -474,15 +480,17 @@ class LogXML: self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats: dict[str, int] = dict.fromkeys( + self.stats: Dict[str, int] = dict.fromkeys( ["error", "passed", "failure", "skipped"], 0 ) - self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} - self.node_reporters_ordered: list[_NodeReporter] = [] - self.global_properties: list[tuple[str, str]] = [] + self.node_reporters: Dict[ + Tuple[Union[str, TestReport], object], _NodeReporter + ] = {} + self.node_reporters_ordered: List[_NodeReporter] = [] + self.global_properties: List[Tuple[str, str]] = [] # List of reports that failed on call but teardown is pending. - self.open_reports: list[TestReport] = [] + self.open_reports: List[TestReport] = [] self.cnt_double_fail_tests = 0 # Replaces convenience family with real family. @@ -501,8 +509,8 @@ class LogXML: if reporter is not None: reporter.finalize() - def node_reporter(self, report: TestReport | str) -> _NodeReporter: - nodeid: str | TestReport = getattr(report, "nodeid", report) + def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: + nodeid: Union[str, TestReport] = getattr(report, "nodeid", report) # Local hack to handle xdist report order. workernode = getattr(report, "node", None) @@ -616,7 +624,7 @@ class LogXML: def update_testcase_duration(self, report: TestReport) -> None: """Accumulate total duration for nodeid from given report and update the Junit.testcase with the new total if already created.""" - if self.report_duration in {"total", report.when}: + if self.report_duration == "total" or report.when == self.report_duration: reporter = self.node_reporter(report) reporter.duration += getattr(report, "duration", 0.0) @@ -634,7 +642,7 @@ class LogXML: reporter._add_simple("error", "internal error", str(excrepr)) def pytest_sessionstart(self) -> None: - self.suite_start = timing.Instant() + self.suite_start_time = timing.time() def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) @@ -642,7 +650,8 @@ class LogXML: os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: - duration = self.suite_start.elapsed() + suite_stop_time = timing.time() + suite_time_delta = suite_stop_time - self.suite_start_time numtests = ( self.stats["passed"] @@ -660,8 +669,8 @@ class LogXML: failures=str(self.stats["failure"]), skipped=str(self.stats["skipped"]), tests=str(numtests), - time=f"{duration.seconds:.3f}", - timestamp=self.suite_start.as_utc().astimezone().isoformat(), + time="%.3f" % suite_time_delta, + timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(), hostname=platform.node(), ) global_properties = self._get_global_properties_node() @@ -670,22 +679,18 @@ class LogXML: for node_reporter in self.node_reporters_ordered: suite_node.append(node_reporter.to_xml()) testsuites = ET.Element("testsuites") - testsuites.set("name", "pytest tests") testsuites.append(suite_node) logfile.write(ET.tostring(testsuites, encoding="unicode")) - def pytest_terminal_summary( - self, terminalreporter: TerminalReporter, config: pytest.Config - ) -> None: - if config.get_verbosity() >= 0: - terminalreporter.write_sep("-", f"generated xml file: {self.logfile}") + def pytest_terminal_summary(self, terminalreporter: TerminalReporter) -> None: + terminalreporter.write_sep("-", f"generated xml file: {self.logfile}") def add_global_property(self, name: str, value: object) -> None: __tracebackhide__ = True _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) - def _get_global_properties_node(self) -> ET.Element | None: + def _get_global_properties_node(self) -> Optional[ET.Element]: """Return a Junit node containing custom properties, if any.""" if self.global_properties: properties = ET.Element("properties") diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/legacypath.py b/Backend/venv/lib/python3.12/site-packages/_pytest/legacypath.py index 59e8ef6e..af1d0c07 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/legacypath.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/legacypath.py @@ -1,19 +1,17 @@ -# mypy: allow-untyped-defs """Add backward compatibility support for the legacy py path type.""" - -from __future__ import annotations - import dataclasses -from pathlib import Path import shlex import subprocess -from typing import Final -from typing import final +from pathlib import Path +from typing import List +from typing import Optional from typing import TYPE_CHECKING +from typing import Union from iniconfig import SectionWrapper from _pytest.cacheprovider import Cache +from _pytest.compat import final from _pytest.compat import LEGACY_PATH from _pytest.compat import legacy_path from _pytest.config import Config @@ -33,8 +31,9 @@ from _pytest.pytester import RunResult from _pytest.terminal import TerminalReporter from _pytest.tmpdir import TempPathFactory - if TYPE_CHECKING: + from typing_extensions import Final + import pexpect @@ -49,8 +48,8 @@ class Testdir: __test__ = False - CLOSE_STDIN: Final = Pytester.CLOSE_STDIN - TimeoutExpired: Final = Pytester.TimeoutExpired + CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN + TimeoutExpired: "Final" = Pytester.TimeoutExpired def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -90,6 +89,7 @@ class Testdir: return self._pytester.chdir() def finalize(self) -> None: + """See :meth:`Pytester._finalize`.""" return self._pytester._finalize() def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: @@ -144,7 +144,7 @@ class Testdir: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode(self, config: Config, arg) -> Item | Collector | None: + def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -152,7 +152,7 @@ class Testdir: """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems(self, colitems: list[Item | Collector]) -> list[Item]: + def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -204,7 +204,9 @@ class Testdir: source, configargs=configargs, withinit=withinit ) - def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: + def collect_by_name( + self, modcol: Collector, name: str + ) -> Optional[Union[Item, Collector]]: """See :meth:`Pytester.collect_by_name`.""" return self._pytester.collect_by_name(modcol, name) @@ -235,11 +237,13 @@ class Testdir: """See :meth:`Pytester.runpytest_subprocess`.""" return self._pytester.runpytest_subprocess(*args, timeout=timeout) - def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: + def spawn_pytest( + self, string: str, expect_timeout: float = 10.0 + ) -> "pexpect.spawn": """See :meth:`Pytester.spawn_pytest`.""" return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": """See :meth:`Pytester.spawn`.""" return self._pytester.spawn(cmd, expect_timeout=expect_timeout) @@ -266,7 +270,7 @@ class LegacyTestdirPlugin: @final @dataclasses.dataclass class TempdirFactory: - """Backward compatibility wrapper that implements ``py.path.local`` + """Backward compatibility wrapper that implements :class:`py.path.local` for :class:`TempPathFactory`. .. note:: @@ -285,11 +289,11 @@ class TempdirFactory: self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" + """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) def getbasetemp(self) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) @@ -304,11 +308,16 @@ class LegacyTmpdirPlugin: @staticmethod @fixture def tmpdir(tmp_path: Path) -> LEGACY_PATH: - """Return a temporary directory (as `legacy_path`_ object) - which is unique to each test function invocation. - The temporary directory is created as a subdirectory - of the base temporary directory, with configurable retention, - as discussed in :ref:`temporary directory location and retention`. + """Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. If + ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. + + The returned object is a `legacy_path`_ object. .. note:: These days, it is preferred to use ``tmp_path``. @@ -364,7 +373,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: Config) -> LEGACY_PATH | None: +def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. @@ -374,7 +383,7 @@ def Config_inifile(self: Config) -> LEGACY_PATH | None: return legacy_path(str(self.inipath)) if self.inipath else None -def Session_startdir(self: Session) -> LEGACY_PATH: +def Session_stardir(self: Session) -> LEGACY_PATH: """The path from which pytest was invoked. Prefer to use ``startpath`` which is a :class:`pathlib.Path`. @@ -384,7 +393,9 @@ def Session_startdir(self: Session) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): +def Config__getini_unknown_type( + self, name: str, type: str, value: Union[str, List[str]] +): if type == "pathlist": # TODO: This assert is probably not valid in all cases. assert self.inipath is not None @@ -427,7 +438,7 @@ def pytest_load_initial_conftests(early_config: Config) -> None: mp.setattr(Config, "inifile", property(Config_inifile), raising=False) # Add Session.startdir property. - mp.setattr(Session, "startdir", property(Session_startdir), raising=False) + mp.setattr(Session, "startdir", property(Session_stardir), raising=False) # Add pathlist configuration type. mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/logging.py b/Backend/venv/lib/python3.12/site-packages/_pytest/logging.py index e4fed579..9f2f1c79 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/logging.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/logging.py @@ -1,33 +1,31 @@ -# mypy: allow-untyped-defs """Access and control log capturing.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import Set as AbstractSet +import io +import logging +import os +import re from contextlib import contextmanager from contextlib import nullcontext from datetime import datetime from datetime import timedelta from datetime import timezone -import io from io import StringIO -import logging from logging import LogRecord -import os from pathlib import Path -import re -from types import TracebackType -from typing import final -from typing import Generic -from typing import Literal +from typing import AbstractSet +from typing import Dict +from typing import Generator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar +from typing import Union from _pytest import nodes from _pytest._io import TerminalWriter from _pytest.capture import CaptureManager +from _pytest.compat import final from _pytest.config import _strtobool from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -41,9 +39,10 @@ from _pytest.main import Session from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter - if TYPE_CHECKING: logging_StreamHandler = logging.StreamHandler[StringIO] + + from typing_extensions import Literal else: logging_StreamHandler = logging.StreamHandler @@ -51,7 +50,7 @@ DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" _ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m") caplog_handler_key = StashKey["LogCaptureHandler"]() -caplog_records_key = StashKey[dict[str, list[logging.LogRecord]]]() +caplog_records_key = StashKey[Dict[str, List[logging.LogRecord]]]() def _remove_ansi_escape_sequences(text: str) -> str: @@ -64,14 +63,13 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: + def formatTime(self, record: LogRecord, datefmt=None) -> str: if datefmt and "%f" in datefmt: ct = self.converter(record.created) tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) # Construct `datetime.datetime` object from `struct_time` # and msecs information from `record` - # Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861). - dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz) + dt = datetime(*ct[0:6], microsecond=round(record.msecs * 1000), tzinfo=tz) return dt.strftime(datefmt) # Use `logging.Formatter` for non-microsecond formats return super().formatTime(record, datefmt) @@ -96,7 +94,7 @@ class ColoredLevelFormatter(DatetimeFormatter): super().__init__(*args, **kwargs) self._terminalwriter = terminalwriter self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: dict[int, str] = {} + self._level_to_fmt_mapping: Dict[int, str] = {} for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): self.add_color_level(level, *color_opts) @@ -114,6 +112,7 @@ class ColoredLevelFormatter(DatetimeFormatter): .. warning:: This is an experimental API. """ + assert self._fmt is not None levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) if not levelname_fmt_match: @@ -144,12 +143,12 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: + def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: super().__init__(fmt) self._auto_indent = self._get_auto_indent(auto_indent) @staticmethod - def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: + def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: """Determine the current auto indentation setting. Specify auto indent behavior (on/off/fixed) by passing in @@ -180,6 +179,7 @@ class PercentStyleMultiline(logging.PercentStyle): 0 (auto-indent turned off) or >0 (explicitly set indentation position). """ + if auto_indent_option is None: return 0 elif isinstance(auto_indent_option, bool): @@ -206,7 +206,7 @@ class PercentStyleMultiline(logging.PercentStyle): if "\n" in record.message: if hasattr(record, "auto_indent"): # Passed in from the "extra={}" kwarg on the call to logging.log(). - auto_indent = self._get_auto_indent(record.auto_indent) + auto_indent = self._get_auto_indent(record.auto_indent) # type: ignore[attr-defined] else: auto_indent = self._auto_indent @@ -295,13 +295,6 @@ def pytest_addoption(parser: Parser) -> None: default=None, help="Path to a file when logging will be written to", ) - add_option_ini( - "--log-file-mode", - dest="log_file_mode", - default="w", - choices=["w", "a"], - help="Log file open mode", - ) add_option_ini( "--log-file-level", dest="log_file_level", @@ -311,13 +304,13 @@ def pytest_addoption(parser: Parser) -> None: add_option_ini( "--log-file-format", dest="log_file_format", - default=None, + default=DEFAULT_LOG_FORMAT, help="Log format used by the logging module", ) add_option_ini( "--log-file-date-format", dest="log_file_date_format", - default=None, + default=DEFAULT_LOG_DATE_FORMAT, help="Log date format used by the logging module", ) add_option_ini( @@ -339,16 +332,16 @@ _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) # Not using @contextmanager for performance reasons. -class catching_logs(Generic[_HandlerType]): +class catching_logs: """Context manager that prepares the whole logging machinery properly.""" __slots__ = ("handler", "level", "orig_level") - def __init__(self, handler: _HandlerType, level: int | None = None) -> None: + def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: self.handler = handler self.level = level - def __enter__(self) -> _HandlerType: + def __enter__(self): root_logger = logging.getLogger() if self.level is not None: self.handler.setLevel(self.level) @@ -358,12 +351,7 @@ class catching_logs(Generic[_HandlerType]): root_logger.setLevel(min(self.orig_level, self.level)) return self.handler - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: + def __exit__(self, type, value, traceback): root_logger = logging.getLogger() if self.level is not None: root_logger.setLevel(self.orig_level) @@ -376,7 +364,7 @@ class LogCaptureHandler(logging_StreamHandler): def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) - self.records: list[logging.LogRecord] = [] + self.records: List[logging.LogRecord] = [] def emit(self, record: logging.LogRecord) -> None: """Keep the log records in a list in addition to the log text.""" @@ -397,7 +385,7 @@ class LogCaptureHandler(logging_StreamHandler): # The default behavior of logging is to print "Logging error" # to stderr with the call stack and some extra details. # pytest wants to make such mistakes visible during testing. - raise # noqa: PLE0704 + raise @final @@ -407,10 +395,10 @@ class LogCaptureFixture: def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._item = item - self._initial_handler_level: int | None = None + self._initial_handler_level: Optional[int] = None # Dict of log name -> log level. - self._initial_logger_levels: dict[str | None, int] = {} - self._initial_disabled_logging_level: int | None = None + self._initial_logger_levels: Dict[Optional[str], int] = {} + self._initial_disabled_logging_level: Optional[int] = None def _finalize(self) -> None: """Finalize the fixture. @@ -434,8 +422,8 @@ class LogCaptureFixture: return self._item.stash[caplog_handler_key] def get_records( - self, when: Literal["setup", "call", "teardown"] - ) -> list[logging.LogRecord]: + self, when: "Literal['setup', 'call', 'teardown']" + ) -> List[logging.LogRecord]: """Get the logging records for one of the possible test phases. :param when: @@ -454,12 +442,12 @@ class LogCaptureFixture: return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self) -> list[logging.LogRecord]: + def records(self) -> List[logging.LogRecord]: """The list of log records.""" return self.handler.records @property - def record_tuples(self) -> list[tuple[str, int, str]]: + def record_tuples(self) -> List[Tuple[str, int, str]]: """A list of a stripped down version of log records intended for use in assertion comparison. @@ -470,7 +458,7 @@ class LogCaptureFixture: return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self) -> list[str]: + def messages(self) -> List[str]: """A list of format-interpolated log messages. Unlike 'records', which contains the format string and parameters for @@ -493,7 +481,7 @@ class LogCaptureFixture: self.handler.clear() def _force_enable_logging( - self, level: int | str, logger_obj: logging.Logger + self, level: Union[int, str], logger_obj: logging.Logger ) -> int: """Enable the desired logging level if the global level was disabled via ``logging.disabled``. @@ -509,7 +497,7 @@ class LogCaptureFixture: :return: The original disabled logging level. """ - original_disable_level: int = logger_obj.manager.disable + original_disable_level: int = logger_obj.manager.disable # type: ignore[attr-defined] if isinstance(level, str): # Try to translate the level string to an int for `logging.disable()` @@ -526,7 +514,7 @@ class LogCaptureFixture: return original_disable_level - def set_level(self, level: int | str, logger: str | None = None) -> None: + def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: """Set the threshold level of a logger for the duration of a test. Logging messages which are less severe than this level will not be captured. @@ -535,7 +523,7 @@ class LogCaptureFixture: The levels of the loggers changed by this function will be restored to their initial values at the end of the test. - Will enable the requested logging level if it was disabled via :func:`logging.disable`. + Will enable the requested logging level if it was disabled via :meth:`logging.disable`. :param level: The level. :param logger: The logger to update. If not given, the root logger. @@ -552,12 +540,14 @@ class LogCaptureFixture: self._initial_disabled_logging_level = initial_disabled_logging_level @contextmanager - def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]: + def at_level( + self, level: Union[int, str], logger: Optional[str] = None + ) -> Generator[None, None, None]: """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the level is restored to its original value. - Will enable the requested logging level if it was disabled via :func:`logging.disable`. + Will enable the requested logging level if it was disabled via :meth:`logging.disable`. :param level: The level. :param logger: The logger to update. If not given, the root logger. @@ -575,25 +565,9 @@ class LogCaptureFixture: self.handler.setLevel(handler_orig_level) logging.disable(original_disable_level) - @contextmanager - def filtering(self, filter_: logging.Filter) -> Generator[None]: - """Context manager that temporarily adds the given filter to the caplog's - :meth:`handler` for the 'with' statement block, and removes that filter at the - end of the block. - - :param filter_: A custom :class:`logging.Filter` object. - - .. versionadded:: 7.5 - """ - self.handler.addFilter(filter_) - try: - yield - finally: - self.handler.removeFilter(filter_) - @fixture -def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]: +def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: """Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -609,7 +583,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]: result._finalize() -def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: +def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: @@ -626,9 +600,9 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None except ValueError as e: # Python logging does not recognise this as a logging level raise UsageError( - f"'{log_level}' is not recognized as a logging level name for " - f"'{setting_name}'. Please consider passing the " - "logging level num instead." + "'{}' is not recognized as a logging level name for " + "'{}'. Please consider passing the " + "logging level num instead.".format(log_level, setting_name) ) from e @@ -662,19 +636,14 @@ class LoggingPlugin: self.report_handler.setFormatter(self.formatter) # File logging. - self.log_file_level = get_log_level_for_setting( - config, "log_file_level", "log_level" - ) + self.log_file_level = get_log_level_for_setting(config, "log_file_level") log_file = get_option_ini(config, "log_file") or os.devnull if log_file != os.devnull: directory = os.path.dirname(os.path.abspath(log_file)) if not os.path.isdir(directory): os.makedirs(directory) - self.log_file_mode = get_option_ini(config, "log_file_mode") or "w" - self.log_file_handler = _FileHandler( - log_file, mode=self.log_file_mode, encoding="UTF-8" - ) + self.log_file_handler = _FileHandler(log_file, mode="w", encoding="UTF-8") log_file_format = get_option_ini(config, "log_file_format", "log_format") log_file_date_format = get_option_ini( config, "log_file_date_format", "log_date_format" @@ -695,9 +664,9 @@ class LoggingPlugin: assert terminal_reporter is not None capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. - self.log_cli_handler: ( - _LiveLoggingStreamHandler | _LiveLoggingNullHandler - ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + self.log_cli_handler: Union[ + _LiveLoggingStreamHandler, _LiveLoggingNullHandler + ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) else: self.log_cli_handler = _LiveLoggingNullHandler() log_cli_formatter = self._create_formatter( @@ -708,7 +677,7 @@ class LoggingPlugin: self.log_cli_handler.setFormatter(log_cli_formatter) self._disable_loggers(loggers_to_disable=config.option.logger_disable) - def _disable_loggers(self, loggers_to_disable: list[str]) -> None: + def _disable_loggers(self, loggers_to_disable: List[str]) -> None: if not loggers_to_disable: return @@ -751,12 +720,12 @@ class LoggingPlugin: fpath.parent.mkdir(exist_ok=True, parents=True) # https://github.com/python/mypy/issues/11193 - stream: io.TextIOWrapper = fpath.open(mode=self.log_file_mode, encoding="UTF-8") # type: ignore[assignment] + stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment] old_stream = self.log_file_handler.setStream(stream) if old_stream: old_stream.close() - def _log_cli_enabled(self) -> bool: + def _log_cli_enabled(self): """Return whether live logging is enabled.""" enabled = self._config.getoption( "--log-cli-level" @@ -771,34 +740,35 @@ class LoggingPlugin: return True - @hookimpl(wrapper=True, tryfirst=True) - def pytest_sessionstart(self) -> Generator[None]: + @hookimpl(hookwrapper=True, tryfirst=True) + def pytest_sessionstart(self) -> Generator[None, None, None]: self.log_cli_handler.set_when("sessionstart") with catching_logs(self.log_cli_handler, level=self.log_cli_level): with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) + yield - @hookimpl(wrapper=True, tryfirst=True) - def pytest_collection(self) -> Generator[None]: + @hookimpl(hookwrapper=True, tryfirst=True) + def pytest_collection(self) -> Generator[None, None, None]: self.log_cli_handler.set_when("collection") with catching_logs(self.log_cli_handler, level=self.log_cli_level): with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) + yield - @hookimpl(wrapper=True) - def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]: + @hookimpl(hookwrapper=True) + def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]: if session.config.option.collectonly: - return (yield) + yield + return - if self._log_cli_enabled() and self._config.get_verbosity() < 1: + if self._log_cli_enabled() and self._config.getoption("verbose") < 1: # The verbose flag is needed to avoid messy test progress output. self._config.option.verbose = 1 with catching_logs(self.log_cli_handler, level=self.log_cli_level): with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) # Run all the tests. + yield # Run all the tests. @hookimpl def pytest_runtest_logstart(self) -> None: @@ -809,68 +779,58 @@ class LoggingPlugin: def pytest_runtest_logreport(self) -> None: self.log_cli_handler.set_when("logreport") - @contextmanager - def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None]: + def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]: """Implement the internals of the pytest_runtest_xxx() hooks.""" - with ( - catching_logs( - self.caplog_handler, - level=self.log_level, - ) as caplog_handler, - catching_logs( - self.report_handler, - level=self.log_level, - ) as report_handler, - ): + with catching_logs( + self.caplog_handler, + level=self.log_level, + ) as caplog_handler, catching_logs( + self.report_handler, + level=self.log_level, + ) as report_handler: caplog_handler.reset() report_handler.reset() item.stash[caplog_records_key][when] = caplog_handler.records item.stash[caplog_handler_key] = caplog_handler - try: - yield - finally: - log = report_handler.stream.getvalue().strip() - item.add_report_section(when, "log", log) + yield - @hookimpl(wrapper=True) - def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None]: + log = report_handler.stream.getvalue().strip() + item.add_report_section(when, "log", log) + + @hookimpl(hookwrapper=True) + def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("setup") - empty: dict[str, list[logging.LogRecord]] = {} + empty: Dict[str, List[logging.LogRecord]] = {} item.stash[caplog_records_key] = empty - with self._runtest_for(item, "setup"): - yield + yield from self._runtest_for(item, "setup") - @hookimpl(wrapper=True) - def pytest_runtest_call(self, item: nodes.Item) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("call") - with self._runtest_for(item, "call"): - yield + yield from self._runtest_for(item, "call") - @hookimpl(wrapper=True) - def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("teardown") - try: - with self._runtest_for(item, "teardown"): - yield - finally: - del item.stash[caplog_records_key] - del item.stash[caplog_handler_key] + yield from self._runtest_for(item, "teardown") + del item.stash[caplog_records_key] + del item.stash[caplog_handler_key] @hookimpl def pytest_runtest_logfinish(self) -> None: self.log_cli_handler.set_when("finish") - @hookimpl(wrapper=True, tryfirst=True) - def pytest_sessionfinish(self) -> Generator[None]: + @hookimpl(hookwrapper=True, tryfirst=True) + def pytest_sessionfinish(self) -> Generator[None, None, None]: self.log_cli_handler.set_when("sessionfinish") with catching_logs(self.log_cli_handler, level=self.log_cli_level): with catching_logs(self.log_file_handler, level=self.log_file_level): - return (yield) + yield @hookimpl def pytest_unconfigure(self) -> None: @@ -903,7 +863,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): def __init__( self, terminal_reporter: TerminalReporter, - capture_manager: CaptureManager | None, + capture_manager: Optional[CaptureManager], ) -> None: super().__init__(stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager @@ -915,7 +875,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when: str | None) -> None: + def set_when(self, when: Optional[str]) -> None: """Prepare for the given test phase (setup/call/teardown).""" self._when = when self._section_name_shown = False diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/main.py b/Backend/venv/lib/python3.12/site-packages/_pytest/main.py index 9bc930df..ea89a63f 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/main.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/main.py @@ -1,63 +1,79 @@ """Core implementation of the testing process: init, session, runtest loop.""" - -from __future__ import annotations - import argparse -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Sequence -from collections.abc import Set as AbstractSet import dataclasses import fnmatch import functools import importlib -import importlib.util import os -from pathlib import Path import sys -from typing import final -from typing import Literal -from typing import overload +from pathlib import Path +from typing import Callable +from typing import Dict +from typing import FrozenSet +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING -import warnings +from typing import Union -import pluggy - -from _pytest import nodes import _pytest._code +from _pytest import nodes +from _pytest.compat import final +from _pytest.compat import overload from _pytest.config import Config from _pytest.config import directory_arg from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import PytestPluginManager from _pytest.config import UsageError -from _pytest.config.argparsing import OverrideIniAction from _pytest.config.argparsing import Parser -from _pytest.config.compat import PathAwareHookProxy +from _pytest.fixtures import FixtureManager from _pytest.outcomes import exit from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import safe_exists -from _pytest.pathlib import samefile_nofollow -from _pytest.pathlib import scandir +from _pytest.pathlib import visit from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.runner import collect_one_node from _pytest.runner import SetupState -from _pytest.warning_types import PytestWarning if TYPE_CHECKING: - from typing_extensions import Self - - from _pytest.fixtures import FixtureManager + from typing_extensions import Literal def pytest_addoption(parser: Parser) -> None: - group = parser.getgroup("general") - group._addoption( # private to use reserved lower-case short option + parser.addini( + "norecursedirs", + "Directory patterns to avoid for recursion", + type="args", + default=[ + "*.egg", + ".*", + "_darcs", + "build", + "CVS", + "dist", + "node_modules", + "venv", + "{arch}", + ], + ) + parser.addini( + "testpaths", + "Directories to search for tests when no files or directories are given on the " + "command line", + type="args", + default=[], + ) + group = parser.getgroup("general", "Running and selection options") + group._addoption( "-x", "--exitfirst", action="store_const", @@ -65,60 +81,6 @@ def pytest_addoption(parser: Parser) -> None: const=1, help="Exit instantly on first error or failed test", ) - group.addoption( - "--maxfail", - metavar="num", - action="store", - type=int, - dest="maxfail", - default=0, - help="Exit after first num failures or errors", - ) - group.addoption( - "--strict-config", - action=OverrideIniAction, - ini_option="strict_config", - ini_value="true", - help="Enables the strict_config option", - ) - group.addoption( - "--strict-markers", - action=OverrideIniAction, - ini_option="strict_markers", - ini_value="true", - help="Enables the strict_markers option", - ) - group.addoption( - "--strict", - action=OverrideIniAction, - ini_option="strict", - ini_value="true", - help="Enables the strict option", - ) - parser.addini( - "strict_config", - "Any warnings encountered while parsing the `pytest` section of the " - "configuration file raise errors", - type="bool", - # None => fallback to `strict`. - default=None, - ) - parser.addini( - "strict_markers", - "Markers not registered in the `markers` section of the configuration " - "file raise errors", - type="bool", - # None => fallback to `strict`. - default=None, - ) - parser.addini( - "strict", - "Enables all strictness options, currently: " - "strict_config, strict_markers, strict_xfail, strict_parametrization_ids", - type="bool", - default=False, - ) - group = parser.getgroup("pytest-warnings") group.addoption( "-W", @@ -133,6 +95,56 @@ def pytest_addoption(parser: Parser) -> None: "warnings.filterwarnings. " "Processed after -W/--pythonwarnings.", ) + group._addoption( + "--maxfail", + metavar="num", + action="store", + type=int, + dest="maxfail", + default=0, + help="Exit after first num failures or errors", + ) + group._addoption( + "--strict-config", + action="store_true", + help="Any warnings encountered while parsing the `pytest` section of the " + "configuration file raise errors", + ) + group._addoption( + "--strict-markers", + action="store_true", + help="Markers not registered in the `markers` section of the configuration " + "file raise errors", + ) + group._addoption( + "--strict", + action="store_true", + help="(Deprecated) alias to --strict-markers", + ) + group._addoption( + "-c", + "--config-file", + metavar="FILE", + type=str, + dest="inifilename", + help="Load configuration from `FILE` instead of trying to locate one of the " + "implicit configuration files.", + ) + group._addoption( + "--continue-on-collection-errors", + action="store_true", + default=False, + dest="continue_on_collection_errors", + help="Force test execution even if collection errors occur", + ) + group._addoption( + "--rootdir", + action="store", + dest="rootdir", + help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', " + "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: " + "'$HOME/root_dir'.", + ) group = parser.getgroup("collect", "collection") group.addoption( @@ -195,13 +207,6 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Don't ignore tests in a local virtualenv directory", ) - group.addoption( - "--continue-on-collection-errors", - action="store_true", - default=False, - dest="continue_on_collection_errors", - help="Force test execution even if collection errors occur", - ) group.addoption( "--import-mode", default="prepend", @@ -210,60 +215,8 @@ def pytest_addoption(parser: Parser) -> None: help="Prepend/append to sys.path when importing test modules and conftest " "files. Default: prepend.", ) - parser.addini( - "norecursedirs", - "Directory patterns to avoid for recursion", - type="args", - default=[ - "*.egg", - ".*", - "_darcs", - "build", - "CVS", - "dist", - "node_modules", - "venv", - "{arch}", - ], - ) - parser.addini( - "testpaths", - "Directories to search for tests when no files or directories are given on the " - "command line", - type="args", - default=[], - ) - parser.addini( - "collect_imported_tests", - "Whether to collect tests in imported modules outside `testpaths`", - type="bool", - default=True, - ) - parser.addini( - "consider_namespace_packages", - type="bool", - default=False, - help="Consider namespace packages when resolving module names during import", - ) group = parser.getgroup("debugconfig", "test session debugging and configuration") - group._addoption( # private to use reserved lower-case short option - "-c", - "--config-file", - metavar="FILE", - type=str, - dest="inifilename", - help="Load configuration from `FILE` instead of trying to locate one of the " - "implicit configuration files.", - ) - group.addoption( - "--rootdir", - action="store", - dest="rootdir", - help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', " - "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: " - "'$HOME/root_dir'.", - ) group.addoption( "--basetemp", dest="basetemp", @@ -303,8 +256,8 @@ def validate_basetemp(path: str) -> str: def wrap_session( - config: Config, doit: Callable[[Config, Session], int | ExitCode | None] -) -> int | ExitCode: + config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] +) -> Union[int, ExitCode]: """Skeleton command line program.""" session = Session.from_config(config) session.exitstatus = ExitCode.OK @@ -323,7 +276,7 @@ def wrap_session( session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: int | ExitCode = ExitCode.INTERRUPTED + exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -361,11 +314,11 @@ def wrap_session( return session.exitstatus -def pytest_cmdline_main(config: Config) -> int | ExitCode: +def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: return wrap_session(config, _main) -def _main(config: Config, session: Session) -> int | ExitCode | None: +def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: """Default command line protocol for initialization, session, running tests and reporting.""" config.hook.pytest_collection(session=session) @@ -378,14 +331,15 @@ def _main(config: Config, session: Session) -> int | ExitCode | None: return None -def pytest_collection(session: Session) -> None: +def pytest_collection(session: "Session") -> None: session.perform_collect() -def pytest_runtestloop(session: Session) -> bool: +def pytest_runtestloop(session: "Session") -> bool: if session.testsfailed and not session.config.option.continue_on_collection_errors: raise session.Interrupted( - f"{session.testsfailed} error{'s' if session.testsfailed != 1 else ''} during collection" + "%d error%s during collection" + % (session.testsfailed, "s" if session.testsfailed != 1 else "") ) if session.config.option.collectonly: @@ -403,31 +357,27 @@ def pytest_runtestloop(session: Session) -> bool: def _in_venv(path: Path) -> bool: """Attempt to detect if ``path`` is the root of a Virtual Environment by - checking for the existence of the pyvenv.cfg file. - - [https://peps.python.org/pep-0405/] - - For regression protection we also check for conda environments that do not include pyenv.cfg yet -- - https://github.com/conda/conda/issues/13337 is the conda issue tracking adding pyenv.cfg. - - Checking for the `conda-meta/history` file per https://github.com/pytest-dev/pytest/issues/12652#issuecomment-2246336902. - - """ + checking for the existence of the appropriate activate script.""" + bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin") try: - return ( - path.joinpath("pyvenv.cfg").is_file() - or path.joinpath("conda-meta", "history").is_file() - ) + if not bindir.is_dir(): + return False except OSError: return False + activates = ( + "activate", + "activate.csh", + "activate.fish", + "Activate", + "Activate.bat", + "Activate.ps1", + ) + return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: - if collection_path.name == "__pycache__": - return True - +def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: ignore_paths = config._getconftest_pathlist( - "collect_ignore", path=collection_path.parent + "collect_ignore", path=collection_path.parent, rootpath=config.rootpath ) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") @@ -438,7 +388,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: return True ignore_globs = config._getconftest_pathlist( - "collect_ignore_glob", path=collection_path.parent + "collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath ) ignore_globs = ignore_globs or [] excludeglobopt = config.getoption("ignore_glob") @@ -460,13 +410,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: return None -def pytest_collect_directory( - path: Path, parent: nodes.Collector -) -> nodes.Collector | None: - return Dir.from_parent(parent, path=path) - - -def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: +def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return @@ -485,15 +429,11 @@ def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> No class FSHookProxy: - def __init__( - self, - pm: PytestPluginManager, - remove_mods: AbstractSet[object], - ) -> None: + def __init__(self, pm: PytestPluginManager, remove_mods) -> None: self.pm = pm self.remove_mods = remove_mods - def __getattr__(self, name: str) -> pluggy.HookCaller: + def __getattr__(self, name: str): x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) self.__dict__[name] = x return x @@ -510,7 +450,7 @@ class Failed(Exception): @dataclasses.dataclass -class _bestrelpath_cache(dict[Path, str]): +class _bestrelpath_cache(Dict[Path, str]): __slots__ = ("path",) path: Path @@ -522,59 +462,7 @@ class _bestrelpath_cache(dict[Path, str]): @final -class Dir(nodes.Directory): - """Collector of files in a file system directory. - - .. versionadded:: 8.0 - - .. note:: - - Python directories with an `__init__.py` file are instead collected by - :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` - collectors. - """ - - @classmethod - def from_parent( # type: ignore[override] - cls, - parent: nodes.Collector, - *, - path: Path, - ) -> Self: - """The public constructor. - - :param parent: The parent collector of this Dir. - :param path: The directory's path. - :type path: pathlib.Path - """ - return super().from_parent(parent=parent, path=path) - - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - config = self.config - col: nodes.Collector | None - cols: Sequence[nodes.Collector] - ihook = self.ihook - for direntry in scandir(self.path): - if direntry.is_dir(): - path = Path(direntry.path) - if not self.session.isinitpath(path, with_parents=True): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - col = ihook.pytest_collect_directory(path=path, parent=self) - if col is not None: - yield col - - elif direntry.is_file(): - path = Path(direntry.path) - if not self.session.isinitpath(path): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - cols = ihook.pytest_collect_file(file_path=path, parent=self) - yield from cols - - -@final -class Session(nodes.Collector): +class Session(nodes.FSCollector): """The root of the collection tree. ``Session`` collects the initial paths given as arguments to pytest. @@ -586,11 +474,10 @@ class Session(nodes.Collector): _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. _fixturemanager: FixtureManager - exitstatus: int | ExitCode + exitstatus: Union[int, ExitCode] def __init__(self, config: Config) -> None: super().__init__( - name="", path=config.rootpath, fspath=None, parent=None, @@ -600,68 +487,28 @@ class Session(nodes.Collector): ) self.testsfailed = 0 self.testscollected = 0 - self._shouldstop: bool | str = False - self._shouldfail: bool | str = False + self.shouldstop: Union[bool, str] = False + self.shouldfail: Union[bool, str] = False self.trace = config.trace.root.get("collection") - self._initialpaths: frozenset[Path] = frozenset() - self._initialpaths_with_parents: frozenset[Path] = frozenset() - self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: list[CollectionArgument] = [] - self._collection_cache: dict[nodes.Collector, CollectReport] = {} - self.items: list[nodes.Item] = [] + self._initialpaths: FrozenSet[Path] = frozenset() - self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) + self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) self.config.pluginmanager.register(self, name="session") @classmethod - def from_config(cls, config: Config) -> Session: + def from_config(cls, config: Config) -> "Session": session: Session = cls._create(config=config) return session def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} {self.name} " - f"exitstatus=%r " - f"testsfailed={self.testsfailed} " - f"testscollected={self.testscollected}>" - ) % getattr(self, "exitstatus", "") - - @property - def shouldstop(self) -> bool | str: - return self._shouldstop - - @shouldstop.setter - def shouldstop(self, value: bool | str) -> None: - # The runner checks shouldfail and assumes that if it is set we are - # definitely stopping, so prevent unsetting it. - if value is False and self._shouldstop: - warnings.warn( - PytestWarning( - "session.shouldstop cannot be unset after it has been set; ignoring." - ), - stacklevel=2, - ) - return - self._shouldstop = value - - @property - def shouldfail(self) -> bool | str: - return self._shouldfail - - @shouldfail.setter - def shouldfail(self, value: bool | str) -> None: - # The runner checks shouldfail and assumes that if it is set we are - # definitely stopping, so prevent unsetting it. - if value is False and self._shouldfail: - warnings.warn( - PytestWarning( - "session.shouldfail cannot be unset after it has been set; ignoring." - ), - stacklevel=2, - ) - return - self._shouldfail = value + return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % ( + self.__class__.__name__, + self.name, + getattr(self, "exitstatus", ""), + self.testsfailed, + self.testscollected, + ) @property def startpath(self) -> Path: @@ -683,100 +530,92 @@ class Session(nodes.Collector): raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: + def pytest_runtest_logreport( + self, report: Union[TestReport, CollectReport] + ) -> None: if report.failed and not hasattr(report, "wasxfail"): self.testsfailed += 1 maxfail = self.config.getvalue("maxfail") if maxfail and self.testsfailed >= maxfail: - self.shouldfail = f"stopping after {self.testsfailed} failures" + self.shouldfail = "stopping after %d failures" % (self.testsfailed) pytest_collectreport = pytest_runtest_logreport - def isinitpath( - self, - path: str | os.PathLike[str], - *, - with_parents: bool = False, - ) -> bool: - """Is path an initial path? - - An initial path is a path explicitly given to pytest on the command - line. - - :param with_parents: - If set, also return True if the path is a parent of an initial path. - - .. versionchanged:: 8.0 - Added the ``with_parents`` parameter. - """ + def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: # Optimization: Path(Path(...)) is much slower than isinstance. path_ = path if isinstance(path, Path) else Path(path) - if with_parents: - return path_ in self._initialpaths_with_parents - else: - return path_ in self._initialpaths + return path_ in self._initialpaths - def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: + def gethookproxy(self, fspath: "os.PathLike[str]"): # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager # Check if we have the common case of running # hooks with all conftest.py files. - my_conftestmodules = pm._getconftestmodules(path) + my_conftestmodules = pm._getconftestmodules( + path, + self.config.getoption("importmode"), + rootpath=self.config.rootpath, + ) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) - proxy: pluggy.HookRelay if remove_mods: - # One or more conftests are not in use at this path. - proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] + # One or more conftests are not in use at this fspath. + from .config.compat import PathAwareHookProxy + + proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) else: # All plugins are active for this fspath. proxy = self.config.hook return proxy - def _collect_path( - self, - path: Path, - path_cache: dict[Path, Sequence[nodes.Collector]], + def _recurse(self, direntry: "os.DirEntry[str]") -> bool: + if direntry.name == "__pycache__": + return False + fspath = Path(direntry.path) + ihook = self.gethookproxy(fspath.parent) + if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): + return False + return True + + def _collectfile( + self, fspath: Path, handle_dupes: bool = True ) -> Sequence[nodes.Collector]: - """Create a Collector for the given path. + assert ( + fspath.is_file() + ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( + fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() + ) + ihook = self.gethookproxy(fspath) + if not self.isinitpath(fspath): + if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): + return () - `path_cache` makes it so the same Collectors are returned for the same - path. - """ - if path in path_cache: - return path_cache[path] + if handle_dupes: + keepduplicates = self.config.getoption("keepduplicates") + if not keepduplicates: + duplicate_paths = self.config.pluginmanager._duplicatepaths + if fspath in duplicate_paths: + return () + else: + duplicate_paths.add(fspath) - if path.is_dir(): - ihook = self.gethookproxy(path.parent) - col: nodes.Collector | None = ihook.pytest_collect_directory( - path=path, parent=self - ) - cols: Sequence[nodes.Collector] = (col,) if col is not None else () - - elif path.is_file(): - ihook = self.gethookproxy(path) - cols = ihook.pytest_collect_file(file_path=path, parent=self) - - else: - # Broken symlink or invalid/missing file. - cols = () - - path_cache[path] = cols - return cols + return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] @overload def perform_collect( - self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... - ) -> Sequence[nodes.Item]: ... + self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... + ) -> Sequence[nodes.Item]: + ... @overload - def perform_collect( - self, args: Sequence[str] | None = ..., genitems: bool = ... - ) -> Sequence[nodes.Item | nodes.Collector]: ... + def perform_collect( # noqa: F811 + self, args: Optional[Sequence[str]] = ..., genitems: bool = ... + ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + ... - def perform_collect( - self, args: Sequence[str] | None = None, genitems: bool = True - ) -> Sequence[nodes.Item | nodes.Collector]: + def perform_collect( # noqa: F811 + self, args: Optional[Sequence[str]] = None, genitems: bool = True + ) -> Sequence[Union[nodes.Item, nodes.Collector]]: """Perform the collection phase for this session. This is called by the default :hook:`pytest_collection` hook @@ -796,44 +635,24 @@ class Session(nodes.Collector): self.trace("perform_collect", self, args) self.trace.root.indent += 1 + self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: List[Tuple[Path, List[str]]] = [] + self.items: List[nodes.Item] = [] + hook = self.config.hook - self._notfound = [] - self._initial_parts = [] - self._collection_cache = {} - self.items = [] - items: Sequence[nodes.Item | nodes.Collector] = self.items - consider_namespace_packages: bool = self.config.getini( - "consider_namespace_packages" - ) + items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: - initialpaths: list[Path] = [] - initialpaths_with_parents: list[Path] = [] - - collection_args = [ - resolve_collection_argument( + initialpaths: List[Path] = [] + for arg in args: + fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, arg, - i, as_pypath=self.config.option.pyargs, - consider_namespace_packages=consider_namespace_packages, ) - for i, arg in enumerate(args) - ] - - if not self.config.getoption("keepduplicates"): - # Normalize the collection arguments -- remove duplicates and overlaps. - self._initial_parts = normalize_collection_arguments(collection_args) - else: - self._initial_parts = collection_args - - for collection_argument in self._initial_parts: - initialpaths.append(collection_argument.path) - initialpaths_with_parents.append(collection_argument.path) - initialpaths_with_parents.extend(collection_argument.path.parents) + self._initial_parts.append((fspath, parts)) + initialpaths.append(fspath) self._initialpaths = frozenset(initialpaths) - self._initialpaths_with_parents = frozenset(initialpaths_with_parents) - rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 @@ -842,13 +661,12 @@ class Session(nodes.Collector): for arg, collectors in self._notfound: if collectors: errors.append( - f"not found: {arg}\n(no match in any of {collectors!r})" + f"not found: {arg}\n(no name {arg!r} in any of {collectors!r})" ) else: errors.append(f"found no collectors for {arg}") raise UsageError(*errors) - if not genitems: items = rep.result else: @@ -861,225 +679,193 @@ class Session(nodes.Collector): session=self, config=self.config, items=items ) finally: - self._notfound = [] - self._initial_parts = [] - self._collection_cache = {} hook.pytest_collection_finish(session=self) - if genitems: - self.testscollected = len(items) - + self.testscollected = len(items) return items - def _collect_one_node( - self, - node: nodes.Collector, - handle_dupes: bool = True, - ) -> tuple[CollectReport, bool]: - if node in self._collection_cache and handle_dupes: - rep = self._collection_cache[node] - return rep, True - else: - rep = collect_one_node(node) - self._collection_cache[node] = rep - return rep, False + def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + from _pytest.python import Package - def collect(self) -> Iterator[nodes.Item | nodes.Collector]: - # This is a cache for the root directories of the initial paths. - # We can't use collection_cache for Session because of its special - # role as the bootstrapping collector. - path_cache: dict[Path, Sequence[nodes.Collector]] = {} + # Keep track of any collected nodes in here, so we don't duplicate fixtures. + node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} + node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {} - pm = self.config.pluginmanager + # Keep track of any collected collectors in matchnodes paths, so they + # are not collected more than once. + matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} - for collection_argument in self._initial_parts: - self.trace("processing argument", collection_argument) + # Directories of pkgs with dunder-init files. + pkg_roots: Dict[Path, Package] = {} + + for argpath, names in self._initial_parts: + self.trace("processing argument", (argpath, names)) self.trace.root.indent += 1 - argpath = collection_argument.path - names = collection_argument.parts - parametrization = collection_argument.parametrization - module_name = collection_argument.module_name + # Start with a Session root, and delve to argpath item (dir or file) + # and stack all Packages found on the way. + # No point in finding packages when collecting doctests. + if not self.config.getoption("doctestmodules", False): + pm = self.config.pluginmanager + for parent in (argpath, *argpath.parents): + if not pm._is_in_confcutdir(argpath): + break - # resolve_collection_argument() ensures this. + if parent.is_dir(): + pkginit = parent / "__init__.py" + if pkginit.is_file() and pkginit not in node_cache1: + col = self._collectfile(pkginit, handle_dupes=False) + if col: + if isinstance(col[0], Package): + pkg_roots[parent] = col[0] + node_cache1[col[0].path] = [col[0]] + + # If it's a directory argument, recurse and look for any Subpackages. + # Let the Package collector deal with subnodes, don't collect here. if argpath.is_dir(): assert not names, f"invalid arg {(argpath, names)!r}" - paths = [argpath] - # Add relevant parents of the path, from the root, e.g. - # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] - if module_name is None: - # Paths outside of the confcutdir should not be considered. - for path in argpath.parents: - if not pm._is_in_confcutdir(path): - break - paths.insert(0, path) - else: - # For --pyargs arguments, only consider paths matching the module - # name. Paths beyond the package hierarchy are not included. - module_name_parts = module_name.split(".") - for i, path in enumerate(argpath.parents, 2): - if i > len(module_name_parts) or path.stem != module_name_parts[-i]: - break - paths.insert(0, path) - - # Start going over the parts from the root, collecting each level - # and discarding all nodes which don't match the level's part. - any_matched_in_initial_part = False - notfound_collectors = [] - work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ - (self, [*paths, *names]) - ] - while work: - matchnode, matchparts = work.pop() - - # Pop'd all of the parts, this is a match. - if not matchparts: - yield matchnode - any_matched_in_initial_part = True - continue - - # Should have been matched by now, discard. - if not isinstance(matchnode, nodes.Collector): - continue - - # Collect this level of matching. - # Collecting Session (self) is done directly to avoid endless - # recursion to this function. - subnodes: Sequence[nodes.Collector | nodes.Item] - if isinstance(matchnode, Session): - assert isinstance(matchparts[0], Path) - subnodes = matchnode._collect_path(matchparts[0], path_cache) - else: - # For backward compat, files given directly multiple - # times on the command line should not be deduplicated. - handle_dupes = not ( - len(matchparts) == 1 - and isinstance(matchparts[0], Path) - and matchparts[0].is_file() - ) - rep, duplicate = self._collect_one_node(matchnode, handle_dupes) - if not duplicate and not rep.passed: - # Report collection failures here to avoid failing to - # run some test specified in the command line because - # the module could not be imported (#134). - matchnode.ihook.pytest_collectreport(report=rep) - if not rep.passed: + seen_dirs: Set[Path] = set() + for direntry in visit(argpath, self._recurse): + if not direntry.is_file(): continue - subnodes = rep.result - # Prune this level. - any_matched_in_collector = False - for node in reversed(subnodes): - # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. - if isinstance(matchparts[0], Path): - is_match = node.path == matchparts[0] - if sys.platform == "win32" and not is_match: - # In case the file paths do not match, fallback to samefile() to - # account for short-paths on Windows (#11895). But use a version - # which doesn't resolve symlinks, otherwise we might match the - # same file more than once (#12039). - is_match = samefile_nofollow(node.path, matchparts[0]) + path = Path(direntry.path) + dirpath = path.parent - # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. - else: - if len(matchparts) == 1: - # This the last part, one parametrization goes. - if parametrization is not None: - # A parametrized arg must match exactly. - is_match = node.name == matchparts[0] + parametrization - else: - # A non-parameterized arg matches all parametrizations (if any). - # TODO: Remove the hacky split once the collection structure - # contains parametrization. - is_match = node.name.split("[")[0] == matchparts[0] + if dirpath not in seen_dirs: + # Collect packages first. + seen_dirs.add(dirpath) + pkginit = dirpath / "__init__.py" + if pkginit.exists(): + for x in self._collectfile(pkginit): + yield x + if isinstance(x, Package): + pkg_roots[dirpath] = x + if dirpath in pkg_roots: + # Do not collect packages here. + continue + + for x in self._collectfile(path): + key2 = (type(x), x.path) + if key2 in node_cache2: + yield node_cache2[key2] else: - is_match = node.name == matchparts[0] - if is_match: - work.append((node, matchparts[1:])) - any_matched_in_collector = True + node_cache2[key2] = x + yield x + else: + assert argpath.is_file() - if not any_matched_in_collector: - notfound_collectors.append(matchnode) + if argpath in node_cache1: + col = node_cache1[argpath] + else: + collect_root = pkg_roots.get(argpath.parent, self) + col = collect_root._collectfile(argpath, handle_dupes=False) + if col: + node_cache1[argpath] = col - if not any_matched_in_initial_part: - report_arg = "::".join((str(argpath), *names)) - self._notfound.append((report_arg, notfound_collectors)) + matching = [] + work: List[ + Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]] + ] = [(col, names)] + while work: + self.trace("matchnodes", col, names) + self.trace.root.indent += 1 + + matchnodes, matchnames = work.pop() + for node in matchnodes: + if not matchnames: + matching.append(node) + continue + if not isinstance(node, nodes.Collector): + continue + key = (type(node), node.nodeid) + if key in matchnodes_cache: + rep = matchnodes_cache[key] + else: + rep = collect_one_node(node) + matchnodes_cache[key] = rep + if rep.passed: + submatchnodes = [] + for r in rep.result: + # TODO: Remove parametrized workaround once collection structure contains + # parametrization. + if ( + r.name == matchnames[0] + or r.name.split("[")[0] == matchnames[0] + ): + submatchnodes.append(r) + if submatchnodes: + work.append((submatchnodes, matchnames[1:])) + else: + # Report collection failures here to avoid failing to run some test + # specified in the command line because the module could not be + # imported (#134). + node.ihook.pytest_collectreport(report=rep) + + self.trace("matchnodes finished -> ", len(matching), "nodes") + self.trace.root.indent -= 1 + + if not matching: + report_arg = "::".join((str(argpath), *names)) + self._notfound.append((report_arg, col)) + continue + + # If __init__.py was the only file requested, then the matched + # node will be the corresponding Package (by default), and the + # first yielded item will be the __init__ Module itself, so + # just use that. If this special case isn't taken, then all the + # files in the package will be yielded. + if argpath.name == "__init__.py" and isinstance(matching[0], Package): + try: + yield next(iter(matching[0].collect())) + except StopIteration: + # The package collects nothing with only an __init__.py + # file in it, which gets ignored by the default + # "python_files" option. + pass + continue + + yield from matching self.trace.root.indent -= 1 - def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: + def genitems( + self, node: Union[nodes.Item, nodes.Collector] + ) -> Iterator[nodes.Item]: self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) yield node else: assert isinstance(node, nodes.Collector) - # For backward compat, dedup only applies to files. - handle_dupes = not isinstance(node, nodes.File) - rep, duplicate = self._collect_one_node(node, handle_dupes) + rep = collect_one_node(node) if rep.passed: for subnode in rep.result: yield from self.genitems(subnode) - if not duplicate: - node.ihook.pytest_collectreport(report=rep) + node.ihook.pytest_collectreport(report=rep) -def search_pypath( - module_name: str, *, consider_namespace_packages: bool = False -) -> str | None: - """Search sys.path for the given a dotted module name, and return its file - system path if found.""" +def search_pypath(module_name: str) -> str: + """Search sys.path for the given a dotted module name, and return its file system path.""" try: spec = importlib.util.find_spec(module_name) # AttributeError: looks like package module, but actually filename # ImportError: module does not exist # ValueError: not a module name except (AttributeError, ImportError, ValueError): - return None - - if spec is None: - return None - - if ( - spec.submodule_search_locations is None - or len(spec.submodule_search_locations) == 0 - ): - # Must be a simple module. + return module_name + if spec is None or spec.origin is None or spec.origin == "namespace": + return module_name + elif spec.submodule_search_locations: + return os.path.dirname(spec.origin) + else: return spec.origin - if consider_namespace_packages: - # If submodule_search_locations is set, it's a package (regular or namespace). - # Typically there is a single entry, but documentation claims it can be empty too - # (e.g. if the package has no physical location). - return spec.submodule_search_locations[0] - - if spec.origin is None: - # This is only the case for namespace packages - return None - - return os.path.dirname(spec.origin) - - -@dataclasses.dataclass(frozen=True) -class CollectionArgument: - """A resolved collection argument.""" - - path: Path - parts: Sequence[str] - parametrization: str | None - module_name: str | None - original_index: int - def resolve_collection_argument( - invocation_path: Path, - arg: str, - arg_index: int, - *, - as_pypath: bool = False, - consider_namespace_packages: bool = False, -) -> CollectionArgument: + invocation_path: Path, arg: str, *, as_pypath: bool = False +) -> Tuple[Path, List[str]]: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain @@ -1087,45 +873,27 @@ def resolve_collection_argument( "pkg/tests/test_foo.py::TestClass::test_foo" - This function ensures the path exists, and returns a resolved `CollectionArgument`: + This function ensures the path exists, and returns a tuple: - CollectionArgument( - path=Path("/full/path/to/pkg/tests/test_foo.py"), - parts=["TestClass", "test_foo"], - module_name=None, - ) + (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) When as_pypath is True, expects that the command-line argument actually contains module paths instead of file-system paths: - "pkg.tests.test_foo::TestClass::test_foo[a,b]" + "pkg.tests.test_foo::TestClass::test_foo" In which case we search sys.path for a matching module, and then return the *path* to the - found module, which may look like this: - - CollectionArgument( - path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"), - parts=["TestClass", "test_foo"], - parametrization="[a,b]", - module_name="pkg.tests.test_foo", - ) + found module. If the path doesn't exist, raise UsageError. If the path is a directory and selection parts are present, raise UsageError. """ - base, squacket, rest = arg.partition("[") + base, squacket, rest = str(arg).partition("[") strpath, *parts = base.split("::") - if squacket and not parts: - raise UsageError(f"path cannot contain [] parametrization: {arg}") - parametrization = f"{squacket}{rest}" if squacket else None - module_name = None + if parts: + parts[-1] = f"{parts[-1]}{squacket}{rest}" if as_pypath: - pyarg_strpath = search_pypath( - strpath, consider_namespace_packages=consider_namespace_packages - ) - if pyarg_strpath is not None: - module_name = strpath - strpath = pyarg_strpath + strpath = search_pypath(strpath) fspath = invocation_path / strpath fspath = absolutepath(fspath) if not safe_exists(fspath): @@ -1142,62 +910,4 @@ def resolve_collection_argument( else "directory argument cannot contain :: selection parts: {arg}" ) raise UsageError(msg.format(arg=arg)) - return CollectionArgument( - path=fspath, - parts=parts, - parametrization=parametrization, - module_name=module_name, - original_index=arg_index, - ) - - -def is_collection_argument_subsumed_by( - arg: CollectionArgument, by: CollectionArgument -) -> bool: - """Check if `arg` is subsumed (contained) by `by`.""" - # First check path subsumption. - if by.path != arg.path: - # `by` subsumes `arg` if `by` is a parent directory of `arg` and has no - # parts (collects everything in that directory). - if not by.parts: - return arg.path.is_relative_to(by.path) - return False - # Paths are equal, check parts. - # For example: ("TestClass",) is a prefix of ("TestClass", "test_method"). - if len(by.parts) > len(arg.parts) or arg.parts[: len(by.parts)] != by.parts: - return False - # Paths and parts are equal, check parametrization. - # A `by` without parametrization (None) matches everything, e.g. - # `pytest x.py::test_it` matches `x.py::test_it[0]`. Otherwise must be - # exactly equal. - if by.parametrization is not None and by.parametrization != arg.parametrization: - return False - return True - - -def normalize_collection_arguments( - collection_args: Sequence[CollectionArgument], -) -> list[CollectionArgument]: - """Normalize collection arguments to eliminate overlapping paths and parts. - - Detects when collection arguments overlap in either paths or parts and only - keeps the shorter prefix, or the earliest argument if duplicate, preserving - order. The result is prefix-free. - """ - # A quadratic algorithm is not acceptable since large inputs are possible. - # So this uses an O(n*log(n)) algorithm which takes advantage of the - # property that after sorting, a collection argument will immediately - # precede collection arguments it subsumes. An O(n) algorithm is not worth - # it. - collection_args_sorted = sorted( - collection_args, - key=lambda arg: (arg.path, arg.parts, arg.parametrization or ""), - ) - normalized: list[CollectionArgument] = [] - last_kept = None - for arg in collection_args_sorted: - if last_kept is None or not is_collection_argument_subsumed_by(arg, last_kept): - normalized.append(arg) - last_kept = arg - normalized.sort(key=lambda arg: arg.original_index) - return normalized + return fspath, parts diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__init__.py b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__init__.py index 841d7811..de46b4c8 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__init__.py @@ -1,19 +1,16 @@ """Generic mechanism for marking and selecting python functions.""" - -from __future__ import annotations - -import collections -from collections.abc import Collection -from collections.abc import Iterable -from collections.abc import Set as AbstractSet import dataclasses +from typing import AbstractSet +from typing import Collection +from typing import List +from typing import Optional from typing import TYPE_CHECKING +from typing import Union from .expression import Expression -from .structures import _HiddenParam +from .expression import ParseError from .structures import EMPTY_PARAMETERSET_OPTION from .structures import get_empty_parameterset_mark -from .structures import HIDDEN_PARAM from .structures import Mark from .structures import MARK_GEN from .structures import MarkDecorator @@ -23,17 +20,14 @@ from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import UsageError -from _pytest.config.argparsing import NOT_SET from _pytest.config.argparsing import Parser from _pytest.stash import StashKey - if TYPE_CHECKING: from _pytest.nodes import Item __all__ = [ - "HIDDEN_PARAM", "MARK_GEN", "Mark", "MarkDecorator", @@ -43,13 +37,13 @@ __all__ = [ ] -old_mark_config_key = StashKey[Config | None]() +old_mark_config_key = StashKey[Optional[Config]]() def param( *values: object, - marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), - id: str | _HiddenParam | None = None, + marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (), + id: Optional[str] = None, ) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures `. @@ -67,34 +61,22 @@ def param( assert eval(test_input) == expected :param values: Variable args of the values of the parameter set, in order. - - :param marks: - A single mark or a list of marks to be applied to this parameter set. - - :ref:`pytest.mark.usefixtures ` cannot be added via this parameter. - - :type id: str | Literal[pytest.HIDDEN_PARAM] | None - :param id: - The id to attribute to this parameter set. - - .. versionadded:: 8.4 - :ref:`hidden-param` means to hide the parameter set - from the test name. Can only be used at most 1 time, as - test names need to be unique. + :param marks: A single mark or a list of marks to be applied to this parameter set. + :param id: The id to attribute to this parameter set. """ return ParameterSet.param(*values, marks=marks, id=id) def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") - group._addoption( # private to use reserved lower-case short option + group._addoption( "-k", action="store", dest="keyword", default="", metavar="EXPRESSION", help="Only run tests which match the given substring expression. " - "An expression is a Python evaluable expression " + "An expression is a Python evaluatable expression " "where all names are substring-matched against test names " "and their parent classes. Example: -k 'test_method or test_" "other' matches all test functions and classes whose name " @@ -107,7 +89,7 @@ def pytest_addoption(parser: Parser) -> None: "The matching is case-insensitive.", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-m", action="store", dest="markexpr", @@ -123,12 +105,12 @@ def pytest_addoption(parser: Parser) -> None: help="show markers (builtin, plugin and per-project ones).", ) - parser.addini("markers", "Register new markers for test functions", "linelist") + parser.addini("markers", "Markers for test functions", "linelist") parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") @hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: import _pytest.config if config.option.markers: @@ -138,7 +120,7 @@ def pytest_cmdline_main(config: Config) -> int | ExitCode | None: parts = line.split(":", 1) name = parts[0] rest = parts[1] if len(parts) == 2 else "" - tw.write(f"@pytest.mark.{name}:", bold=True) + tw.write("@pytest.mark.%s:" % name, bold=True) tw.line(rest) tw.line() config._ensure_unconfigure() @@ -167,22 +149,15 @@ class KeywordMatcher: _names: AbstractSet[str] @classmethod - def from_item(cls, item: Item) -> KeywordMatcher: + def from_item(cls, item: "Item") -> "KeywordMatcher": mapped_names = set() - # Add the names of the current item and any parent items, - # except the Session and root Directory's which are not - # interesting for matching. + # Add the names of the current item and any parent items. import pytest for node in item.listchain(): - if isinstance(node, pytest.Session): - continue - if isinstance(node, pytest.Directory) and isinstance( - node.parent, pytest.Session - ): - continue - mapped_names.add(node.name) + if not isinstance(node, pytest.Session): + mapped_names.add(node.name) # Add the names added as extra keywords to current or parent items. mapped_names.update(item.listextrakeywords()) @@ -197,14 +172,17 @@ class KeywordMatcher: return cls(mapped_names) - def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool: - if kwargs: - raise UsageError("Keyword expressions do not support call parameters.") + def __call__(self, subname: str) -> bool: subname = subname.lower() - return any(subname in name.lower() for name in self._names) + names = (name.lower() for name in self._names) + + for name in names: + if subname in name: + return True + return False -def deselect_by_keyword(items: list[Item], config: Config) -> None: +def deselect_by_keyword(items: "List[Item]", config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return @@ -231,37 +209,29 @@ class MarkMatcher: Tries to match on any marker names, attached to the given colitem. """ - __slots__ = ("own_mark_name_mapping",) + __slots__ = ("own_mark_names",) - own_mark_name_mapping: dict[str, list[Mark]] + own_mark_names: AbstractSet[str] @classmethod - def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: - mark_name_mapping = collections.defaultdict(list) - for mark in markers: - mark_name_mapping[mark.name].append(mark) - return cls(mark_name_mapping) + def from_item(cls, item: "Item") -> "MarkMatcher": + mark_names = {mark.name for mark in item.iter_markers()} + return cls(mark_names) - def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: - if not (matches := self.own_mark_name_mapping.get(name, [])): - return False - - for mark in matches: # pylint: disable=consider-using-any-or-all - if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()): - return True - return False + def __call__(self, name: str) -> bool: + return name in self.own_mark_names -def deselect_by_mark(items: list[Item], config: Config) -> None: +def deselect_by_mark(items: "List[Item]", config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") - remaining: list[Item] = [] - deselected: list[Item] = [] + remaining: List[Item] = [] + deselected: List[Item] = [] for item in items: - if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): + if expr.evaluate(MarkMatcher.from_item(item)): remaining.append(item) else: deselected.append(item) @@ -273,13 +243,11 @@ def deselect_by_mark(items: list[Item], config: Config) -> None: def _parse_expression(expr: str, exc_message: str) -> Expression: try: return Expression.compile(expr) - except SyntaxError as e: - raise UsageError( - f"{exc_message}: {e.text}: at column {e.offset}: {e.msg}" - ) from None + except ParseError as e: + raise UsageError(f"{exc_message}: {expr}: {e}") from None -def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: +def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) @@ -292,8 +260,8 @@ def pytest_configure(config: Config) -> None: if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): raise UsageError( - f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect" - f" but it is {empty_parameterset!r}" + "{!s} must be one of skip, xfail or fail_at_collect" + " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset) ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..98ad9cd4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/expression.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/expression.cpython-312.pyc new file mode 100644 index 00000000..91ebc632 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/expression.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc new file mode 100644 index 00000000..381a4a6f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/__pycache__/structures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/expression.py b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/expression.py index 3bdbd03c..9287bcee 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/expression.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/expression.py @@ -5,49 +5,40 @@ The grammar is: expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? - +not_expr: 'not' not_expr | '(' expr ')' | ident ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ -kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') -name: a valid ident, but not a reserved keyword -value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' The semantics are: - Empty expression evaluates to False. -- ident evaluates to True or False according to a provided matcher function. -- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. +- ident evaluates to True of False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. """ - -from __future__ import annotations - import ast -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence import dataclasses import enum -import keyword import re +import sys import types -from typing import Final -from typing import final -from typing import Literal +from typing import Callable +from typing import Iterator +from typing import Mapping from typing import NoReturn -from typing import overload -from typing import Protocol +from typing import Optional +from typing import Sequence + +if sys.version_info >= (3, 8): + astNameConstant = ast.Constant +else: + astNameConstant = ast.NameConstant __all__ = [ "Expression", - "ExpressionMatcher", + "ParseError", ] -FILE_NAME: Final = "" - - class TokenType(enum.Enum): LPAREN = "left parenthesis" RPAREN = "right parenthesis" @@ -56,24 +47,35 @@ class TokenType(enum.Enum): NOT = "not" IDENT = "identifier" EOF = "end of input" - EQUAL = "=" - STRING = "string literal" - COMMA = "," @dataclasses.dataclass(frozen=True) class Token: - __slots__ = ("pos", "type", "value") + __slots__ = ("type", "value", "pos") type: TokenType value: str pos: int +class ParseError(Exception): + """The expression contains invalid syntax. + + :param column: The column in the line where the error occurred (1-based). + :param message: A description of the error. + """ + + def __init__(self, column: int, message: str) -> None: + self.column = column + self.message = message + + def __str__(self) -> str: + return f"at column {self.column}: {self.message}" + + class Scanner: - __slots__ = ("current", "input", "tokens") + __slots__ = ("tokens", "current") def __init__(self, input: str) -> None: - self.input = input self.tokens = self.lex(input) self.current = next(self.tokens) @@ -88,27 +90,6 @@ class Scanner: elif input[pos] == ")": yield Token(TokenType.RPAREN, ")", pos) pos += 1 - elif input[pos] == "=": - yield Token(TokenType.EQUAL, "=", pos) - pos += 1 - elif input[pos] == ",": - yield Token(TokenType.COMMA, ",", pos) - pos += 1 - elif (quote_char := input[pos]) in ("'", '"'): - end_quote_pos = input.find(quote_char, pos + 1) - if end_quote_pos == -1: - raise SyntaxError( - f'closing quote "{quote_char}" is missing', - (FILE_NAME, 1, pos + 1, input), - ) - value = input[pos : end_quote_pos + 1] - if (backslash_pos := input.find("\\")) != -1: - raise SyntaxError( - r'escaping with "\" not supported in marker expression', - (FILE_NAME, 1, backslash_pos + 1, input), - ) - yield Token(TokenType.STRING, value, pos) - pos += len(value) else: match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:]) if match: @@ -123,21 +104,13 @@ class Scanner: yield Token(TokenType.IDENT, value, pos) pos += len(value) else: - raise SyntaxError( + raise ParseError( + pos + 1, f'unexpected character "{input[pos]}"', - (FILE_NAME, 1, pos + 1, input), ) yield Token(TokenType.EOF, "", pos) - @overload - def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ... - - @overload - def accept( - self, type: TokenType, *, reject: Literal[False] = False - ) -> Token | None: ... - - def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: + def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: if self.current.type is type: token = self.current if token.type is not TokenType.EOF: @@ -148,12 +121,12 @@ class Scanner: return None def reject(self, expected: Sequence[TokenType]) -> NoReturn: - raise SyntaxError( + raise ParseError( + self.current.pos + 1, "expected {}; got {}".format( " OR ".join(type.value for type in expected), self.current.type.value, ), - (FILE_NAME, 1, self.current.pos + 1, self.input), ) @@ -165,7 +138,7 @@ IDENT_PREFIX = "$" def expression(s: Scanner) -> ast.Expression: if s.accept(TokenType.EOF): - ret: ast.expr = ast.Constant(False) + ret: ast.expr = astNameConstant(False) else: ret = expr(s) s.accept(TokenType.EOF, reject=True) @@ -197,108 +170,18 @@ def not_expr(s: Scanner) -> ast.expr: return ret ident = s.accept(TokenType.IDENT) if ident: - name = ast.Name(IDENT_PREFIX + ident.value, ast.Load()) - if s.accept(TokenType.LPAREN): - ret = ast.Call(func=name, args=[], keywords=all_kwargs(s)) - s.accept(TokenType.RPAREN, reject=True) - else: - ret = name - return ret - + return ast.Name(IDENT_PREFIX + ident.value, ast.Load()) s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) -BUILTIN_MATCHERS = {"True": True, "False": False, "None": None} - - -def single_kwarg(s: Scanner) -> ast.keyword: - keyword_name = s.accept(TokenType.IDENT, reject=True) - if not keyword_name.value.isidentifier(): - raise SyntaxError( - f"not a valid python identifier {keyword_name.value}", - (FILE_NAME, 1, keyword_name.pos + 1, s.input), - ) - if keyword.iskeyword(keyword_name.value): - raise SyntaxError( - f"unexpected reserved python keyword `{keyword_name.value}`", - (FILE_NAME, 1, keyword_name.pos + 1, s.input), - ) - s.accept(TokenType.EQUAL, reject=True) - - if value_token := s.accept(TokenType.STRING): - value: str | int | bool | None = value_token.value[1:-1] # strip quotes - else: - value_token = s.accept(TokenType.IDENT, reject=True) - if (number := value_token.value).isdigit() or ( - number.startswith("-") and number[1:].isdigit() - ): - value = int(number) - elif value_token.value in BUILTIN_MATCHERS: - value = BUILTIN_MATCHERS[value_token.value] - else: - raise SyntaxError( - f'unexpected character/s "{value_token.value}"', - (FILE_NAME, 1, value_token.pos + 1, s.input), - ) - - ret = ast.keyword(keyword_name.value, ast.Constant(value)) - return ret - - -def all_kwargs(s: Scanner) -> list[ast.keyword]: - ret = [single_kwarg(s)] - while s.accept(TokenType.COMMA): - ret.append(single_kwarg(s)) - return ret - - -class ExpressionMatcher(Protocol): - """A callable which, given an identifier and optional kwargs, should return - whether it matches in an :class:`Expression` evaluation. - - Should be prepared to handle arbitrary strings as input. - - If no kwargs are provided, the expression of the form `foo`. - If kwargs are provided, the expression is of the form `foo(1, b=True, "s")`. - - If the expression is not supported (e.g. don't want to accept the kwargs - syntax variant), should raise :class:`~pytest.UsageError`. - - Example:: - - def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: - # Match `cat`. - if name == "cat" and not kwargs: - return True - # Match `dog(barks=True)`. - if name == "dog" and kwargs == {"barks": False}: - return True - return False - """ - - def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ... - - -@dataclasses.dataclass -class MatcherNameAdapter: - matcher: ExpressionMatcher - name: str - - def __bool__(self) -> bool: - return self.matcher(self.name) - - def __call__(self, **kwargs: str | int | bool | None) -> bool: - return self.matcher(self.name, **kwargs) - - -class MatcherAdapter(Mapping[str, MatcherNameAdapter]): +class MatcherAdapter(Mapping[str, bool]): """Adapts a matcher function to a locals mapping as required by eval().""" - def __init__(self, matcher: ExpressionMatcher) -> None: + def __init__(self, matcher: Callable[[str], bool]) -> None: self.matcher = matcher - def __getitem__(self, key: str) -> MatcherNameAdapter: - return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) + def __getitem__(self, key: str) -> bool: + return self.matcher(key[len(IDENT_PREFIX) :]) def __iter__(self) -> Iterator[str]: raise NotImplementedError() @@ -307,47 +190,39 @@ class MatcherAdapter(Mapping[str, MatcherNameAdapter]): raise NotImplementedError() -@final class Expression: """A compiled match expression as used by -k and -m. The expression can be evaluated against different matchers. """ - __slots__ = ("_code", "input") + __slots__ = ("code",) - def __init__(self, input: str, code: types.CodeType) -> None: - #: The original input line, as a string. - self.input: Final = input - self._code: Final = code + def __init__(self, code: types.CodeType) -> None: + self.code = code @classmethod - def compile(cls, input: str) -> Expression: + def compile(self, input: str) -> "Expression": """Compile a match expression. :param input: The input expression - one line. - - :raises SyntaxError: If the expression is malformed. """ astexpr = expression(Scanner(input)) - code = compile( + code: types.CodeType = compile( astexpr, filename="", mode="eval", ) - return Expression(input, code) + return Expression(code) - def evaluate(self, matcher: ExpressionMatcher) -> bool: + def evaluate(self, matcher: Callable[[str], bool]) -> bool: """Evaluate the match expression. :param matcher: - A callback which determines whether an identifier matches or not. - See the :class:`ExpressionMatcher` protocol for details and example. + Given an identifier, should return whether it matches or not. + Should be prepared to handle arbitrary strings as input. :returns: Whether the expression matches or not. - - :raises UsageError: - If the matcher doesn't support the expression. Cannot happen if the - matcher supports all expressions. """ - return bool(eval(self._code, {"__builtins__": {}}, MatcherAdapter(matcher))) + ret: bool = eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher)) + return ret diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/structures.py b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/structures.py index 16bb6d81..55620f04 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/mark/structures.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/mark/structures.py @@ -1,37 +1,36 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - import collections.abc -from collections.abc import Callable -from collections.abc import Collection -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import MutableMapping -from collections.abc import Sequence import dataclasses -import enum import inspect +import warnings from typing import Any -from typing import final +from typing import Callable +from typing import Collection +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import MutableMapping from typing import NamedTuple +from typing import Optional from typing import overload +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -import warnings +from typing import Union from .._code import getfslineno +from ..compat import ascii_escaped +from ..compat import final from ..compat import NOTSET from ..compat import NotSetType from _pytest.config import Config from _pytest.deprecated import check_ispytest -from _pytest.deprecated import MARKED_FIXTURE from _pytest.outcomes import fail -from _pytest.raises import AbstractRaises -from _pytest.scope import _ScopeName from _pytest.warning_types import PytestUnknownMarkWarning - if TYPE_CHECKING: from ..nodes import Node @@ -39,37 +38,33 @@ if TYPE_CHECKING: EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" -# Singleton type for HIDDEN_PARAM, as described in: -# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions -class _HiddenParam(enum.Enum): - token = 0 - - -#: Can be used as a parameter set id to hide it from the test name. -HIDDEN_PARAM = _HiddenParam.token - - def istestfunc(func) -> bool: return callable(func) and getattr(func, "__name__", "") != "" def get_empty_parameterset_mark( config: Config, argnames: Sequence[str], func -) -> MarkDecorator: +) -> "MarkDecorator": from ..nodes import Collector - argslisting = ", ".join(argnames) + fs, lineno = getfslineno(func) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, + func.__name__, + fs, + lineno, + ) - _fs, lineno = getfslineno(func) - reason = f"got empty parameter set for ({argslisting})" requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) if requested_mark in ("", None, "skip"): mark = MARK_GEN.skip(reason=reason) elif requested_mark == "xfail": mark = MARK_GEN.xfail(reason=reason, run=False) elif requested_mark == "fail_at_collect": + f_name = func.__name__ + _, lineno = getfslineno(func) raise Collector.CollectError( - f"Empty parameter set in '{func.__name__}' at line {lineno + 1}" + "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1) ) else: raise LookupError(requested_mark) @@ -77,68 +72,34 @@ def get_empty_parameterset_mark( class ParameterSet(NamedTuple): - """A set of values for a set of parameters along with associated marks and - an optional ID for the set. - - Examples:: - - pytest.param(1, 2, 3) - # ParameterSet(values=(1, 2, 3), marks=(), id=None) - - pytest.param("hello", id="greeting") - # ParameterSet(values=("hello",), marks=(), id="greeting") - - # Parameter set with marks - pytest.param(42, marks=pytest.mark.xfail) - # ParameterSet(values=(42,), marks=(MarkDecorator(...),), id=None) - - # From parametrize mark (parameter names + list of parameter sets) - pytest.mark.parametrize( - ("a", "b", "expected"), - [ - (1, 2, 3), - pytest.param(40, 2, 42, id="everything"), - ], - ) - # ParameterSet(values=(1, 2, 3), marks=(), id=None) - # ParameterSet(values=(40, 2, 42), marks=(), id="everything") - """ - - values: Sequence[object | NotSetType] - marks: Collection[MarkDecorator | Mark] - id: str | _HiddenParam | None + values: Sequence[Union[object, NotSetType]] + marks: Collection[Union["MarkDecorator", "Mark"]] + id: Optional[str] @classmethod def param( cls, *values: object, - marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), - id: str | _HiddenParam | None = None, - ) -> ParameterSet: + marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), + id: Optional[str] = None, + ) -> "ParameterSet": if isinstance(marks, MarkDecorator): marks = (marks,) else: assert isinstance(marks, collections.abc.Collection) - if any(i.name == "usefixtures" for i in marks): - raise ValueError( - "pytest.param cannot add pytest.mark.usefixtures; see " - "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param" - ) if id is not None: - if not isinstance(id, str) and id is not HIDDEN_PARAM: - raise TypeError( - "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, " - f"got {type(id)}: {id!r}", - ) + if not isinstance(id, str): + raise TypeError(f"Expected id to be a string, got {type(id)}: {id!r}") + id = ascii_escaped(id) return cls(values, marks, id) @classmethod def extract_from( cls, - parameterset: ParameterSet | Sequence[object] | object, + parameterset: Union["ParameterSet", Sequence[object], object], force_tuple: bool = False, - ) -> ParameterSet: + ) -> "ParameterSet": """Extract from an object or objects. :param parameterset: @@ -149,6 +110,7 @@ class ParameterSet(NamedTuple): Enforce tuple wrapping so single argument tuple values don't get decomposed and break tests. """ + if isinstance(parameterset, cls): return parameterset if force_tuple: @@ -163,11 +125,11 @@ class ParameterSet(NamedTuple): @staticmethod def _parse_parametrize_args( - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], + argnames: Union[str, Sequence[str]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], *args, **kwargs, - ) -> tuple[Sequence[str], bool]: + ) -> Tuple[Sequence[str], bool]: if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -177,9 +139,9 @@ class ParameterSet(NamedTuple): @staticmethod def _parse_parametrize_parameters( - argvalues: Iterable[ParameterSet | Sequence[object] | object], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], force_tuple: bool, - ) -> list[ParameterSet]: + ) -> List["ParameterSet"]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @@ -187,12 +149,12 @@ class ParameterSet(NamedTuple): @classmethod def _for_parametrize( cls, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], + argnames: Union[str, Sequence[str]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], func, config: Config, nodeid: str, - ) -> tuple[Sequence[str], list[ParameterSet]]: + ) -> Tuple[Sequence[str], List["ParameterSet"]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -222,9 +184,7 @@ class ParameterSet(NamedTuple): # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. mark = get_empty_parameterset_mark(config, argnames, func) parameters.append( - ParameterSet( - values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET" - ) + ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) ) return argnames, parameters @@ -237,24 +197,24 @@ class Mark: #: Name of the mark. name: str #: Positional arguments of the mark decorator. - args: tuple[Any, ...] + args: Tuple[Any, ...] #: Keyword arguments of the mark decorator. kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) + _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Sequence[str] | None = dataclasses.field( + _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( default=None, repr=False ) def __init__( self, name: str, - args: tuple[Any, ...], + args: Tuple[Any, ...], kwargs: Mapping[str, Any], - param_ids_from: Mark | None = None, - param_ids_generated: Sequence[str] | None = None, + param_ids_from: Optional["Mark"] = None, + param_ids_generated: Optional[Sequence[str]] = None, *, _ispytest: bool = False, ) -> None: @@ -270,7 +230,7 @@ class Mark: def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: Mark) -> Mark: + def combined_with(self, other: "Mark") -> "Mark": """Return a new Mark which is a combination of this Mark and another Mark. @@ -282,7 +242,7 @@ class Mark: assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Mark | None = None + param_ids_from: Optional[Mark] = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -301,7 +261,7 @@ class Mark: # A generic parameter designating an object to which a Mark may # be applied -- a test function (callable) or class. # Note: a lambda is not allowed, but this can't be represented. -Markable = TypeVar("Markable", bound=Callable[..., object] | type) +Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) @dataclasses.dataclass @@ -310,8 +270,8 @@ class MarkDecorator: ``MarkDecorators`` are created with ``pytest.mark``:: - mark1 = pytest.mark.NAME # Simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator and can then be applied as decorators to test functions:: @@ -353,7 +313,7 @@ class MarkDecorator: return self.mark.name @property - def args(self) -> tuple[Any, ...]: + def args(self) -> Tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -367,7 +327,7 @@ class MarkDecorator: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: + def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": """Return a MarkDecorator with extra arguments added. Unlike calling the MarkDecorator, with_args() can be used even @@ -380,11 +340,11 @@ class MarkDecorator: # return type. Not much we can do about that. Thankfully mypy picks # the first match so it works out even if we break the rules. @overload - def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] + def __call__(self, arg: Markable) -> Markable: # type: ignore[misc] pass @overload - def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: + def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": pass def __call__(self, *args: object, **kwargs: object): @@ -392,22 +352,17 @@ class MarkDecorator: if args and not kwargs: func = args[0] is_class = inspect.isclass(func) - # For staticmethods/classmethods, the marks are eventually fetched from the - # function object, not the descriptor, so unwrap. - unwrapped_func = func - if isinstance(func, staticmethod | classmethod): - unwrapped_func = func.__func__ - if len(args) == 1 and (istestfunc(unwrapped_func) or is_class): - store_mark(unwrapped_func, self.mark, stacklevel=3) + if len(args) == 1 and (istestfunc(func) or is_class): + store_mark(func, self.mark) return func return self.with_args(*args, **kwargs) def get_unpacked_marks( - obj: object | type, + obj: Union[object, type], *, consider_mro: bool = True, -) -> list[Mark]: +) -> List[Mark]: """Obtain the unpacked marks that are stored on an object. If obj is a class and consider_mro is true, return marks applied to @@ -437,7 +392,7 @@ def get_unpacked_marks( def normalize_mark_list( - mark_list: Iterable[Mark | MarkDecorator], + mark_list: Iterable[Union[Mark, MarkDecorator]] ) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks @@ -449,22 +404,16 @@ def normalize_mark_list( for mark in mark_list: mark_obj = getattr(mark, "mark", mark) if not isinstance(mark_obj, Mark): - raise TypeError(f"got {mark_obj!r} instead of Mark") + raise TypeError(f"got {repr(mark_obj)} instead of Mark") yield mark_obj -def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None: +def store_mark(obj, mark: Mark) -> None: """Store a Mark on an object. This is used to implement the Mark declarations/decorators correctly. """ assert isinstance(mark, Mark), mark - - from ..fixtures import getfixturemarker - - if getfixturemarker(obj) is not None: - warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel) - # Always reassign name to avoid updating pytestmark in a reference that # was only borrowed. obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] @@ -473,52 +422,59 @@ def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None: # Typing for builtin pytest marks. This is cheating; it gives builtin marks # special privilege, and breaks modularity. But practicality beats purity... if TYPE_CHECKING: + from _pytest.scope import _ScopeName class _SkipMarkDecorator(MarkDecorator): - @overload # type: ignore[override,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: ... + @overload # type: ignore[override,misc,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: + ... @overload - def __call__(self, reason: str = ...) -> MarkDecorator: ... + def __call__(self, reason: str = ...) -> "MarkDecorator": + ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - condition: str | bool = ..., - *conditions: str | bool, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], reason: str = ..., - ) -> MarkDecorator: ... + ) -> MarkDecorator: + ... class _XfailMarkDecorator(MarkDecorator): - @overload # type: ignore[override,no-overload-impl] - def __call__(self, arg: Markable) -> Markable: ... + @overload # type: ignore[override,misc,no-overload-impl] + def __call__(self, arg: Markable) -> Markable: + ... @overload def __call__( self, - condition: str | bool = False, - *conditions: str | bool, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], reason: str = ..., run: bool = ..., - raises: None - | type[BaseException] - | tuple[type[BaseException], ...] - | AbstractRaises[BaseException] = ..., + raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ..., strict: bool = ..., - ) -> MarkDecorator: ... + ) -> MarkDecorator: + ... class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], + argnames: Union[str, Sequence[str]], + argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], *, - indirect: bool | Sequence[str] = ..., - ids: Iterable[None | str | float | int | bool] - | Callable[[Any], object | None] - | None = ..., - scope: _ScopeName | None = ..., - ) -> MarkDecorator: ... + indirect: Union[bool, Sequence[str]] = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + scope: Optional[_ScopeName] = ..., + ) -> MarkDecorator: + ... class _UsefixturesMarkDecorator(MarkDecorator): def __call__(self, *fixtures: str) -> MarkDecorator: # type: ignore[override] @@ -538,10 +494,9 @@ class MarkGenerator: import pytest - @pytest.mark.slowtest def test_function(): - pass + pass applies a 'slowtest' :class:`Mark` on ``test_function``. """ @@ -557,8 +512,8 @@ class MarkGenerator: def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._config: Config | None = None - self._markers: set[str] = set() + self._config: Optional[Config] = None + self._markers: Set[str] = set() def __getattr__(self, name: str) -> MarkDecorator: """Generate a new :class:`MarkDecorator` with the given name.""" @@ -580,24 +535,21 @@ class MarkGenerator: # If the name is not in the set of known marks after updating, # then it really is time to issue a warning or an error. if name not in self._markers: - # Raise a specific error for common misspellings of "parametrize". - if name in ["parameterize", "parametrise", "parameterise"]: - __tracebackhide__ = True - fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") - - strict_markers = self._config.getini("strict_markers") - if strict_markers is None: - strict_markers = self._config.getini("strict") - if strict_markers: + if self._config.option.strict_markers or self._config.option.strict: fail( f"{name!r} not found in `markers` configuration option", pytrace=False, ) + # Raise a specific error for common misspellings of "parametrize". + if name in ["parameterize", "parametrise", "parameterise"]: + __tracebackhide__ = True + fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") + warnings.warn( - f"Unknown pytest.mark.{name} - is this a typo? You can register " + "Unknown pytest.mark.%s - is this a typo? You can register " "custom marks to avoid this warning - for details, see " - "https://docs.pytest.org/en/stable/how-to/mark.html", + "https://docs.pytest.org/en/stable/how-to/mark.html" % name, PytestUnknownMarkWarning, 2, ) @@ -610,9 +562,9 @@ MARK_GEN = MarkGenerator(_ispytest=True) @final class NodeKeywords(MutableMapping[str, Any]): - __slots__ = ("_markers", "node", "parent") + __slots__ = ("node", "parent", "_markers") - def __init__(self, node: Node) -> None: + def __init__(self, node: "Node") -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} @@ -632,13 +584,15 @@ class NodeKeywords(MutableMapping[str, Any]): # below and use the collections.abc fallback, but that would be slow. def __contains__(self, key: object) -> bool: - return key in self._markers or ( - self.parent is not None and key in self.parent.keywords + return ( + key in self._markers + or self.parent is not None + and key in self.parent.keywords ) def update( # type: ignore[override] self, - other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), + other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), **kwds: Any, ) -> None: self._markers.update(other) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/monkeypatch.py b/Backend/venv/lib/python3.12/site-packages/_pytest/monkeypatch.py index 07cc3fc4..9e51ff33 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/monkeypatch.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/monkeypatch.py @@ -1,27 +1,24 @@ -# mypy: allow-untyped-defs """Monkeypatching and mocking functionality.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import MutableMapping -from contextlib import contextmanager import os -from pathlib import Path import re import sys -from typing import Any -from typing import final -from typing import overload -from typing import TypeVar import warnings +from contextlib import contextmanager +from typing import Any +from typing import Generator +from typing import List +from typing import Mapping +from typing import MutableMapping +from typing import Optional +from typing import overload +from typing import Tuple +from typing import TypeVar +from typing import Union -from _pytest.deprecated import MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES +from _pytest.compat import final from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning - RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") @@ -30,7 +27,7 @@ V = TypeVar("V") @fixture -def monkeypatch() -> Generator[MonkeyPatch]: +def monkeypatch() -> Generator["MonkeyPatch", None, None]: """A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -92,12 +89,14 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: obj = getattr(obj, name) except AttributeError as e: raise AttributeError( - f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}" + "{!r} object at {} has no attribute {!r}".format( + type(obj).__name__, ann, name + ) ) from e return obj -def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: +def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: if not isinstance(import_path, str) or "." not in import_path: raise TypeError(f"must be absolute import path string, not {import_path!r}") module, attr = import_path.rsplit(".", 1) @@ -130,14 +129,14 @@ class MonkeyPatch: """ def __init__(self) -> None: - self._setattr: list[tuple[object, str, object]] = [] - self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: str | None = None - self._savesyspath: list[str] | None = None + self._setattr: List[Tuple[object, str, object]] = [] + self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] + self._cwd: Optional[str] = None + self._savesyspath: Optional[List[str]] = None @classmethod @contextmanager - def context(cls) -> Generator[MonkeyPatch]: + def context(cls) -> Generator["MonkeyPatch", None, None]: """Context manager that returns a new :class:`MonkeyPatch` object which undoes any patching done inside the ``with`` block upon exit. @@ -169,7 +168,8 @@ class MonkeyPatch: name: object, value: Notset = ..., raising: bool = ..., - ) -> None: ... + ) -> None: + ... @overload def setattr( @@ -178,12 +178,13 @@ class MonkeyPatch: name: str, value: object, raising: bool = ..., - ) -> None: ... + ) -> None: + ... def setattr( self, - target: str | object, - name: object | str, + target: Union[str, object], + name: Union[object, str], value: object = notset, raising: bool = True, ) -> None: @@ -254,8 +255,8 @@ class MonkeyPatch: def delattr( self, - target: object | str, - name: str | Notset = notset, + target: Union[object, str], + name: Union[str, Notset] = notset, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -310,7 +311,7 @@ class MonkeyPatch: # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict del dic[name] # type: ignore[attr-defined] - def setenv(self, name: str, value: str, prepend: str | None = None) -> None: + def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: """Set environment variable ``name`` to ``value``. If ``prepend`` is a character, read the current environment variable @@ -320,8 +321,10 @@ class MonkeyPatch: if not isinstance(value, str): warnings.warn( # type: ignore[unreachable] PytestWarning( - f"Value of environment variable {name} type should be str, but got " - f"{value!r} (type: {type(value).__name__}); converted to str implicitly" + "Value of environment variable {name} type should be str, but got " + "{value!r} (type: {type}); converted to str implicitly".format( + name=name, value=value, type=type(value).__name__ + ) ), stacklevel=2, ) @@ -341,6 +344,7 @@ class MonkeyPatch: def syspath_prepend(self, path) -> None: """Prepend ``path`` to ``sys.path`` list of import locations.""" + if self._savesyspath is None: self._savesyspath = sys.path[:] sys.path.insert(0, str(path)) @@ -348,26 +352,8 @@ class MonkeyPatch: # https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171 # this is only needed when pkg_resources was already loaded by the namespace package if "pkg_resources" in sys.modules: - import pkg_resources from pkg_resources import fixup_namespace_packages - # Only issue deprecation warning if this call would actually have an - # effect for this specific path. - if ( - hasattr(pkg_resources, "_namespace_packages") - and pkg_resources._namespace_packages - ): - path_obj = Path(str(path)) - for ns_pkg in pkg_resources._namespace_packages: - if ns_pkg is None: - continue - ns_pkg_path = path_obj / ns_pkg.replace(".", os.sep) - if ns_pkg_path.is_dir(): - warnings.warn( - MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES, stacklevel=2 - ) - break - fixup_namespace_packages(str(path)) # A call to syspathinsert() usually means that the caller wants to @@ -381,7 +367,7 @@ class MonkeyPatch: invalidate_caches() - def chdir(self, path: str | os.PathLike[str]) -> None: + def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: """Change the current working directory to the specified path. :param path: diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/nodes.py b/Backend/venv/lib/python3.12/site-packages/_pytest/nodes.py index 6690f6ab..667a02b7 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/nodes.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/nodes.py @@ -1,52 +1,47 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -import abc -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import MutableMapping -from functools import cached_property -from functools import lru_cache import os -import pathlib +import warnings +from inspect import signature from pathlib import Path from typing import Any +from typing import Callable from typing import cast -from typing import NoReturn +from typing import Iterable +from typing import Iterator +from typing import List +from typing import MutableMapping +from typing import Optional from typing import overload +from typing import Set +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -import warnings - -import pluggy +from typing import Union import _pytest._code from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr from _pytest._code.code import Traceback -from _pytest._code.code import TracebackStyle +from _pytest.compat import cached_property from _pytest.compat import LEGACY_PATH -from _pytest.compat import signature from _pytest.config import Config from _pytest.config import ConftestImportFailure -from _pytest.config.compat import _check_path +from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import NODE_CTOR_FSPATH_ARG from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords from _pytest.outcomes import fail from _pytest.pathlib import absolutepath +from _pytest.pathlib import commonpath from _pytest.stash import Stash from _pytest.warning_types import PytestWarning - if TYPE_CHECKING: - from typing_extensions import Self - # Imported here due to circular import. from _pytest.main import Session + from _pytest._code.code import _TracebackStyle SEP = "/" @@ -54,13 +49,63 @@ SEP = "/" tracebackcutdir = Path(_pytest.__file__).parent -_T = TypeVar("_T") +def iterparentnodeids(nodeid: str) -> Iterator[str]: + """Return the parent node IDs of a given node ID, inclusive. + + For the node ID + + "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" + + the result would be + + "" + "testing" + "testing/code" + "testing/code/test_excinfo.py" + "testing/code/test_excinfo.py::TestFormattedExcinfo" + "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" + + Note that / components are only considered until the first ::. + """ + pos = 0 + first_colons: Optional[int] = nodeid.find("::") + if first_colons == -1: + first_colons = None + # The root Session node - always present. + yield "" + # Eagerly consume SEP parts until first colons. + while True: + at = nodeid.find(SEP, pos, first_colons) + if at == -1: + break + if at > 0: + yield nodeid[:at] + pos = at + len(SEP) + # Eagerly consume :: parts. + while True: + at = nodeid.find("::", pos) + if at == -1: + break + if at > 0: + yield nodeid[:at] + pos = at + len("::") + # The node ID itself. + if nodeid: + yield nodeid + + +def _check_path(path: Path, fspath: LEGACY_PATH) -> None: + if Path(fspath) != path: + raise ValueError( + f"Path({fspath!r}) != {path!r}\n" + "if both path and fspath are given they need to be equal" + ) def _imply_path( - node_type: type[Node], - path: Path | None, - fspath: LEGACY_PATH | None, + node_type: Type["Node"], + path: Optional[Path], + fspath: Optional[LEGACY_PATH], ) -> Path: if fspath is not None: warnings.warn( @@ -81,51 +126,37 @@ def _imply_path( _NodeType = TypeVar("_NodeType", bound="Node") -class NodeMeta(abc.ABCMeta): - """Metaclass used by :class:`Node` to enforce that direct construction raises - :class:`Failed`. - - This behaviour supports the indirection introduced with :meth:`Node.from_parent`, - the named constructor to be used instead of direct construction. The design - decision to enforce indirection with :class:`NodeMeta` was made as a - temporary aid for refactoring the collection tree, which was diagnosed to - have :class:`Node` objects whose creational patterns were overly entangled. - Once the refactoring is complete, this metaclass can be removed. - - See https://github.com/pytest-dev/pytest/projects/3 for an overview of the - progress on detangling the :class:`Node` classes. - """ - - def __call__(cls, *k, **kw) -> NoReturn: +class NodeMeta(type): + def __call__(self, *k, **kw): msg = ( "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" "See " "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" " for more details." - ).format(name=f"{cls.__module__}.{cls.__name__}") + ).format(name=f"{self.__module__}.{self.__name__}") fail(msg, pytrace=False) - def _create(cls: type[_T], *k, **kw) -> _T: + def _create(self, *k, **kw): try: - return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] + return super().__call__(*k, **kw) except TypeError: - sig = signature(getattr(cls, "__init__")) + sig = signature(getattr(self, "__init__")) known_kw = {k: v for k, v in kw.items() if k in sig.parameters} from .warning_types import PytestDeprecationWarning warnings.warn( PytestDeprecationWarning( - f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n" + f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n" "See https://docs.pytest.org/en/stable/deprecations.html" "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs " "for more details." ) ) - return super().__call__(*k, **known_kw) # type: ignore[no-any-return,misc] + return super().__call__(*k, **known_kw) -class Node(abc.ABC, metaclass=NodeMeta): +class Node(metaclass=NodeMeta): r"""Base class of :class:`Collector` and :class:`Item`, the components of the test collection tree. @@ -136,32 +167,32 @@ class Node(abc.ABC, metaclass=NodeMeta): # Implemented in the legacypath plugin. #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage #: for methods not migrated to ``pathlib.Path`` yet, such as - #: :meth:`Item.reportinfo `. Will be deprecated in - #: a future release, prefer using :attr:`path` instead. + #: :meth:`Item.reportinfo`. Will be deprecated in a future release, prefer + #: using :attr:`path` instead. fspath: LEGACY_PATH # Use __slots__ to make attribute access faster. # Note that __dict__ is still available. __slots__ = ( - "__dict__", - "_nodeid", - "_store", - "config", "name", "parent", - "path", + "config", "session", + "path", + "_nodeid", + "_store", + "__dict__", ) def __init__( self, name: str, - parent: Node | None = None, - config: Config | None = None, - session: Session | None = None, - fspath: LEGACY_PATH | None = None, - path: Path | None = None, - nodeid: str | None = None, + parent: "Optional[Node]" = None, + config: Optional[Config] = None, + session: "Optional[Session]" = None, + fspath: Optional[LEGACY_PATH] = None, + path: Optional[Path] = None, + nodeid: Optional[str] = None, ) -> None: #: A unique name within the scope of the parent node. self.name: str = name @@ -188,17 +219,17 @@ class Node(abc.ABC, metaclass=NodeMeta): if path is None and fspath is None: path = getattr(parent, "path", None) #: Filesystem path where this node was collected from (can be None). - self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath) + self.path: Path = _imply_path(type(self), path, fspath=fspath) # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. self.keywords: MutableMapping[str, Any] = NodeKeywords(self) #: The marker objects belonging to this node. - self.own_markers: list[Mark] = [] + self.own_markers: List[Mark] = [] #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: set[str] = set() + self.extra_keyword_matches: Set[str] = set() if nodeid is not None: assert "::()" not in nodeid @@ -215,7 +246,7 @@ class Node(abc.ABC, metaclass=NodeMeta): self._store = self.stash @classmethod - def from_parent(cls, parent: Node, **kw) -> Self: + def from_parent(cls, parent: "Node", **kw): """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -233,7 +264,7 @@ class Node(abc.ABC, metaclass=NodeMeta): return cls._create(parent=parent, **kw) @property - def ihook(self) -> pluggy.HookRelay: + def ihook(self): """fspath-sensitive hook proxy used to call pytest hooks.""" return self.session.gethookproxy(self.path) @@ -264,7 +295,9 @@ class Node(abc.ABC, metaclass=NodeMeta): # enforce type checks here to avoid getting a generic type error later otherwise. if not isinstance(warning, Warning): raise ValueError( - f"warning must be an instance of Warning or subclass, got {warning!r}" + "warning must be an instance of Warning or subclass, got {!r}".format( + warning + ) ) path, lineno = get_fslocation_from_item(self) assert lineno is not None @@ -291,29 +324,23 @@ class Node(abc.ABC, metaclass=NodeMeta): def teardown(self) -> None: pass - def iter_parents(self) -> Iterator[Node]: - """Iterate over all parent collectors starting from and including self - up to the root of the collection tree. + def listchain(self) -> List["Node"]: + """Return list of all parent collectors up to self, starting from + the root of collection tree. - .. versionadded:: 8.1 + :returns: The nodes. """ - parent: Node | None = self - while parent is not None: - yield parent - parent = parent.parent - - def listchain(self) -> list[Node]: - """Return a list of all parent collectors starting from the root of the - collection tree down to and including self.""" chain = [] - item: Node | None = self + item: Optional[Node] = self while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: + def add_marker( + self, marker: Union[str, MarkDecorator], append: bool = True + ) -> None: """Dynamically add a marker object to the node. :param marker: @@ -335,7 +362,7 @@ class Node(abc.ABC, metaclass=NodeMeta): else: self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name: str | None = None) -> Iterator[Mark]: + def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -344,25 +371,29 @@ class Node(abc.ABC, metaclass=NodeMeta): return (x[1] for x in self.iter_markers_with_node(name=name)) def iter_markers_with_node( - self, name: str | None = None - ) -> Iterator[tuple[Node, Mark]]: + self, name: Optional[str] = None + ) -> Iterator[Tuple["Node", Mark]]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. :returns: An iterator of (node, mark) tuples. """ - for node in self.iter_parents(): + for node in reversed(self.listchain()): for mark in node.own_markers: if name is None or getattr(mark, "name", None) == name: yield node, mark @overload - def get_closest_marker(self, name: str) -> Mark | None: ... + def get_closest_marker(self, name: str) -> Optional[Mark]: + ... @overload - def get_closest_marker(self, name: str, default: Mark) -> Mark: ... + def get_closest_marker(self, name: str, default: Mark) -> Mark: + ... - def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: + def get_closest_marker( + self, name: str, default: Optional[Mark] = None + ) -> Optional[Mark]: """Return the first marker matching the name, from closest (for example function) to farther level (for example module level). @@ -371,14 +402,14 @@ class Node(abc.ABC, metaclass=NodeMeta): """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self) -> set[str]: + def listextrakeywords(self) -> Set[str]: """Return a set of all extra keywords in self and any parents.""" - extra_keywords: set[str] = set() + extra_keywords: Set[str] = set() for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords - def listnames(self) -> list[str]: + def listnames(self) -> List[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: @@ -390,17 +421,18 @@ class Node(abc.ABC, metaclass=NodeMeta): """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls: type[_NodeType]) -> _NodeType | None: - """Get the closest parent node (including self) which is an instance of + def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: + """Get the next parent node (including self) which is an instance of the given class. :param cls: The node class to search for. :returns: The node, if found. """ - for node in self.iter_parents(): - if isinstance(node, cls): - return node - return None + current: Optional[Node] = self + while current and not isinstance(current, cls): + current = current.parent + assert current is None or isinstance(current, cls) + return current def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: return excinfo.traceback @@ -408,19 +440,19 @@ class Node(abc.ABC, metaclass=NodeMeta): def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: TracebackStyle | None = None, + style: "Optional[_TracebackStyle]" = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exception(excinfo.value.cause) + excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: style = "value" if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] if self.config.getoption("fulltrace", False): style = "long" tbfilter = False @@ -435,13 +467,11 @@ class Node(abc.ABC, metaclass=NodeMeta): else: style = "long" - if self.config.get_verbosity() > 1: + if self.config.getoption("verbose", 0) > 1: truncate_locals = False else: truncate_locals = True - truncate_args = False if self.config.get_verbosity() > 2 else True - # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. # It is possible for a fixture/test to change the CWD while this code runs, which # would then result in the user seeing confusing paths in the failure message. @@ -460,14 +490,13 @@ class Node(abc.ABC, metaclass=NodeMeta): style=style, tbfilter=tbfilter, truncate_locals=truncate_locals, - truncate_args=truncate_args, ) def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: TracebackStyle | None = None, - ) -> str | TerminalRepr: + style: "Optional[_TracebackStyle]" = None, + ) -> Union[str, TerminalRepr]: """Return a representation of a collection or test failure. .. seealso:: :ref:`non-python tests` @@ -477,26 +506,26 @@ class Node(abc.ABC, metaclass=NodeMeta): return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: +def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: """Try to extract the actual location from a node, depending on available attributes: * "location": a pair (path, lineno) * "obj": a Python object that the node wraps. - * "path": just a path + * "fspath": just a path :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. - location: tuple[str, int | None, str] | None = getattr(node, "location", None) + location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) if location is not None: return location[:2] obj = getattr(node, "obj", None) if obj is not None: return getfslineno(obj) - return getattr(node, "path", "unknown location"), -1 + return getattr(node, "fspath", "unknown location"), -1 -class Collector(Node, abc.ABC): +class Collector(Node): """Base class of all collectors. Collector create children through `collect()` and thus iteratively build @@ -506,15 +535,14 @@ class Collector(Node, abc.ABC): class CollectError(Exception): """An error during collection, contains a custom message.""" - @abc.abstractmethod - def collect(self) -> Iterable[Item | Collector]: + def collect(self) -> Iterable[Union["Item", "Collector"]]: """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException] - ) -> str | TerminalRepr: + ) -> Union[str, TerminalRepr]: """Return a representation of a collection failure. :param excinfo: Exception information for the failure. @@ -539,37 +567,31 @@ class Collector(Node, abc.ABC): ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - return ntraceback.filter(excinfo) + return excinfo.traceback.filter(excinfo) return excinfo.traceback -@lru_cache(maxsize=1000) -def _check_initialpaths_for_relpath( - initial_paths: frozenset[Path], path: Path -) -> str | None: - if path in initial_paths: - return "" - - for parent in path.parents: - if parent in initial_paths: - return str(path.relative_to(parent)) - +def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: + for initial_path in session._initialpaths: + if commonpath(path, initial_path) == initial_path: + rel = str(path.relative_to(initial_path)) + return "" if rel == "." else rel return None -class FSCollector(Collector, abc.ABC): +class FSCollector(Collector): """Base class for filesystem collectors.""" def __init__( self, - fspath: LEGACY_PATH | None = None, - path_or_parent: Path | Node | None = None, - path: Path | None = None, - name: str | None = None, - parent: Node | None = None, - config: Config | None = None, - session: Session | None = None, - nodeid: str | None = None, + fspath: Optional[LEGACY_PATH] = None, + path_or_parent: Optional[Union[Path, Node]] = None, + path: Optional[Path] = None, + name: Optional[str] = None, + parent: Optional[Node] = None, + config: Optional[Config] = None, + session: Optional["Session"] = None, + nodeid: Optional[str] = None, ) -> None: if path_or_parent: if isinstance(path_or_parent, Node): @@ -600,7 +622,7 @@ class FSCollector(Collector, abc.ABC): try: nodeid = str(self.path.relative_to(session.config.rootpath)) except ValueError: - nodeid = _check_initialpaths_for_relpath(session._initialpaths, path) + nodeid = _check_initialpaths_for_relpath(session, path) if nodeid and os.sep != SEP: nodeid = nodeid.replace(os.sep, SEP) @@ -619,40 +641,30 @@ class FSCollector(Collector, abc.ABC): cls, parent, *, - fspath: LEGACY_PATH | None = None, - path: Path | None = None, + fspath: Optional[LEGACY_PATH] = None, + path: Optional[Path] = None, **kw, - ) -> Self: + ): """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) + def gethookproxy(self, fspath: "os.PathLike[str]"): + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.gethookproxy(fspath) -class File(FSCollector, abc.ABC): + def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.isinitpath(path) + + +class File(FSCollector): """Base class for collecting tests from a file. :ref:`non-python tests`. """ -class Directory(FSCollector, abc.ABC): - """Base class for collecting files from a directory. - - A basic directory collector does the following: goes over the files and - sub-directories in the directory and creates collectors for them by calling - the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, - after checking that they are not ignored using - :hook:`pytest_ignore_collect`. - - The default directory collectors are :class:`~pytest.Dir` and - :class:`~pytest.Package`. - - .. versionadded:: 8.0 - - :ref:`custom directory collectors`. - """ - - -class Item(Node, abc.ABC): +class Item(Node): """Base class of all test invocation items. Note that for a single function there might be multiple test invocation items. @@ -664,9 +676,9 @@ class Item(Node, abc.ABC): self, name, parent=None, - config: Config | None = None, - session: Session | None = None, - nodeid: str | None = None, + config: Optional[Config] = None, + session: Optional["Session"] = None, + nodeid: Optional[str] = None, **kw, ) -> None: # The first two arguments are intentionally passed positionally, @@ -681,11 +693,11 @@ class Item(Node, abc.ABC): nodeid=nodeid, **kw, ) - self._report_sections: list[tuple[str, str, str]] = [] + self._report_sections: List[Tuple[str, str, str]] = [] #: A list of tuples (name, value) that holds user defined properties #: for this test. - self.user_properties: list[tuple[str, object]] = [] + self.user_properties: List[Tuple[str, object]] = [] self._check_item_and_collector_diamond_inheritance() @@ -718,7 +730,6 @@ class Item(Node, abc.ABC): PytestWarning, ) - @abc.abstractmethod def runtest(self) -> None: """Run the test case for this item. @@ -745,7 +756,7 @@ class Item(Node, abc.ABC): if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: + def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: """Get location information for this item for test reports. Returns a tuple with three elements: @@ -759,14 +770,14 @@ class Item(Node, abc.ABC): return self.path, None, "" @cached_property - def location(self) -> tuple[str, int | None, str]: + def location(self) -> Tuple[str, Optional[int], str]: """ Returns a tuple of ``(relfspath, lineno, testname)`` for this item where ``relfspath`` is file path relative to ``config.rootpath`` and lineno is a 0-based line number. """ location = self.reportinfo() - path = absolutepath(location[0]) + path = absolutepath(os.fspath(location[0])) relfspath = self.session._node_location_to_relpath(path) assert type(location[2]) is str return (relfspath, location[1], location[2]) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/nose.py b/Backend/venv/lib/python3.12/site-packages/_pytest/nose.py new file mode 100644 index 00000000..273bd045 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/nose.py @@ -0,0 +1,50 @@ +"""Run testsuites written for nose.""" +import warnings + +from _pytest.config import hookimpl +from _pytest.deprecated import NOSE_SUPPORT +from _pytest.fixtures import getfixturemarker +from _pytest.nodes import Item +from _pytest.python import Function +from _pytest.unittest import TestCaseFunction + + +@hookimpl(trylast=True) +def pytest_runtest_setup(item: Item) -> None: + if not isinstance(item, Function): + return + # Don't do nose style setup/teardown on direct unittest style classes. + if isinstance(item, TestCaseFunction): + return + + # Capture the narrowed type of item for the teardown closure, + # see https://github.com/python/mypy/issues/2608 + func = item + + call_optional(func.obj, "setup", func.nodeid) + func.addfinalizer(lambda: call_optional(func.obj, "teardown", func.nodeid)) + + # NOTE: Module- and class-level fixtures are handled in python.py + # with `pluginmanager.has_plugin("nose")` checks. + # It would have been nicer to implement them outside of core, but + # it's not straightforward. + + +def call_optional(obj: object, name: str, nodeid: str) -> bool: + method = getattr(obj, name, None) + if method is None: + return False + is_fixture = getfixturemarker(method) is not None + if is_fixture: + return False + if not callable(method): + return False + # Warn about deprecation of this plugin. + method_name = getattr(method, "__name__", str(method)) + warnings.warn( + NOSE_SUPPORT.format(nodeid=nodeid, method=method_name, stage=name), stacklevel=2 + ) + # If there are any problems allow the exception to raise rather than + # silently ignoring it. + method() + return True diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/outcomes.py b/Backend/venv/lib/python3.12/site-packages/_pytest/outcomes.py index 766be95c..1be97dda 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/outcomes.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/outcomes.py @@ -1,21 +1,35 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" - -from __future__ import annotations - import sys +import warnings from typing import Any -from typing import ClassVar +from typing import Callable +from typing import cast from typing import NoReturn +from typing import Optional +from typing import Type +from typing import TypeVar -from .warning_types import PytestDeprecationWarning +from _pytest.deprecated import KEYWORD_MSG_ARG + +TYPE_CHECKING = False # Avoid circular import through compat. + +if TYPE_CHECKING: + from typing_extensions import Protocol +else: + # typing.Protocol is only available starting from Python 3.8. It is also + # available from typing_extensions, but we don't want a runtime dependency + # on that. So use a dummy runtime implementation. + from typing import Generic + + Protocol = Generic class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info about test and collection outcomes.""" - def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: + def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: if msg is not None and not isinstance(msg, str): error_msg = ( # type: ignore[unreachable] "{} expected string as 'msg' parameter, got '{}' instead.\n" @@ -44,7 +58,7 @@ class Skipped(OutcomeException): def __init__( self, - msg: str | None = None, + msg: Optional[str] = None, pytrace: bool = True, allow_module_level: bool = False, *, @@ -67,18 +81,41 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: int | None = None + self, msg: str = "unknown reason", returncode: Optional[int] = None ) -> None: self.msg = msg self.returncode = returncode super().__init__(msg) -class XFailed(Failed): - """Raised from an explicit call to pytest.xfail().""" +# Elaborate hack to work around https://github.com/python/mypy/issues/2087. +# Ideally would just be `exit.Exception = Exit` etc. + +_F = TypeVar("_F", bound=Callable[..., object]) +_ET = TypeVar("_ET", bound=Type[BaseException]) -class _Exit: +class _WithException(Protocol[_F, _ET]): + Exception: _ET + __call__: _F + + +def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]: + def decorate(func: _F) -> _WithException[_F, _ET]: + func_with_exception = cast(_WithException[_F, _ET], func) + func_with_exception.Exception = exception_type + return func_with_exception + + return decorate + + +# Exposed helper methods. + + +@_with_exception(Exit) +def exit( + reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None +) -> NoReturn: """Exit testing process. :param reason: @@ -86,24 +123,30 @@ class _Exit: only because `msg` is deprecated. :param returncode: - Return code to be used when exiting pytest. None means the same as ``0`` (no error), - same as :func:`sys.exit`. + Return code to be used when exiting pytest. - :raises pytest.exit.Exception: - The exception that is raised. + :param msg: + Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. """ + __tracebackhide__ = True + from _pytest.config import UsageError - Exception: ClassVar[type[Exit]] = Exit - - def __call__(self, reason: str = "", returncode: int | None = None) -> NoReturn: - __tracebackhide__ = True - raise Exit(msg=reason, returncode=returncode) + if reason and msg: + raise UsageError( + "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`." + ) + if not reason: + if msg is None: + raise UsageError("exit() requires a reason argument") + warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2) + reason = msg + raise Exit(reason, returncode) -exit: _Exit = _Exit() - - -class _Skip: +@_with_exception(Skipped) +def skip( + reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None +) -> NoReturn: """Skip an executing test with the given message. This function should be called only during testing (setup, call or teardown) or @@ -121,8 +164,8 @@ class _Skip: Defaults to False. - :raises pytest.skip.Exception: - The exception that is raised. + :param msg: + Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. .. note:: It is better to use the :ref:`pytest.mark.skipif ref` marker when @@ -131,18 +174,13 @@ class _Skip: Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`) to skip a doctest statically. """ - - Exception: ClassVar[type[Skipped]] = Skipped - - def __call__(self, reason: str = "", allow_module_level: bool = False) -> NoReturn: - __tracebackhide__ = True - raise Skipped(msg=reason, allow_module_level=allow_module_level) + __tracebackhide__ = True + reason = _resolve_msg_to_reason("skip", reason, msg) + raise Skipped(msg=reason, allow_module_level=allow_module_level) -skip: _Skip = _Skip() - - -class _Fail: +@_with_exception(Failed) +def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> NoReturn: """Explicitly fail an executing test with the given message. :param reason: @@ -152,28 +190,60 @@ class _Fail: If False, msg represents the full failure information and no python traceback will be reported. - :raises pytest.fail.Exception: - The exception that is raised. + :param msg: + Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. """ - - Exception: ClassVar[type[Failed]] = Failed - - def __call__(self, reason: str = "", pytrace: bool = True) -> NoReturn: - __tracebackhide__ = True - raise Failed(msg=reason, pytrace=pytrace) + __tracebackhide__ = True + reason = _resolve_msg_to_reason("fail", reason, msg) + raise Failed(msg=reason, pytrace=pytrace) -fail: _Fail = _Fail() +def _resolve_msg_to_reason( + func_name: str, reason: str, msg: Optional[str] = None +) -> str: + """ + Handles converting the deprecated msg parameter if provided into + reason, raising a deprecation warning. This function will be removed + when the optional msg argument is removed from here in future. + + :param str func_name: + The name of the offending function, this is formatted into the deprecation message. + + :param str reason: + The reason= passed into either pytest.fail() or pytest.skip() + + :param str msg: + The msg= passed into either pytest.fail() or pytest.skip(). This will + be converted into reason if it is provided to allow pytest.skip(msg=) or + pytest.fail(msg=) to continue working in the interim period. + + :returns: + The value to use as reason. + + """ + __tracebackhide__ = True + if msg is not None: + if reason: + from pytest import UsageError + + raise UsageError( + f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted." + ) + warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3) + reason = msg + return reason -class _XFail: +class XFailed(Failed): + """Raised from an explicit call to pytest.xfail().""" + + +@_with_exception(XFailed) +def xfail(reason: str = "") -> NoReturn: """Imperatively xfail an executing test or setup function with the given reason. This function should be called only during testing (setup, call or teardown). - No other code is executed after using ``xfail()`` (it is implemented - internally by raising an exception). - :param reason: The message to show the user as reason for the xfail. @@ -181,27 +251,13 @@ class _XFail: It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be xfailed under certain conditions like known bugs or missing features. - - :raises pytest.xfail.Exception: - The exception that is raised. """ - - Exception: ClassVar[type[XFailed]] = XFailed - - def __call__(self, reason: str = "") -> NoReturn: - __tracebackhide__ = True - raise XFailed(msg=reason) - - -xfail: _XFail = _XFail() + __tracebackhide__ = True + raise XFailed(reason) def importorskip( - modname: str, - minversion: str | None = None, - reason: str | None = None, - *, - exc_type: type[ImportError] | None = None, + modname: str, minversion: Optional[str] = None, reason: Optional[str] = None ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. @@ -214,84 +270,30 @@ def importorskip( :param reason: If given, this reason is shown as the message when the module cannot be imported. - :param exc_type: - The exception that should be captured in order to skip modules. - Must be :py:class:`ImportError` or a subclass. - - If the module can be imported but raises :class:`ImportError`, pytest will - issue a warning to the user, as often users expect the module not to be - found (which would raise :class:`ModuleNotFoundError` instead). - - This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. - - See :ref:`import-or-skip-import-error` for details. - :returns: The imported module. This should be assigned to its canonical name. - :raises pytest.skip.Exception: - If the module cannot be imported. - Example:: docutils = pytest.importorskip("docutils") - - .. versionadded:: 8.2 - - The ``exc_type`` parameter. """ import warnings __tracebackhide__ = True compile(modname, "", "eval") # to catch syntaxerrors - # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError), - # as this might be hiding an installation/environment problem, which is not usually what is intended - # when using importorskip() (#11523). - # In 9.1, to keep the function signature compatible, we just change the code below to: - # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given. - # 2. Remove `warn_on_import` and the warning handling. - if exc_type is None: - exc_type = ImportError - warn_on_import_error = True - else: - warn_on_import_error = False - - skipped: Skipped | None = None - warning: Warning | None = None - with warnings.catch_warnings(): # Make sure to ignore ImportWarnings that might happen because # of existing directories with the same name we're trying to # import but without a __init__.py file. warnings.simplefilter("ignore") - try: __import__(modname) - except exc_type as exc: - # Do not raise or issue warnings inside the catch_warnings() block. + except ImportError as exc: if reason is None: reason = f"could not import {modname!r}: {exc}" - skipped = Skipped(reason, allow_module_level=True) - - if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): - lines = [ - "", - f"Module '{modname}' was found, but when imported by pytest it raised:", - f" {exc!r}", - "In pytest 9.1 this warning will become an error by default.", - "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " - "warning by passing exc_type=ImportError explicitly.", - "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", - ] - warning = PytestDeprecationWarning("\n".join(lines)) - - if warning: - warnings.warn(warning, stacklevel=2) - if skipped: - raise skipped - + raise Skipped(reason, allow_module_level=True) from None mod = sys.modules[modname] if minversion is None: return mod @@ -302,7 +304,8 @@ def importorskip( if verattr is None or Version(verattr) < Version(minversion): raise Skipped( - f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", + "module %r has __version__ %r, required is: %r" + % (modname, verattr, minversion), allow_module_level=True, ) return mod diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/pastebin.py b/Backend/venv/lib/python3.12/site-packages/_pytest/pastebin.py index c7b39d96..22c7a622 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/pastebin.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/pastebin.py @@ -1,18 +1,15 @@ -# mypy: allow-untyped-defs """Submit failure or test session information to a pastebin service.""" - -from __future__ import annotations - -from io import StringIO import tempfile +from io import StringIO from typing import IO +from typing import Union +import pytest from _pytest.config import Config from _pytest.config import create_terminal_writer from _pytest.config.argparsing import Parser from _pytest.stash import StashKey from _pytest.terminal import TerminalReporter -import pytest pastebinfile_key = StashKey[IO[bytes]]() @@ -20,7 +17,7 @@ pastebinfile_key = StashKey[IO[bytes]]() def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") - group.addoption( + group._addoption( "--pastebin", metavar="mode", action="store", @@ -66,19 +63,18 @@ def pytest_unconfigure(config: Config) -> None: # Write summary. tr.write_sep("=", "Sending information to Paste Service") pastebinurl = create_new_paste(sessionlog) - tr.write_line(f"pastebin session-log: {pastebinurl}\n") + tr.write_line("pastebin session-log: %s\n" % pastebinurl) -def create_new_paste(contents: str | bytes) -> str: +def create_new_paste(contents: Union[str, bytes]) -> str: """Create a new paste using the bpaste.net service. :contents: Paste contents string. :returns: URL to the pasted contents, or an error message. """ import re - from urllib.error import HTTPError - from urllib.parse import urlencode from urllib.request import urlopen + from urllib.parse import urlencode params = {"code": contents, "lexer": "text", "expiry": "1week"} url = "https://bpa.st" @@ -86,11 +82,8 @@ def create_new_paste(contents: str | bytes) -> str: response: str = ( urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") ) - except HTTPError as e: - with e: # HTTPErrors are also http responses that must be closed! - return f"bad response: {e}" - except OSError as e: # eg urllib.error.URLError - return f"bad response: {e}" + except OSError as exc_info: # urllib errors + return "bad response: %s" % exc_info m = re.search(r'href="/raw/(\w+)"', response) if m: return f"{url}/show/{m.group(1)}" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/pathlib.py b/Backend/venv/lib/python3.12/site-packages/_pytest/pathlib.py index cd154346..c2f8535f 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/pathlib.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/pathlib.py @@ -1,22 +1,20 @@ -from __future__ import annotations - import atexit -from collections.abc import Callable -from collections.abc import Iterable -from collections.abc import Iterator import contextlib +import fnmatch +import importlib.util +import itertools +import os +import shutil +import sys +import types +import uuid +import warnings from enum import Enum from errno import EBADF from errno import ELOOP from errno import ENOENT from errno import ENOTDIR -import fnmatch from functools import partial -from importlib.machinery import ModuleSpec -from importlib.machinery import PathFinder -import importlib.util -import itertools -import os from os.path import expanduser from os.path import expandvars from os.path import isabs @@ -24,27 +22,26 @@ from os.path import sep from pathlib import Path from pathlib import PurePath from posixpath import sep as posix_sep -import shutil -import sys -import types from types import ModuleType -from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import Type from typing import TypeVar -import uuid -import warnings +from typing import Union from _pytest.compat import assert_never from _pytest.outcomes import skip from _pytest.warning_types import PytestWarning - -if sys.version_info < (3, 11): - from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader -else: - from importlib.machinery import NamespaceLoader - LOCK_TIMEOUT = 60 * 60 * 24 * 3 + _AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath) # The following function, variables and comments were @@ -59,7 +56,7 @@ _IGNORED_WINERRORS = ( ) -def _ignore_error(exception: Exception) -> bool: +def _ignore_error(exception): return ( getattr(exception, "errno", None) in _IGNORED_ERRORS or getattr(exception, "winerror", None) in _IGNORED_WINERRORS @@ -71,10 +68,12 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: def on_rm_rf_error( - func: Callable[..., Any] | None, + func, path: str, - excinfo: BaseException - | tuple[type[BaseException], BaseException, types.TracebackType | None], + excinfo: Union[ + BaseException, + Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], + ], *, start_path: Path, ) -> bool: @@ -102,7 +101,9 @@ def on_rm_rf_error( if func not in (os.open,): warnings.warn( PytestWarning( - f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}" + "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( + func, path, type(exc), exc + ) ) ) return False @@ -170,23 +171,23 @@ def rm_rf(path: Path) -> None: shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]: - """Find all elements in root that begin with the prefix, case-insensitive.""" +def find_prefixed(root: Path, prefix: str) -> Iterator[Path]: + """Find all elements in root that begin with the prefix, case insensitive.""" l_prefix = prefix.lower() - for x in os.scandir(root): + for x in root.iterdir(): if x.name.lower().startswith(l_prefix): yield x -def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]: +def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]: """Return the parts of the paths following the prefix. :param iter: Iterator over path names. :param prefix: Expected prefix of the path names. """ p_len = len(prefix) - for entry in iter: - yield entry.name[p_len:] + for p in iter: + yield p.name[p_len:] def find_suffixes(root: Path, prefix: str) -> Iterator[str]: @@ -194,7 +195,7 @@ def find_suffixes(root: Path, prefix: str) -> Iterator[str]: return extract_suffixes(find_prefixed(root, prefix), prefix) -def parse_num(maybe_num: str) -> int: +def parse_num(maybe_num) -> int: """Parse number path suffixes, returns -1 on error.""" try: return int(maybe_num) @@ -202,7 +203,9 @@ def parse_num(maybe_num: str) -> int: return -1 -def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: +def _force_symlink( + root: Path, target: Union[str, PurePath], link_to: Union[str, Path] +) -> None: """Helper to create the current symlink. It's full of race conditions that are reasonably OK to ignore @@ -239,7 +242,7 @@ def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path: else: raise OSError( "could not create numbered dir with prefix " - f"{prefix} in {root} after 10 tries" + "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root) ) @@ -260,9 +263,7 @@ def create_cleanup_lock(p: Path) -> Path: return lock_path -def register_cleanup_lock_removal( - lock_path: Path, register: Any = atexit.register -) -> Any: +def register_cleanup_lock_removal(lock_path: Path, register=atexit.register): """Register a cleanup function for removing a lock, by default on atexit.""" pid = os.getpid() @@ -345,15 +346,15 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: """List candidates for numbered directories to be removed - follows py.path.""" max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) max_delete = max_existing - keep - entries = find_prefixed(root, prefix) - entries, entries2 = itertools.tee(entries) - numbers = map(parse_num, extract_suffixes(entries2, prefix)) - for entry, number in zip(entries, numbers, strict=True): + paths = find_prefixed(root, prefix) + paths, paths2 = itertools.tee(paths) + numbers = map(parse_num, extract_suffixes(paths2, prefix)) + for path, number in zip(paths, numbers): if number <= max_delete: - yield Path(entry) + yield path -def cleanup_dead_symlinks(root: Path) -> None: +def cleanup_dead_symlinks(root: Path): for left_dir in root.iterdir(): if left_dir.is_symlink(): if not left_dir.resolve().exists(): @@ -416,7 +417,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: +def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: """A port of FNMatcher from py.path.common which works with PurePath() instances. The difference between this algorithm and PurePath.match() is that the @@ -452,19 +453,15 @@ def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: return fnmatch.fnmatch(name, pattern) -def parts(s: str) -> set[str]: +def parts(s: str) -> Set[str]: parts = s.split(sep) return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} -def symlink_or_skip( - src: os.PathLike[str] | str, - dst: os.PathLike[str] | str, - **kwargs: Any, -) -> None: +def symlink_or_skip(src, dst, **kwargs): """Make a symlink, or skip the test in case symlinks are not supported.""" try: - os.symlink(src, dst, **kwargs) + os.symlink(str(src), str(dst), **kwargs) except OSError as e: skip(f"symlinks not supported: {e}") @@ -487,90 +484,73 @@ class ImportPathMismatchError(ImportError): def import_path( - path: str | os.PathLike[str], + p: Union[str, "os.PathLike[str]"], *, - mode: str | ImportMode = ImportMode.prepend, + mode: Union[str, ImportMode] = ImportMode.prepend, root: Path, - consider_namespace_packages: bool, ) -> ModuleType: - """ - Import and return a module from the given path, which can be a file (a module) or + """Import and return a module from the given path, which can be a file (a module) or a directory (a package). - :param path: - Path to the file to import. + The import mechanism used is controlled by the `mode` parameter: - :param mode: - Controls the underlying import mechanism that will be used: + * `mode == ImportMode.prepend`: the directory containing the module (or package, taking + `__init__.py` files into account) will be put at the *start* of `sys.path` before + being imported with `importlib.import_module`. - * ImportMode.prepend: the directory containing the module (or package, taking - `__init__.py` files into account) will be put at the *start* of `sys.path` before - being imported with `importlib.import_module`. + * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended + to the end of `sys.path`, if not already in `sys.path`. - * ImportMode.append: same as `prepend`, but the directory will be appended - to the end of `sys.path`, if not already in `sys.path`. - - * ImportMode.importlib: uses more fine control mechanisms provided by `importlib` - to import the module, which avoids having to muck with `sys.path` at all. It effectively - allows having same-named test modules in different places. + * `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib` + to import the module, which avoids having to muck with `sys.path` at all. It effectively + allows having same-named test modules in different places. :param root: Used as an anchor when mode == ImportMode.importlib to obtain a unique name for the module being imported so it can safely be stored into ``sys.modules``. - :param consider_namespace_packages: - If True, consider namespace packages when resolving module names. - :raises ImportPathMismatchError: If after importing the given `path` and the module `__file__` are different. Only raised in `prepend` and `append` modes. """ - path = Path(path) mode = ImportMode(mode) + path = Path(p) + if not path.exists(): raise ImportError(path) if mode is ImportMode.importlib: - # Try to import this module using the standard import mechanisms, but - # without touching sys.path. - try: - pkg_root, module_name = resolve_pkg_root_and_module_name( - path, consider_namespace_packages=consider_namespace_packages - ) - except CouldNotResolvePathError: - pass - else: - # If the given module name is already in sys.modules, do not import it again. - with contextlib.suppress(KeyError): - return sys.modules[module_name] - - mod = _import_module_using_spec( - module_name, path, pkg_root, insert_modules=False - ) - if mod is not None: - return mod - - # Could not import the module with the current sys.path, so we fall back - # to importing the file as a single module, not being a part of a package. module_name = module_name_from_path(path, root) with contextlib.suppress(KeyError): return sys.modules[module_name] - mod = _import_module_using_spec( - module_name, path, path.parent, insert_modules=True - ) - if mod is None: + for meta_importer in sys.meta_path: + spec = meta_importer.find_spec(module_name, [str(path.parent)]) + if spec is not None: + break + else: + spec = importlib.util.spec_from_file_location(module_name, str(path)) + + if spec is None: raise ImportError(f"Can't find module {module_name} at location {path}") + mod = importlib.util.module_from_spec(spec) + sys.modules[module_name] = mod + spec.loader.exec_module(mod) # type: ignore[union-attr] + insert_missing_modules(sys.modules, module_name) return mod - try: - pkg_root, module_name = resolve_pkg_root_and_module_name( - path, consider_namespace_packages=consider_namespace_packages - ) - except CouldNotResolvePathError: - pkg_root, module_name = path.parent, path.stem + pkg_path = resolve_package_path(path) + if pkg_path is not None: + pkg_root = pkg_path.parent + names = list(path.with_suffix("").relative_to(pkg_root).parts) + if names[-1] == "__init__": + names.pop() + module_name = ".".join(names) + else: + pkg_root = path.parent + module_name = path.stem # Change sys.path permanently: restoring it at the end of this function would cause surprising # problems because of delayed imports: for example, a conftest.py file imported by this function @@ -612,148 +592,6 @@ def import_path( return mod -def _import_module_using_spec( - module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool -) -> ModuleType | None: - """ - Tries to import a module by its canonical name, path, and its parent location. - - :param module_name: - The expected module name, will become the key of `sys.modules`. - - :param module_path: - The file path of the module, for example `/foo/bar/test_demo.py`. - If module is a package, pass the path to the `__init__.py` of the package. - If module is a namespace package, pass directory path. - - :param module_location: - The parent location of the module. - If module is a package, pass the directory containing the `__init__.py` file. - - :param insert_modules: - If True, will call `insert_missing_modules` to create empty intermediate modules - with made-up module names (when importing test files not reachable from `sys.path`). - - Example 1 of parent_module_*: - - module_name: "a.b.c.demo" - module_path: Path("a/b/c/demo.py") - module_location: Path("a/b/c/") - if "a.b.c" is package ("a/b/c/__init__.py" exists), then - parent_module_name: "a.b.c" - parent_module_path: Path("a/b/c/__init__.py") - parent_module_location: Path("a/b/c/") - else: - parent_module_name: "a.b.c" - parent_module_path: Path("a/b/c") - parent_module_location: Path("a/b/") - - Example 2 of parent_module_*: - - module_name: "a.b.c" - module_path: Path("a/b/c/__init__.py") - module_location: Path("a/b/c/") - if "a.b" is package ("a/b/__init__.py" exists), then - parent_module_name: "a.b" - parent_module_path: Path("a/b/__init__.py") - parent_module_location: Path("a/b/") - else: - parent_module_name: "a.b" - parent_module_path: Path("a/b/") - parent_module_location: Path("a/") - """ - # Attempt to import the parent module, seems is our responsibility: - # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 - parent_module_name, _, name = module_name.rpartition(".") - parent_module: ModuleType | None = None - if parent_module_name: - parent_module = sys.modules.get(parent_module_name) - # If the parent_module lacks the `__path__` attribute, AttributeError when finding a submodule's spec, - # requiring re-import according to the path. - need_reimport = not hasattr(parent_module, "__path__") - if parent_module is None or need_reimport: - # Get parent_location based on location, get parent_path based on path. - if module_path.name == "__init__.py": - # If the current module is in a package, - # need to leave the package first and then enter the parent module. - parent_module_path = module_path.parent.parent - else: - parent_module_path = module_path.parent - - if (parent_module_path / "__init__.py").is_file(): - # If the parent module is a package, loading by __init__.py file. - parent_module_path = parent_module_path / "__init__.py" - - parent_module = _import_module_using_spec( - parent_module_name, - parent_module_path, - parent_module_path.parent, - insert_modules=insert_modules, - ) - - # Checking with sys.meta_path first in case one of its hooks can import this module, - # such as our own assertion-rewrite hook. - for meta_importer in sys.meta_path: - module_name_of_meta = getattr(meta_importer.__class__, "__module__", "") - if module_name_of_meta == "_pytest.assertion.rewrite" and module_path.is_file(): - # Import modules in subdirectories by module_path - # to ensure assertion rewrites are not missed (#12659). - find_spec_path = [str(module_location), str(module_path)] - else: - find_spec_path = [str(module_location)] - - spec = meta_importer.find_spec(module_name, find_spec_path) - - if spec_matches_module_path(spec, module_path): - break - else: - loader = None - if module_path.is_dir(): - # The `spec_from_file_location` matches a loader based on the file extension by default. - # For a namespace package, need to manually specify a loader. - loader = NamespaceLoader(name, module_path, PathFinder()) # type: ignore[arg-type] - - spec = importlib.util.spec_from_file_location( - module_name, str(module_path), loader=loader - ) - - if spec_matches_module_path(spec, module_path): - assert spec is not None - # Find spec and import this module. - mod = importlib.util.module_from_spec(spec) - sys.modules[module_name] = mod - spec.loader.exec_module(mod) # type: ignore[union-attr] - - # Set this module as an attribute of the parent module (#12194). - if parent_module is not None: - setattr(parent_module, name, mod) - - if insert_modules: - insert_missing_modules(sys.modules, module_name) - return mod - - return None - - -def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool: - """Return true if the given ModuleSpec can be used to import the given module path.""" - if module_spec is None: - return False - - if module_spec.origin: - return Path(module_spec.origin) == module_path - - # Compare the path with the `module_spec.submodule_Search_Locations` in case - # the module is part of a namespace package. - # https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.submodule_search_locations - if module_spec.submodule_search_locations: # can be None. - for path in module_spec.submodule_search_locations: - if Path(path) == module_path: - return True - - return False - - # Implement a special _is_same function on Windows which returns True if the two filenames # compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678). if sys.platform.startswith("win"): @@ -790,15 +628,10 @@ def module_name_from_path(path: Path, root: Path) -> str: if len(path_parts) >= 2 and path_parts[-1] == "__init__": path_parts = path_parts[:-1] - # Module names cannot contain ".", normalize them to "_". This prevents - # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules. - # Also, important to replace "." at the start of paths, as those are considered relative imports. - path_parts = tuple(x.replace(".", "_") for x in path_parts) - return ".".join(path_parts) -def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: +def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None: """ Used by ``import_path`` to create intermediate modules when using mode=importlib. @@ -807,45 +640,48 @@ def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> otherwise "src.tests.test_foo" is not importable by ``__import__``. """ module_parts = module_name.split(".") + child_module: Union[ModuleType, None] = None + module: Union[ModuleType, None] = None + child_name: str = "" while module_name: - parent_module_name, _, child_name = module_name.rpartition(".") - if parent_module_name: - parent_module = modules.get(parent_module_name) - if parent_module is None: - try: - # If sys.meta_path is empty, calling import_module will issue - # a warning and raise ModuleNotFoundError. To avoid the - # warning, we check sys.meta_path explicitly and raise the error - # ourselves to fall back to creating a dummy module. - if not sys.meta_path: - raise ModuleNotFoundError - parent_module = importlib.import_module(parent_module_name) - except ModuleNotFoundError: - parent_module = ModuleType( - module_name, - doc="Empty module created by pytest's importmode=importlib.", - ) - modules[parent_module_name] = parent_module - + if module_name not in modules: + try: + # If sys.meta_path is empty, calling import_module will issue + # a warning and raise ModuleNotFoundError. To avoid the + # warning, we check sys.meta_path explicitly and raise the error + # ourselves to fall back to creating a dummy module. + if not sys.meta_path: + raise ModuleNotFoundError + module = importlib.import_module(module_name) + except ModuleNotFoundError: + module = ModuleType( + module_name, + doc="Empty module created by pytest's importmode=importlib.", + ) + else: + module = modules[module_name] + if child_module: # Add child attribute to the parent that can reference the child # modules. - if not hasattr(parent_module, child_name): - setattr(parent_module, child_name, modules[module_name]) - + if not hasattr(module, child_name): + setattr(module, child_name, child_module) + modules[module_name] = module + # Keep track of the child module while moving up the tree. + child_module, child_name = module, module_name.rpartition(".")[-1] module_parts.pop(-1) module_name = ".".join(module_parts) -def resolve_package_path(path: Path) -> Path | None: +def resolve_package_path(path: Path) -> Optional[Path]: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. - Returns None if it cannot be determined. + Returns None if it can not be determined. """ result = None for parent in itertools.chain((path,), path.parents): if parent.is_dir(): - if not (parent / "__init__.py").is_file(): + if not parent.joinpath("__init__.py").is_file(): break if not parent.name.isidentifier(): break @@ -853,135 +689,30 @@ def resolve_package_path(path: Path) -> Path | None: return result -def resolve_pkg_root_and_module_name( - path: Path, *, consider_namespace_packages: bool = False -) -> tuple[Path, str]: - """ - Return the path to the directory of the root package that contains the - given Python file, and its module name: - - src/ - app/ - __init__.py - core/ - __init__.py - models.py - - Passing the full path to `models.py` will yield Path("src") and "app.core.models". - - If consider_namespace_packages is True, then we additionally check upwards in the hierarchy - for namespace packages: - - https://packaging.python.org/en/latest/guides/packaging-namespace-packages - - Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). - """ - pkg_root: Path | None = None - pkg_path = resolve_package_path(path) - if pkg_path is not None: - pkg_root = pkg_path.parent - if consider_namespace_packages: - start = pkg_root if pkg_root is not None else path.parent - for candidate in (start, *start.parents): - module_name = compute_module_name(candidate, path) - if module_name and is_importable(module_name, path): - # Point the pkg_root to the root of the namespace package. - pkg_root = candidate - break - - if pkg_root is not None: - module_name = compute_module_name(pkg_root, path) - if module_name: - return pkg_root, module_name - - raise CouldNotResolvePathError(f"Could not resolve for {path}") - - -def is_importable(module_name: str, module_path: Path) -> bool: - """ - Return if the given module path could be imported normally by Python, akin to the user - entering the REPL and importing the corresponding module name directly, and corresponds - to the module_path specified. - - :param module_name: - Full module name that we want to check if is importable. - For example, "app.models". - - :param module_path: - Full path to the python module/package we want to check if is importable. - For example, "/projects/src/app/models.py". - """ - try: - # Note this is different from what we do in ``_import_module_using_spec``, where we explicitly search through - # sys.meta_path to be able to pass the path of the module that we want to import (``meta_importer.find_spec``). - # Using importlib.util.find_spec() is different, it gives the same results as trying to import - # the module normally in the REPL. - spec = importlib.util.find_spec(module_name) - except (ImportError, ValueError, ImportWarning): - return False - else: - return spec_matches_module_path(spec, module_path) - - -def compute_module_name(root: Path, module_path: Path) -> str | None: - """Compute a module name based on a path and a root anchor.""" - try: - path_without_suffix = module_path.with_suffix("") - except ValueError: - # Empty paths (such as Path.cwd()) might break meta_path hooks (like our own assertion rewriter). - return None - - try: - relative = path_without_suffix.relative_to(root) - except ValueError: # pragma: no cover - return None - names = list(relative.parts) - if not names: - return None - if names[-1] == "__init__": - names.pop() - return ".".join(names) - - -class CouldNotResolvePathError(Exception): - """Custom exception raised by resolve_pkg_root_and_module_name.""" - - -def scandir( - path: str | os.PathLike[str], - sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, -) -> list[os.DirEntry[str]]: +def scandir(path: Union[str, "os.PathLike[str]"]) -> List["os.DirEntry[str]"]: """Scan a directory recursively, in breadth-first order. - The returned entries are sorted according to the given key. - The default is to sort by name. - If the directory does not exist, return an empty list. + The returned entries are sorted. """ entries = [] - # Attempt to create a scandir iterator for the given path. - try: - scandir_iter = os.scandir(path) - except FileNotFoundError: - # If the directory does not exist, return an empty list. - return [] - # Use the scandir iterator in a context manager to ensure it is properly closed. - with scandir_iter as s: + with os.scandir(path) as s: + # Skip entries with symlink loops and other brokenness, so the caller + # doesn't have to deal with it. for entry in s: try: entry.is_file() except OSError as err: if _ignore_error(err): continue - # Reraise non-ignorable errors to avoid hiding issues. raise entries.append(entry) - entries.sort(key=sort_key) # type: ignore[arg-type] + entries.sort(key=lambda entry: entry.name) return entries def visit( - path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool] -) -> Iterator[os.DirEntry[str]]: + path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] +) -> Iterator["os.DirEntry[str]"]: """Walk a directory recursively, in breadth-first order. The `recurse` predicate determines whether a directory is recursed. @@ -995,16 +726,16 @@ def visit( yield from visit(entry.path, recurse) -def absolutepath(path: str | os.PathLike[str]) -> Path: +def absolutepath(path: Union[Path, str]) -> Path: """Convert a path to an absolute path using os.path.abspath. Prefer this over Path.resolve() (see #6523). Prefer this over Path.absolute() (not public, doesn't normalize). """ - return Path(os.path.abspath(path)) + return Path(os.path.abspath(str(path))) -def commonpath(path1: Path, path2: Path) -> Path | None: +def commonpath(path1: Path, path2: Path) -> Optional[Path]: """Return the common part shared with the other path, or None if there is no common part. @@ -1045,6 +776,24 @@ def bestrelpath(directory: Path, dest: Path) -> str: ) +# Originates from py. path.local.copy(), with siginficant trims and adjustments. +# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True) +def copytree(source: Path, target: Path) -> None: + """Recursively copy a source directory to target.""" + assert source.is_dir() + for entry in visit(source, recurse=lambda entry: not entry.is_symlink()): + x = Path(entry) + relpath = x.relative_to(source) + newx = target / relpath + newx.parent.mkdir(exist_ok=True) + if x.is_symlink(): + newx.symlink_to(os.readlink(x)) + elif x.is_file(): + shutil.copyfile(x, newx) + elif x.is_dir(): + newx.mkdir(exist_ok=True) + + def safe_exists(p: Path) -> bool: """Like Path.exists(), but account for input arguments that might be too long (#11394).""" try: @@ -1053,11 +802,3 @@ def safe_exists(p: Path) -> bool: # ValueError: stat: path too long for Windows # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect return False - - -def samefile_nofollow(p1: Path, p2: Path) -> bool: - """Test whether two paths reference the same actual file or directory. - - Unlike Path.samefile(), does not resolve symlinks. - """ - return os.path.samestat(p1.lstat(), p2.lstat()) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/pytester.py b/Backend/venv/lib/python3.12/site-packages/_pytest/pytester.py index 1cd5f05d..cdfc2c04 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/pytester.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/pytester.py @@ -1,38 +1,37 @@ -# mypy: allow-untyped-defs """(Disabled by default) support for testing pytest and pytest plugins. PYTEST_DONT_REWRITE """ - -from __future__ import annotations - import collections.abc -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Sequence import contextlib -from fnmatch import fnmatch import gc import importlib -from io import StringIO import locale import os -from pathlib import Path import platform import re import shutil import subprocess import sys import traceback +from fnmatch import fnmatch +from io import StringIO +from pathlib import Path from typing import Any -from typing import Final -from typing import final +from typing import Callable +from typing import Dict +from typing import Generator from typing import IO -from typing import Literal +from typing import Iterable +from typing import List +from typing import Optional from typing import overload +from typing import Sequence from typing import TextIO +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING +from typing import Union from weakref import WeakKeyDictionary from iniconfig import IniConfig @@ -41,6 +40,7 @@ from iniconfig import SectionWrapper from _pytest import timing from _pytest._code import Source from _pytest.capture import _get_multicapture +from _pytest.compat import final from _pytest.compat import NOTSET from _pytest.compat import NotSetType from _pytest.config import _PluggyPlugin @@ -61,14 +61,18 @@ from _pytest.outcomes import fail from _pytest.outcomes import importorskip from _pytest.outcomes import skip from _pytest.pathlib import bestrelpath +from _pytest.pathlib import copytree from _pytest.pathlib import make_numbered_dir from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.tmpdir import TempPathFactory -from _pytest.warning_types import PytestFDWarning +from _pytest.warning_types import PytestWarning if TYPE_CHECKING: + from typing_extensions import Final + from typing_extensions import Literal + import pexpect @@ -119,19 +123,14 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: - def get_open_files(self) -> list[tuple[str, str]]: - if sys.version_info >= (3, 11): - # New in Python 3.11, ignores utf-8 mode - encoding = locale.getencoding() - else: - encoding = locale.getpreferredencoding(False) + def get_open_files(self) -> List[Tuple[str, str]]: out = subprocess.run( ("lsof", "-Ffn0", "-p", str(os.getpid())), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, text=True, - encoding=encoding, + encoding=locale.getpreferredencoding(False), ).stdout def isopen(line: str) -> bool: @@ -164,38 +163,36 @@ class LsofFdLeakChecker: else: return True - @hookimpl(wrapper=True, tryfirst=True) - def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]: + @hookimpl(hookwrapper=True, tryfirst=True) + def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: lines1 = self.get_open_files() - try: - return (yield) - finally: - if hasattr(sys, "pypy_version_info"): - gc.collect() - lines2 = self.get_open_files() + yield + if hasattr(sys, "pypy_version_info"): + gc.collect() + lines2 = self.get_open_files() - new_fds = {t[0] for t in lines2} - {t[0] for t in lines1} - leaked_files = [t for t in lines2 if t[0] in new_fds] - if leaked_files: - error = [ - f"***** {len(leaked_files)} FD leakage detected", - *(str(f) for f in leaked_files), - "*** Before:", - *(str(f) for f in lines1), - "*** After:", - *(str(f) for f in lines2), - f"***** {len(leaked_files)} FD leakage detected", - "*** function {}:{}: {} ".format(*item.location), - "See issue #2366", - ] - item.warn(PytestFDWarning("\n".join(error))) + new_fds = {t[0] for t in lines2} - {t[0] for t in lines1} + leaked_files = [t for t in lines2 if t[0] in new_fds] + if leaked_files: + error = [ + "***** %s FD leakage detected" % len(leaked_files), + *(str(f) for f in leaked_files), + "*** Before:", + *(str(f) for f in lines1), + "*** After:", + *(str(f) for f in lines2), + "***** %s FD leakage detected" % len(leaked_files), + "*** function %s:%s: %s " % item.location, + "See issue #2366", + ] + item.warn(PytestWarning("\n".join(error))) # used at least by pytest-xdist plugin @fixture -def _pytest(request: FixtureRequest) -> PytestArg: +def _pytest(request: FixtureRequest) -> "PytestArg": """Return a helper which offers a gethookrecorder(hook) method which returns a HookRecorder instance which helps to make assertions about called hooks.""" @@ -206,13 +203,13 @@ class PytestArg: def __init__(self, request: FixtureRequest) -> None: self._request = request - def gethookrecorder(self, hook) -> HookRecorder: + def gethookrecorder(self, hook) -> "HookRecorder": hookrecorder = HookRecorder(hook._pm) self._request.addfinalizer(hookrecorder.finish_recording) return hookrecorder -def get_public_names(values: Iterable[str]) -> list[str]: +def get_public_names(values: Iterable[str]) -> List[str]: """Only return names from iterator values without a leading underscore.""" return [x for x in values if x[0] != "_"] @@ -242,7 +239,8 @@ class RecordedHookCall: if TYPE_CHECKING: # The class has undetermined attributes, this tells mypy about it. - def __getattr__(self, key: str): ... + def __getattr__(self, key: str): + ... @final @@ -261,8 +259,8 @@ class HookRecorder: check_ispytest(_ispytest) self._pluginmanager = pluginmanager - self.calls: list[RecordedHookCall] = [] - self.ret: int | ExitCode | None = None + self.calls: List[RecordedHookCall] = [] + self.ret: Optional[Union[int, ExitCode]] = None def before(hook_name: str, hook_impls, kwargs) -> None: self.calls.append(RecordedHookCall(hook_name, kwargs)) @@ -275,18 +273,17 @@ class HookRecorder: def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: + def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]: """Get all recorded calls to hooks with the given names (or name).""" if isinstance(names, str): names = names.split() return [call for call in self.calls if call._name in names] - def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: + def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) - # Since Python 3.13, f_locals is not a dict, but eval requires a dict. - backlocals = dict(sys._getframe(1).f_locals) + backlocals = sys._getframe(1).f_locals while entries: name, check = entries.pop(0) for ind, call in enumerate(self.calls[i:]): @@ -310,7 +307,7 @@ class HookRecorder: del self.calls[i] return call lines = [f"could not find call {name!r}, in:"] - lines.extend([f" {x}" for x in self.calls]) + lines.extend([" %s" % x for x in self.calls]) fail("\n".join(lines)) def getcall(self, name: str) -> RecordedHookCall: @@ -323,42 +320,45 @@ class HookRecorder: @overload def getreports( self, - names: Literal["pytest_collectreport"], - ) -> Sequence[CollectReport]: ... + names: "Literal['pytest_collectreport']", + ) -> Sequence[CollectReport]: + ... @overload def getreports( self, - names: Literal["pytest_runtest_logreport"], - ) -> Sequence[TestReport]: ... + names: "Literal['pytest_runtest_logreport']", + ) -> Sequence[TestReport]: + ... @overload def getreports( self, - names: str | Iterable[str] = ( + names: Union[str, Iterable[str]] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[CollectReport | TestReport]: ... + ) -> Sequence[Union[CollectReport, TestReport]]: + ... def getreports( self, - names: str | Iterable[str] = ( + names: Union[str, Iterable[str]] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[CollectReport | TestReport]: + ) -> Sequence[Union[CollectReport, TestReport]]: return [x.report for x in self.getcalls(names)] def matchreport( self, inamepart: str = "", - names: str | Iterable[str] = ( + names: Union[str, Iterable[str]] = ( "pytest_runtest_logreport", "pytest_collectreport", ), - when: str | None = None, - ) -> CollectReport | TestReport: + when: Optional[str] = None, + ) -> Union[CollectReport, TestReport]: """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): @@ -371,43 +371,48 @@ class HookRecorder: values.append(rep) if not values: raise ValueError( - f"could not find test report matching {inamepart!r}: " - "no test reports at all!" + "could not find test report matching %r: " + "no test reports at all!" % (inamepart,) ) if len(values) > 1: raise ValueError( - f"found 2 or more testreports matching {inamepart!r}: {values}" + "found 2 or more testreports matching {!r}: {}".format( + inamepart, values + ) ) return values[0] @overload def getfailures( self, - names: Literal["pytest_collectreport"], - ) -> Sequence[CollectReport]: ... + names: "Literal['pytest_collectreport']", + ) -> Sequence[CollectReport]: + ... @overload def getfailures( self, - names: Literal["pytest_runtest_logreport"], - ) -> Sequence[TestReport]: ... + names: "Literal['pytest_runtest_logreport']", + ) -> Sequence[TestReport]: + ... @overload def getfailures( self, - names: str | Iterable[str] = ( + names: Union[str, Iterable[str]] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[CollectReport | TestReport]: ... + ) -> Sequence[Union[CollectReport, TestReport]]: + ... def getfailures( self, - names: str | Iterable[str] = ( + names: Union[str, Iterable[str]] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[CollectReport | TestReport]: + ) -> Sequence[Union[CollectReport, TestReport]]: return [rep for rep in self.getreports(names) if rep.failed] def getfailedcollections(self) -> Sequence[CollectReport]: @@ -415,10 +420,10 @@ class HookRecorder: def listoutcomes( self, - ) -> tuple[ + ) -> Tuple[ Sequence[TestReport], - Sequence[CollectReport | TestReport], - Sequence[CollectReport | TestReport], + Sequence[Union[CollectReport, TestReport]], + Sequence[Union[CollectReport, TestReport]], ]: passed = [] skipped = [] @@ -437,7 +442,7 @@ class HookRecorder: failed.append(rep) return passed, skipped, failed - def countoutcomes(self) -> list[int]: + def countoutcomes(self) -> List[int]: return [len(x) for x in self.listoutcomes()] def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: @@ -457,14 +462,14 @@ class HookRecorder: @fixture -def linecomp() -> LineComp: +def linecomp() -> "LineComp": """A :class: `LineComp` instance for checking that an input linearly contains a sequence of strings.""" return LineComp() @fixture(name="LineMatcher") -def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: +def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: """A reference to the :class: `LineMatcher`. This is instantiable with a list of lines (without their trailing newlines). @@ -476,7 +481,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: @fixture def pytester( request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> Pytester: +) -> "Pytester": """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -491,7 +496,7 @@ def pytester( @fixture -def _sys_snapshot() -> Generator[None]: +def _sys_snapshot() -> Generator[None, None, None]: snappaths = SysPathsSnapshot() snapmods = SysModulesSnapshot() yield @@ -500,7 +505,7 @@ def _sys_snapshot() -> Generator[None]: @fixture -def _config_for_test() -> Generator[Config]: +def _config_for_test() -> Generator[Config, None, None]: from _pytest.config import get_config config = get_config() @@ -520,13 +525,13 @@ class RunResult: def __init__( self, - ret: int | ExitCode, - outlines: list[str], - errlines: list[str], + ret: Union[int, ExitCode], + outlines: List[str], + errlines: List[str], duration: float, ) -> None: try: - self.ret: int | ExitCode = ExitCode(ret) + self.ret: Union[int, ExitCode] = ExitCode(ret) """The return value.""" except ValueError: self.ret = ret @@ -547,13 +552,11 @@ class RunResult: def __repr__(self) -> str: return ( - f"" + "" + % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) ) - def parseoutcomes(self) -> dict[str, int]: + def parseoutcomes(self) -> Dict[str, int]: """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. @@ -566,7 +569,7 @@ class RunResult: return self.parse_summary_nouns(self.outlines) @classmethod - def parse_summary_nouns(cls, lines) -> dict[str, int]: + def parse_summary_nouns(cls, lines) -> Dict[str, int]: """Extract the nouns from a pytest terminal summary line. It always returns the plural noun for consistency:: @@ -597,8 +600,8 @@ class RunResult: errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: int | None = None, - deselected: int | None = None, + warnings: Optional[int] = None, + deselected: Optional[int] = None, ) -> None: """ Assert that the specified outcomes appear with the respective @@ -623,8 +626,16 @@ class RunResult: ) +class CwdSnapshot: + def __init__(self) -> None: + self.__saved = os.getcwd() + + def restore(self) -> None: + os.chdir(self.__saved) + + class SysModulesSnapshot: - def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: + def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -657,7 +668,7 @@ class Pytester: __test__ = False - CLOSE_STDIN: Final = NOTSET + CLOSE_STDIN: "Final" = NOTSET class TimeoutExpired(Exception): pass @@ -672,9 +683,9 @@ class Pytester: ) -> None: check_ispytest(_ispytest) self._request = request - self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( - WeakKeyDictionary() - ) + self._mod_collections: WeakKeyDictionary[ + Collector, List[Union[Item, Collector]] + ] = WeakKeyDictionary() if request.function: name: str = request.function.__name__ else: @@ -682,20 +693,19 @@ class Pytester: self._name = name self._path: Path = tmp_path_factory.mktemp(name, numbered=True) #: A list of plugins to use with :py:meth:`parseconfig` and - #: :py:meth:`runpytest`. Initially this is an empty list but plugins can - #: be added to the list. - #: - #: When running in subprocess mode, specify plugins by name (str) - adding - #: plugin objects directly is not supported. - self.plugins: list[str | _PluggyPlugin] = [] + #: :py:meth:`runpytest`. Initially this is an empty list but plugins can + #: be added to the list. The type of items to add to the list depends on + #: the method using them so refer to them for details. + self.plugins: List[Union[str, _PluggyPlugin]] = [] + self._cwd_snapshot = CwdSnapshot() self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() + self.chdir() self._request.addfinalizer(self._finalize) self._method = self._request.config.getoption("--runpytest") self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) self._monkeypatch = mp = monkeypatch - self.chdir() mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot)) # Ensure no unexpected caching via tox. mp.delenv("TOX_ENV_DIR", raising=False) @@ -726,6 +736,7 @@ class Pytester: """ self._sys_modules_snapshot.restore() self._sys_path_snapshot.restore() + self._cwd_snapshot.restore() def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: # Some zope modules used by twisted-related tests keep internal state @@ -750,26 +761,23 @@ class Pytester: This is done automatically upon instantiation. """ - self._monkeypatch.chdir(self.path) + os.chdir(self.path) def _makefile( self, ext: str, - lines: Sequence[Any | bytes], - files: dict[str, str], + lines: Sequence[Union[Any, bytes]], + files: Dict[str, str], encoding: str = "utf-8", ) -> Path: items = list(files.items()) - if ext is None: - raise TypeError("ext must not be None") - if ext and not ext.startswith("."): raise ValueError( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" ) - def to_text(s: Any | bytes) -> str: + def to_text(s: Union[Any, bytes]) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) if lines: @@ -822,7 +830,7 @@ class Pytester: return self._makefile(ext, args, kwargs) def makeconftest(self, source: str) -> Path: - """Write a conftest.py file. + """Write a contest.py file. :param source: The contents. :returns: The conftest.py file. @@ -837,16 +845,6 @@ class Pytester: """ return self.makefile(".ini", tox=source) - def maketoml(self, source: str) -> Path: - """Write a pytest.toml file. - - :param source: The contents. - :returns: The pytest.toml file. - - .. versionadded:: 9.0 - """ - return self.makefile(".toml", pytest=source) - def getinicfg(self, source: str) -> SectionWrapper: """Return the pytest section from the tox.ini config file.""" p = self.makeini(source) @@ -902,7 +900,9 @@ class Pytester: """ return self._makefile(".txt", args, kwargs) - def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: + def syspathinsert( + self, path: Optional[Union[str, "os.PathLike[str]"]] = None + ) -> None: """Prepend a directory to sys.path, defaults to :attr:`path`. This is undone automatically when this object dies at the end of each @@ -916,20 +916,19 @@ class Pytester: self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: str | os.PathLike[str]) -> Path: + def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: """Create a new (sub)directory. :param name: The name of the directory, relative to the pytester path. :returns: The created directory. - :rtype: pathlib.Path """ p = self.path / name p.mkdir() return p - def mkpydir(self, name: str | os.PathLike[str]) -> Path: + def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -940,14 +939,13 @@ class Pytester: p.joinpath("__init__.py").touch() return p - def copy_example(self, name: str | None = None) -> Path: + def copy_example(self, name: Optional[str] = None) -> Path: """Copy file from project's directory into the testdir. :param name: The name of the file to copy. :return: Path to the copied directory (inside ``self.path``). - :rtype: pathlib.Path """ example_dir_ = self._request.config.getini("pytester_example_dir") if example_dir_ is None: @@ -975,7 +973,7 @@ class Pytester: example_path = example_dir.joinpath(name) if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True) + copytree(example_path, self.path) return self.path elif example_path.is_file(): result = self.path.joinpath(example_path.name) @@ -986,7 +984,9 @@ class Pytester: f'example "{example_path}" is not found as a file or directory' ) - def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: + def getnode( + self, config: Config, arg: Union[str, "os.PathLike[str]"] + ) -> Union[Collector, Item]: """Get the collection node of a file. :param config: @@ -1005,7 +1005,9 @@ class Pytester: config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: + def getpathnode( + self, path: Union[str, "os.PathLike[str]"] + ) -> Union[Collector, Item]: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to @@ -1025,7 +1027,7 @@ class Pytester: config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: + def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the @@ -1037,7 +1039,7 @@ class Pytester: The collected items. """ session = colitems[0].session - result: list[Item] = [] + result: List[Item] = [] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -1048,7 +1050,7 @@ class Pytester: The calling test instance (class containing the test method) must provide a ``.getrunner()`` method which should return a runner which can run the test protocol for a single item, e.g. - ``_pytest.runner.runtestprotocol``. + :py:func:`_pytest.runner.runtestprotocol`. """ # used from runner functional tests item = self.getitem(source) @@ -1068,11 +1070,11 @@ class Pytester: :param cmdlineargs: Any extra command line arguments to use. """ p = self.makepyfile(source) - values = [*list(cmdlineargs), p] + values = list(cmdlineargs) + [p] return self.inline_run(*values) - def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: - """Run ``pytest.main(['--collect-only'])`` in-process. + def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: + """Run ``pytest.main(['--collectonly'])`` in-process. Runs the :py:func:`pytest.main` function to run all of pytest inside the test process itself like :py:meth:`inline_run`, but returns a @@ -1084,7 +1086,7 @@ class Pytester: def inline_run( self, - *args: str | os.PathLike[str], + *args: Union[str, "os.PathLike[str]"], plugins=(), no_reraise_ctrlc: bool = False, ) -> HookRecorder: @@ -1104,8 +1106,6 @@ class Pytester: Typically we reraise keyboard interrupts from the child run. If True, the KeyboardInterrupt exception is captured. """ - from _pytest.unraisableexception import gc_collect_iterations_key - # (maybe a cpython bug?) the importlib cache sometimes isn't updated # properly between file creation and inline_run (especially if imports # are interspersed with file creation) @@ -1129,16 +1129,11 @@ class Pytester: rec = [] - class PytesterHelperPlugin: - @staticmethod - def pytest_configure(config: Config) -> None: + class Collect: + def pytest_configure(x, config: Config) -> None: rec.append(self.make_hook_recorder(config.pluginmanager)) - # The unraisable plugin GC collect slows down inline - # pytester runs too much. - config.stash[gc_collect_iterations_key] = 0 - - plugins.append(PytesterHelperPlugin()) + plugins.append(Collect()) ret = main([str(x) for x in args], plugins=plugins) if len(rec) == 1: reprec = rec.pop() @@ -1161,7 +1156,7 @@ class Pytester: finalizer() def runpytest_inprocess( - self, *args: str | os.PathLike[str], **kwargs: Any + self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any ) -> RunResult: """Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.""" @@ -1169,7 +1164,7 @@ class Pytester: if syspathinsert: self.syspathinsert() - instant = timing.Instant() + now = timing.time() capture = _get_multicapture("sys") capture.start_capturing() try: @@ -1199,12 +1194,14 @@ class Pytester: assert reprec.ret is not None res = RunResult( - reprec.ret, out.splitlines(), err.splitlines(), instant.elapsed().seconds + reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now ) res.reprec = reprec # type: ignore return res - def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: + def runpytest( + self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any + ) -> RunResult: """Run pytest inline or in a subprocess, depending on the command line option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" new_args = self._ensure_basetemp(args) @@ -1215,19 +1212,17 @@ class Pytester: raise RuntimeError(f"Unrecognized runpytest option: {self._method}") def _ensure_basetemp( - self, args: Sequence[str | os.PathLike[str]] - ) -> list[str | os.PathLike[str]]: + self, args: Sequence[Union[str, "os.PathLike[str]"]] + ) -> List[Union[str, "os.PathLike[str]"]]: new_args = list(args) for x in new_args: if str(x).startswith("--basetemp"): break else: - new_args.append( - "--basetemp={}".format(self.path.parent.joinpath("basetemp")) - ) + new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp")) return new_args - def parseconfig(self, *args: str | os.PathLike[str]) -> Config: + def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: """Return a new pytest :class:`pytest.Config` instance from given commandline args. @@ -1241,16 +1236,17 @@ class Pytester: """ import _pytest.config - new_args = [str(x) for x in self._ensure_basetemp(args)] + new_args = self._ensure_basetemp(args) + new_args = [str(x) for x in new_args] - config = _pytest.config._prepareconfig(new_args, self.plugins) + config = _pytest.config._prepareconfig(new_args, self.plugins) # type: ignore[arg-type] # we don't know what the test will do with this half-setup config # object and thus we make sure it gets unconfigured properly in any # case (otherwise capturing could still be active, for example) self._request.addfinalizer(config._ensure_unconfigure) return config - def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: + def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like @@ -1262,7 +1258,7 @@ class Pytester: return config def getitem( - self, source: str | os.PathLike[str], funcname: str = "test_func" + self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" ) -> Item: """Return the test item for a test function. @@ -1281,9 +1277,11 @@ class Pytester: for item in items: if item.name == funcname: return item - assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" + assert 0, "{!r} item not found in module:\n{}\nitems: {}".format( + funcname, source, items + ) - def getitems(self, source: str | os.PathLike[str]) -> list[Item]: + def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: """Return all test items collected from the module. Writes the source to a Python file and runs pytest's collection on @@ -1294,7 +1292,7 @@ class Pytester: def getmodulecol( self, - source: str | os.PathLike[str], + source: Union[str, "os.PathLike[str]"], configargs=(), *, withinit: bool = False, @@ -1326,7 +1324,9 @@ class Pytester: self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: + def collect_by_name( + self, modcol: Collector, name: str + ) -> Optional[Union[Item, Collector]]: """Return the collection node for name from the module collection. Searches a module collection node for a collection node matching the @@ -1344,10 +1344,10 @@ class Pytester: def popen( self, - cmdargs: Sequence[str | os.PathLike[str]], - stdout: int | TextIO = subprocess.PIPE, - stderr: int | TextIO = subprocess.PIPE, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + cmdargs: Sequence[Union[str, "os.PathLike[str]"]], + stdout: Union[int, TextIO] = subprocess.PIPE, + stderr: Union[int, TextIO] = subprocess.PIPE, + stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1382,9 +1382,9 @@ class Pytester: def run( self, - *cmdargs: str | os.PathLike[str], - timeout: float | None = None, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + *cmdargs: Union[str, "os.PathLike[str]"], + timeout: Optional[float] = None, + stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1401,7 +1401,7 @@ class Pytester: :param stdin: Optional standard input. - - If it is ``CLOSE_STDIN`` (Default), then this method calls + - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and the standard input is closed immediately after the new command is started. @@ -1412,10 +1412,8 @@ class Pytester: - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. - :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int :returns: The result. - """ __tracebackhide__ = True @@ -1426,12 +1424,13 @@ class Pytester: print(" in:", Path.cwd()) with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2: - instant = timing.Instant() + now = timing.time() popen = self.popen( cmdargs, stdin=stdin, stdout=f1, stderr=f2, + close_fds=(sys.platform != "win32"), ) if popen.stdin is not None: popen.stdin.close() @@ -1439,7 +1438,10 @@ class Pytester: def handle_timeout() -> None: __tracebackhide__ = True - timeout_message = f"{timeout} second timeout expired running: {cmdargs}" + timeout_message = ( + "{seconds} second timeout expired running:" + " {command}".format(seconds=timeout, command=cmdargs) + ) popen.kill() popen.wait() @@ -1452,8 +1454,6 @@ class Pytester: ret = popen.wait(timeout) except subprocess.TimeoutExpired: handle_timeout() - f1.flush() - f2.flush() with p1.open(encoding="utf8") as f1, p2.open(encoding="utf8") as f2: out = f1.read().splitlines() @@ -1464,7 +1464,7 @@ class Pytester: with contextlib.suppress(ValueError): ret = ExitCode(ret) - return RunResult(ret, out, err, instant.elapsed().seconds) + return RunResult(ret, out, err, timing.time() - now) def _dump_lines(self, lines, fp): try: @@ -1473,10 +1473,10 @@ class Pytester: except UnicodeEncodeError: print(f"couldn't print to {fp} because of encoding") - def _getpytestargs(self) -> tuple[str, ...]: + def _getpytestargs(self) -> Tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script: os.PathLike[str]) -> RunResult: + def runpython(self, script: "os.PathLike[str]") -> RunResult: """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) @@ -1485,7 +1485,7 @@ class Pytester: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None + self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1505,18 +1505,16 @@ class Pytester: """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = (f"--basetemp={p}", *args) - for plugin in self.plugins: - if not isinstance(plugin, str): - raise ValueError( - f"Specifying plugins as objects is not supported in pytester subprocess mode; " - f"specify by name instead: {plugin}" - ) - args = ("-p", plugin, *args) + args = ("--basetemp=%s" % p,) + args + plugins = [x for x in self.plugins if isinstance(x, str)] + if plugins: + args = ("-p", plugins[0]) + args args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: + def spawn_pytest( + self, string: str, expect_timeout: float = 10.0 + ) -> "pexpect.spawn": """Run pytest using pexpect. This makes sure to use the right pytest and sets up the temporary @@ -1530,7 +1528,7 @@ class Pytester: cmd = f"{invoke} --basetemp={basetemp} {string}" return self.spawn(cmd, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": """Run a command using pexpect. The pexpect child is returned. @@ -1575,9 +1573,9 @@ class LineMatcher: ``text.splitlines()``. """ - def __init__(self, lines: list[str]) -> None: + def __init__(self, lines: List[str]) -> None: self.lines = lines - self._log_output: list[str] = [] + self._log_output: List[str] = [] def __str__(self) -> str: """Return the entire original text. @@ -1587,7 +1585,7 @@ class LineMatcher: """ return "\n".join(self.lines) - def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: + def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): @@ -1615,7 +1613,7 @@ class LineMatcher: self._log("matched: ", repr(line)) break else: - msg = f"line {line!r} not found in output" + msg = "line %r not found in output" % line self._log(msg) self._fail(msg) @@ -1627,7 +1625,7 @@ class LineMatcher: for i, line in enumerate(self.lines): if fnline == line or fnmatch(line, fnline): return self.lines[i + 1 :] - raise ValueError(f"line {fnline!r} not found in output") + raise ValueError("line %r not found in output" % fnline) def _log(self, *args) -> None: self._log_output.append(" ".join(str(x) for x in args)) @@ -1712,7 +1710,7 @@ class LineMatcher: started = True break elif match_func(nextline, line): - self._log(f"{match_nickname}:", repr(line)) + self._log("%s:" % match_nickname, repr(line)) self._log( "{:>{width}}".format("with:", width=wnick), repr(nextline) ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/pytester_assertions.py b/Backend/venv/lib/python3.12/site-packages/_pytest/pytester_assertions.py index 915cc8a1..657e4db5 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/pytester_assertions.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/pytester_assertions.py @@ -1,22 +1,23 @@ """Helper plugin for pytester; should not be loaded on its own.""" - # This plugin contains assertions used by pytester. pytester cannot # contain them itself, since it is imported by the `pytest` module, # hence cannot be subject to assertion rewriting, which requires a # module to not be already imported. -from __future__ import annotations - -from collections.abc import Sequence +from typing import Dict +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union from _pytest.reports import CollectReport from _pytest.reports import TestReport def assertoutcome( - outcomes: tuple[ + outcomes: Tuple[ Sequence[TestReport], - Sequence[CollectReport | TestReport], - Sequence[CollectReport | TestReport], + Sequence[Union[CollectReport, TestReport]], + Sequence[Union[CollectReport, TestReport]], ], passed: int = 0, skipped: int = 0, @@ -35,15 +36,15 @@ def assertoutcome( def assert_outcomes( - outcomes: dict[str, int], + outcomes: Dict[str, int], passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: int | None = None, - deselected: int | None = None, + warnings: Optional[int] = None, + deselected: Optional[int] = None, ) -> None: """Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.""" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/python.py b/Backend/venv/lib/python3.12/site-packages/_pytest/python.py index e6375187..5f8be5d9 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/python.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/python.py @@ -1,35 +1,32 @@ -# mypy: allow-untyped-defs """Python test discovery, setup and run of test functions.""" - -from __future__ import annotations - -import abc -from collections import Counter -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence import dataclasses import enum import fnmatch -from functools import partial import inspect import itertools import os -from pathlib import Path -import re -import textwrap +import sys import types -from typing import Any -from typing import cast -from typing import final -from typing import Literal -from typing import NoReturn -from typing import TYPE_CHECKING import warnings +from collections import Counter +from collections import defaultdict +from functools import partial +from pathlib import Path +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union import _pytest from _pytest import fixtures @@ -39,50 +36,77 @@ from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr from _pytest._code.code import Traceback +from _pytest._io import TerminalWriter from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped +from _pytest.compat import assert_never +from _pytest.compat import final from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func from _pytest.compat import getimfunc +from _pytest.compat import getlocation from _pytest.compat import is_async_function +from _pytest.compat import is_generator from _pytest.compat import LEGACY_PATH from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass +from _pytest.compat import STRING_TYPES from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.fixtures import FixtureDef -from _pytest.fixtures import FixtureRequest +from _pytest.deprecated import INSTANCE_COLLECTOR +from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FuncFixtureInfo -from _pytest.fixtures import get_scope_node from _pytest.main import Session +from _pytest.mark import MARK_GEN from _pytest.mark import ParameterSet -from _pytest.mark.structures import _HiddenParam from _pytest.mark.structures import get_unpacked_marks -from _pytest.mark.structures import HIDDEN_PARAM from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import normalize_mark_list from _pytest.outcomes import fail from _pytest.outcomes import skip +from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.pathlib import ImportPathMismatchError -from _pytest.pathlib import scandir -from _pytest.scope import _ScopeName +from _pytest.pathlib import parts +from _pytest.pathlib import visit from _pytest.scope import Scope -from _pytest.stash import StashKey from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestReturnNotNoneWarning - +from _pytest.warning_types import PytestUnhandledCoroutineWarning if TYPE_CHECKING: - from typing_extensions import Self + from typing_extensions import Literal + + from _pytest.scope import _ScopeName + + +_PYTEST_DIR = Path(_pytest.__file__).parent def pytest_addoption(parser: Parser) -> None: + group = parser.getgroup("general") + group.addoption( + "--fixtures", + "--funcargs", + action="store_true", + dest="showfixtures", + default=False, + help="Show available fixtures, sorted by plugin appearance " + "(fixtures with leading '_' are only shown with '-v')", + ) + group.addoption( + "--fixtures-per-test", + action="store_true", + dest="show_fixtures_per_test", + default=False, + help="Show fixtures per test", + ) parser.addini( "python_files", type="args", @@ -109,16 +133,19 @@ def pytest_addoption(parser: Parser) -> None: help="Disable string escape non-ASCII characters, might cause unwanted " "side effects(use at your own risk)", ) - parser.addini( - "strict_parametrization_ids", - type="bool", - # None => fallback to `strict`. - default=None, - help="Emit an error if non-unique parameter set IDs are detected", - ) -def pytest_generate_tests(metafunc: Metafunc) -> None: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: + if config.option.showfixtures: + showfixtures(config) + return 0 + if config.option.show_fixtures_per_test: + show_fixtures_per_test(config) + return 0 + return None + + +def pytest_generate_tests(metafunc: "Metafunc") -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) @@ -143,59 +170,45 @@ def pytest_configure(config: Config) -> None: ) -def async_fail(nodeid: str) -> None: - msg = ( - "async def functions are not natively supported.\n" +def async_warn_and_skip(nodeid: str) -> None: + msg = "async def functions are not natively supported and have been skipped.\n" + msg += ( "You need to install a suitable plugin for your async framework, for example:\n" - " - anyio\n" - " - pytest-asyncio\n" - " - pytest-tornasync\n" - " - pytest-trio\n" - " - pytest-twisted" ) - fail(msg, pytrace=False) + msg += " - anyio\n" + msg += " - pytest-asyncio\n" + msg += " - pytest-tornasync\n" + msg += " - pytest-trio\n" + msg += " - pytest-twisted" + warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid))) + skip(reason="async def function and no async plugin installed (see warnings)") @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: +def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: testfunction = pyfuncitem.obj if is_async_function(testfunction): - async_fail(pyfuncitem.nodeid) + async_warn_and_skip(pyfuncitem.nodeid) funcargs = pyfuncitem.funcargs testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} result = testfunction(**testargs) if hasattr(result, "__await__") or hasattr(result, "__aiter__"): - async_fail(pyfuncitem.nodeid) + async_warn_and_skip(pyfuncitem.nodeid) elif result is not None: warnings.warn( PytestReturnNotNoneWarning( - f"Test functions should return None, but {pyfuncitem.nodeid} returned {type(result)!r}.\n" - "Did you mean to use `assert` instead of `return`?\n" - "See https://docs.pytest.org/en/stable/how-to/assert.html#return-not-none for more information." + f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a " + "future version of pytest. Did you mean to use `assert` instead of `return`?" ) ) return True -def pytest_collect_directory( - path: Path, parent: nodes.Collector -) -> nodes.Collector | None: - pkginit = path / "__init__.py" - try: - has_pkginit = pkginit.is_file() - except PermissionError: - # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096. - return None - if has_pkginit: - return Package.from_parent(parent, path=path) - return None - - -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: +def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): if not path_matches_patterns( - file_path, parent.config.getini("python_files") + file_path, parent.config.getini("python_files") + ["__init__.py"] ): return None ihook = parent.session.gethookproxy(file_path) @@ -211,19 +224,24 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: - return Module.from_parent(parent, path=module_path) +def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": + if module_path.name == "__init__.py": + pkg: Package = Package.from_parent(parent, path=module_path) + return pkg + mod: Module = Module.from_parent(parent, path=module_path) + return mod @hookimpl(trylast=True) def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: - assert isinstance(collector, Class | Module), type(collector) + collector: Union["Module", "Class"], name: str, obj: object +) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): if collector.istestclass(obj, name): - return Class.from_parent(collector, name=name, obj=obj) + klass: Class = Class.from_parent(collector, name=name, obj=obj) + return klass elif collector.istestfunction(obj, name): # mock seems to store unbound methods (issue473), normalize it. obj = getattr(obj, "__func__", obj) @@ -234,20 +252,23 @@ def pytest_pycollect_makeitem( filename, lineno = getfslineno(obj) warnings.warn_explicit( message=PytestCollectionWarning( - f"cannot collect {name!r} because it is not a function." + "cannot collect %r because it is not a function." % name ), category=None, filename=str(filename), lineno=lineno + 1, ) elif getattr(obj, "__test__", True): - if inspect.isgeneratorfunction(obj): - fail( - f"'yield' keyword is allowed in fixtures, but not in tests ({name})", - pytrace=False, + if is_generator(obj): + res: Function = Function.from_parent(collector, name=name) + reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format( + name=name ) - return list(collector._genfunctions(name, obj)) - return None + res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) + res.warn(PytestCollectionWarning(reason)) + return res + else: + return list(collector._genfunctions(name, obj)) return None @@ -276,10 +297,10 @@ class PyobjMixin(nodes.Node): """Python instance object the function is bound to. Returns None if not a test method, e.g. for a standalone test function, - a class or a module. + a staticmethod, a class or a module. """ - # Overridden by Function. - return None + node = self.getparent(Function) + return getattr(node.obj, "__self__", None) if node is not None else None @property def obj(self): @@ -309,8 +330,10 @@ class PyobjMixin(nodes.Node): def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """Return Python path relative to the containing module.""" + chain = self.listchain() + chain.reverse() parts = [] - for node in self.iter_parents(): + for node in chain: name = node.name if isinstance(node, Module): name = os.path.splitext(name)[0] @@ -322,10 +345,22 @@ class PyobjMixin(nodes.Node): parts.reverse() return ".".join(parts) - def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: + def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: # XXX caching? - path, lineno = getfslineno(self.obj) + obj = self.obj + compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) + if isinstance(compat_co_firstlineno, int): + # nose compatibility + file_path = sys.modules[obj.__module__].__file__ + assert file_path is not None + if file_path.endswith(".pyc"): + file_path = file_path[:-1] + path: Union["os.PathLike[str]", str] = file_path + lineno = compat_co_firstlineno + else: + path, lineno = getfslineno(obj) modpath = self.getmodpath() + assert isinstance(lineno, int) return path, lineno, modpath @@ -334,7 +369,7 @@ class PyobjMixin(nodes.Node): # hook is not called for them. # fmt: off class _EmptyClass: pass # noqa: E701 -IGNORED_ATTRIBUTES = frozenset.union( +IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305 frozenset(), # Module. dir(types.ModuleType("empty_module")), @@ -349,7 +384,7 @@ del _EmptyClass # fmt: on -class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): +class PyCollector(PyobjMixin, nodes.Collector): def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) @@ -367,7 +402,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): - if isinstance(obj, staticmethod | classmethod): + if isinstance(obj, (staticmethod, classmethod)): # staticmethods and classmethods need to be unwrapped. obj = safe_getattr(obj, "__func__", False) return callable(obj) and fixtures.getfixturemarker(obj) is None @@ -375,15 +410,11 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): return False def istestclass(self, obj: object, name: str) -> bool: - if not (self.classnamefilter(name) or self.isnosetest(obj)): - return False - if inspect.isabstract(obj): - return False - return True + return self.classnamefilter(name) or self.isnosetest(obj) def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: """Check if the given name matches the prefix or glob-pattern defined - in configuration.""" + in ini configuration.""" for option in self.config.getini(option_name): if name.startswith(option): return True @@ -396,7 +427,7 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): return True return False - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: if not getattr(self.obj, "__test__", True): return [] @@ -408,12 +439,11 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): # In each class, nodes should be definition ordered. # __dict__ is definition ordered. - seen: set[str] = set() - dict_values: list[list[nodes.Item | nodes.Collector]] = [] - collect_imported_tests = self.session.config.getini("collect_imported_tests") + seen: Set[str] = set() + dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] ihook = self.ihook for dic in dicts: - values: list[nodes.Item | nodes.Collector] = [] + values: List[Union[nodes.Item, nodes.Collector]] = [] # Note: seems like the dict can change during iteration - # be careful not to remove the list() without consideration. for name, obj in list(dic.items()): @@ -422,13 +452,6 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): if name in seen: continue seen.add(name) - - if not collect_imported_tests and isinstance(self, Module): - # Do not collect functions and classes from other modules. - if inspect.isfunction(obj) or inspect.isclass(obj): - if obj.__module__ != self._getobj().__name__: - continue - res = ihook.pytest_pycollect_makeitem( collector=self, name=name, obj=obj ) @@ -447,12 +470,12 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): result.extend(values) return result - def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: + def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: modulecol = self.getparent(Module) assert modulecol is not None module = modulecol.obj clscol = self.getparent(Class) - cls = (clscol and clscol.obj) or None + cls = clscol and clscol.obj or None definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj) fixtureinfo = definition._fixtureinfo @@ -477,16 +500,17 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): if not metafunc._calls: yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) else: - metafunc._recompute_direct_params_indices() - # Direct parametrizations taking place in module/class-specific - # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure - # we update what the function really needs a.k.a its fixture closure. Note that - # direct parametrizations using `@pytest.mark.parametrize` have already been considered - # into making the closure using `ignore_args` arg to `getfixtureclosure`. + # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs. + fm = self.session._fixturemanager + fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm) + + # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures + # with direct parametrization, so make sure we update what the + # function really needs. fixtureinfo.prune_dependency_tree() for callspec in metafunc._calls: - subname = f"{name}[{callspec.id}]" if callspec._idlist else name + subname = f"{name}[{callspec.id}]" yield Function.from_parent( self, name=subname, @@ -497,110 +521,63 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): ) -def importtestmodule( - path: Path, - config: Config, -): - # We assume we are only called once per module. - importmode = config.getoption("--import-mode") - try: - mod = import_path( - path, - mode=importmode, - root=config.rootpath, - consider_namespace_packages=config.getini("consider_namespace_packages"), - ) - except SyntaxError as e: - raise nodes.Collector.CollectError( - ExceptionInfo.from_current().getrepr(style="short") - ) from e - except ImportPathMismatchError as e: - raise nodes.Collector.CollectError( - "import file mismatch:\n" - "imported module {!r} has this __file__ attribute:\n" - " {}\n" - "which is not the same as the test file we want to collect:\n" - " {}\n" - "HINT: remove __pycache__ / .pyc files and/or use a " - "unique basename for your test file modules".format(*e.args) - ) from e - except ImportError as e: - exc_info = ExceptionInfo.from_current() - if config.get_verbosity() < 2: - exc_info.traceback = exc_info.traceback.filter(filter_traceback) - exc_repr = ( - exc_info.getrepr(style="short") - if exc_info.traceback - else exc_info.exconly() - ) - formatted_tb = str(exc_repr) - raise nodes.Collector.CollectError( - f"ImportError while importing test module '{path}'.\n" - "Hint: make sure your test modules/packages have valid Python names.\n" - "Traceback:\n" - f"{formatted_tb}" - ) from e - except skip.Exception as e: - if e.allow_module_level: - raise - raise nodes.Collector.CollectError( - "Using pytest.skip outside of a test will skip the entire module. " - "If that's your intention, pass `allow_module_level=True`. " - "If you want to skip a specific test or an entire class, " - "use the @pytest.mark.skip or @pytest.mark.skipif decorators." - ) from e - config.pluginmanager.consider_module(mod) - return mod - - class Module(nodes.File, PyCollector): """Collector for test classes and functions in a Python module.""" def _getobj(self): - return importtestmodule(self.path, self.config) + return self._importtestmodule() - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - self._register_setup_module_fixture() - self._register_setup_function_fixture() + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + self._inject_setup_module_fixture() + self._inject_setup_function_fixture() self.session._fixturemanager.parsefactories(self) return super().collect() - def _register_setup_module_fixture(self) -> None: - """Register an autouse, module-scoped fixture for the collected module object + def _inject_setup_module_fixture(self) -> None: + """Inject a hidden autouse, module scoped fixture into the collected module object that invokes setUpModule/tearDownModule if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ + has_nose = self.config.pluginmanager.has_plugin("nose") setup_module = _get_first_non_fixture_func( self.obj, ("setUpModule", "setup_module") ) + if setup_module is None and has_nose: + # The name "setup" is too common - only treat as fixture if callable. + setup_module = _get_first_non_fixture_func(self.obj, ("setup",)) + if not callable(setup_module): + setup_module = None teardown_module = _get_first_non_fixture_func( self.obj, ("tearDownModule", "teardown_module") ) + if teardown_module is None and has_nose: + teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",)) + # Same as "setup" above - only treat as fixture if callable. + if not callable(teardown_module): + teardown_module = None if setup_module is None and teardown_module is None: return - def xunit_setup_module_fixture(request) -> Generator[None]: - module = request.module - if setup_module is not None: - _call_with_optional_argument(setup_module, module) - yield - if teardown_module is not None: - _call_with_optional_argument(teardown_module, module) - - self.session._fixturemanager._register_fixture( + @fixtures.fixture( + autouse=True, + scope="module", # Use a unique name to speed up lookup. name=f"_xunit_setup_module_fixture_{self.obj.__name__}", - func=xunit_setup_module_fixture, - nodeid=self.nodeid, - scope="module", - autouse=True, ) + def xunit_setup_module_fixture(request) -> Generator[None, None, None]: + if setup_module is not None: + _call_with_optional_argument(setup_module, request.module) + yield + if teardown_module is not None: + _call_with_optional_argument(teardown_module, request.module) - def _register_setup_function_fixture(self) -> None: - """Register an autouse, function-scoped fixture for the collected module object + self.obj.__pytest_setup_module = xunit_setup_module_fixture + + def _inject_setup_function_fixture(self) -> None: + """Inject a hidden autouse, function scoped fixture into the collected module object that invokes setup_function/teardown_function if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with @@ -613,58 +590,93 @@ class Module(nodes.File, PyCollector): if setup_function is None and teardown_function is None: return - def xunit_setup_function_fixture(request) -> Generator[None]: + @fixtures.fixture( + autouse=True, + scope="function", + # Use a unique name to speed up lookup. + name=f"_xunit_setup_function_fixture_{self.obj.__name__}", + ) + def xunit_setup_function_fixture(request) -> Generator[None, None, None]: if request.instance is not None: # in this case we are bound to an instance, so we need to let # setup_method handle this yield return - function = request.function if setup_function is not None: - _call_with_optional_argument(setup_function, function) + _call_with_optional_argument(setup_function, request.function) yield if teardown_function is not None: - _call_with_optional_argument(teardown_function, function) + _call_with_optional_argument(teardown_function, request.function) - self.session._fixturemanager._register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_function_fixture_{self.obj.__name__}", - func=xunit_setup_function_fixture, - nodeid=self.nodeid, - scope="function", - autouse=True, - ) + self.obj.__pytest_setup_function = xunit_setup_function_fixture + + def _importtestmodule(self): + # We assume we are only called once per module. + importmode = self.config.getoption("--import-mode") + try: + mod = import_path(self.path, mode=importmode, root=self.config.rootpath) + except SyntaxError as e: + raise self.CollectError( + ExceptionInfo.from_current().getrepr(style="short") + ) from e + except ImportPathMismatchError as e: + raise self.CollectError( + "import file mismatch:\n" + "imported module %r has this __file__ attribute:\n" + " %s\n" + "which is not the same as the test file we want to collect:\n" + " %s\n" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules" % e.args + ) from e + except ImportError as e: + exc_info = ExceptionInfo.from_current() + if self.config.getoption("verbose") < 2: + exc_info.traceback = exc_info.traceback.filter(filter_traceback) + exc_repr = ( + exc_info.getrepr(style="short") + if exc_info.traceback + else exc_info.exconly() + ) + formatted_tb = str(exc_repr) + raise self.CollectError( + "ImportError while importing test module '{path}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + "{traceback}".format(path=self.path, traceback=formatted_tb) + ) from e + except skip.Exception as e: + if e.allow_module_level: + raise + raise self.CollectError( + "Using pytest.skip outside of a test will skip the entire module. " + "If that's your intention, pass `allow_module_level=True`. " + "If you want to skip a specific test or an entire class, " + "use the @pytest.mark.skip or @pytest.mark.skipif decorators." + ) from e + self.config.pluginmanager.consider_module(mod) + return mod -class Package(nodes.Directory): +class Package(Module): """Collector for files and directories in a Python packages -- directories - with an `__init__.py` file. - - .. note:: - - Directories without an `__init__.py` file are instead collected by - :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` - collectors. - - .. versionchanged:: 8.0 - - Now inherits from :class:`~pytest.Directory`. - """ + with an `__init__.py` file.""" def __init__( self, - fspath: LEGACY_PATH | None, + fspath: Optional[LEGACY_PATH], parent: nodes.Collector, # NOTE: following args are unused: config=None, session=None, nodeid=None, - path: Path | None = None, + path: Optional[Path] = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. - # super().__init__(self, fspath, parent=parent) + # nodes.FSCollector.__init__(self, fspath, parent=parent) session = parent.session - super().__init__( + nodes.FSCollector.__init__( + self, fspath=fspath, path=path, parent=parent, @@ -672,51 +684,87 @@ class Package(nodes.Directory): session=session, nodeid=nodeid, ) + self.name = self.path.parent.name def setup(self) -> None: - init_mod = importtestmodule(self.path / "__init__.py", self.config) - # Not using fixtures to call setup_module here because autouse fixtures # from packages are not called automatically (#4085). setup_module = _get_first_non_fixture_func( - init_mod, ("setUpModule", "setup_module") + self.obj, ("setUpModule", "setup_module") ) if setup_module is not None: - _call_with_optional_argument(setup_module, init_mod) + _call_with_optional_argument(setup_module, self.obj) teardown_module = _get_first_non_fixture_func( - init_mod, ("tearDownModule", "teardown_module") + self.obj, ("tearDownModule", "teardown_module") ) if teardown_module is not None: - func = partial(_call_with_optional_argument, teardown_module, init_mod) + func = partial(_call_with_optional_argument, teardown_module, self.obj) self.addfinalizer(func) - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: - # Always collect __init__.py first. - def sort_key(entry: os.DirEntry[str]) -> object: - return (entry.name != "__init__.py", entry.name) + def _recurse(self, direntry: "os.DirEntry[str]") -> bool: + if direntry.name == "__pycache__": + return False + fspath = Path(direntry.path) + ihook = self.session.gethookproxy(fspath.parent) + if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): + return False + return True - config = self.config - col: nodes.Collector | None - cols: Sequence[nodes.Collector] - ihook = self.ihook - for direntry in scandir(self.path, sort_key): - if direntry.is_dir(): - path = Path(direntry.path) - if not self.session.isinitpath(path, with_parents=True): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - col = ihook.pytest_collect_directory(path=path, parent=self) - if col is not None: - yield col + def _collectfile( + self, fspath: Path, handle_dupes: bool = True + ) -> Sequence[nodes.Collector]: + assert ( + fspath.is_file() + ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( + fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() + ) + ihook = self.session.gethookproxy(fspath) + if not self.session.isinitpath(fspath): + if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): + return () - elif direntry.is_file(): - path = Path(direntry.path) - if not self.session.isinitpath(path): - if ihook.pytest_ignore_collect(collection_path=path, config=config): - continue - cols = ihook.pytest_collect_file(file_path=path, parent=self) - yield from cols + if handle_dupes: + keepduplicates = self.config.getoption("keepduplicates") + if not keepduplicates: + duplicate_paths = self.config.pluginmanager._duplicatepaths + if fspath in duplicate_paths: + return () + else: + duplicate_paths.add(fspath) + + return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] + + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + this_path = self.path.parent + + # Always collect the __init__ first. + if path_matches_patterns(self.path, self.config.getini("python_files")): + yield Module.from_parent(self, path=self.path) + + pkg_prefixes: Set[Path] = set() + for direntry in visit(str(this_path), recurse=self._recurse): + path = Path(direntry.path) + + # We will visit our own __init__.py file, in which case we skip it. + if direntry.is_file(): + if direntry.name == "__init__.py" and path.parent == this_path: + continue + + parts_ = parts(direntry.path) + if any( + str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path + for pkg_prefix in pkg_prefixes + ): + continue + + if direntry.is_file(): + yield from self._collectfile(path) + elif not direntry.is_dir(): + # Broken symlink or invalid/missing file. + continue + elif path.joinpath("__init__.py").is_file(): + pkg_prefixes.add(path) def _call_with_optional_argument(func, arg) -> None: @@ -731,12 +779,12 @@ def _call_with_optional_argument(func, arg) -> None: func() -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: +def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: """Return the attribute from the given object to be used as a setup/teardown xunit-style function, but only if not marked as a fixture to avoid calling it twice. """ for name in names: - meth: object | None = getattr(obj, name, None) + meth: Optional[object] = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: return meth return None @@ -746,22 +794,23 @@ class Class(PyCollector): """Collector for test methods (and nested classes) in a Python class.""" @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] + def from_parent(cls, parent, *, name, obj=None, **kw): """The public constructor.""" return super().from_parent(name=name, parent=parent, **kw) def newinstance(self): return self.obj() - def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): assert self.parent is not None self.warn( PytestCollectionWarning( - f"cannot collect test class {self.obj.__name__!r} because it has a " - f"__init__ constructor (from: {self.parent.nodeid})" + "cannot collect test class %r because it has a " + "__init__ constructor (from: %s)" + % (self.obj.__name__, self.parent.nodeid) ) ) return [] @@ -769,21 +818,22 @@ class Class(PyCollector): assert self.parent is not None self.warn( PytestCollectionWarning( - f"cannot collect test class {self.obj.__name__!r} because it has a " - f"__new__ constructor (from: {self.parent.nodeid})" + "cannot collect test class %r because it has a " + "__new__ constructor (from: %s)" + % (self.obj.__name__, self.parent.nodeid) ) ) return [] - self._register_setup_class_fixture() - self._register_setup_method_fixture() + self._inject_setup_class_fixture() + self._inject_setup_method_fixture() self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) return super().collect() - def _register_setup_class_fixture(self) -> None: - """Register an autouse, class scoped fixture into the collected class object + def _inject_setup_class_fixture(self) -> None: + """Inject a hidden autouse, class scoped fixture into the collected class object that invokes setup_class/teardown_class if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with @@ -794,58 +844,93 @@ class Class(PyCollector): if setup_class is None and teardown_class is None: return - def xunit_setup_class_fixture(request) -> Generator[None]: - cls = request.cls + @fixtures.fixture( + autouse=True, + scope="class", + # Use a unique name to speed up lookup. + name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", + ) + def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: if setup_class is not None: func = getimfunc(setup_class) - _call_with_optional_argument(func, cls) + _call_with_optional_argument(func, self.obj) yield if teardown_class is not None: func = getimfunc(teardown_class) - _call_with_optional_argument(func, cls) + _call_with_optional_argument(func, self.obj) - self.session._fixturemanager._register_fixture( - # Use a unique name to speed up lookup. - name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", - func=xunit_setup_class_fixture, - nodeid=self.nodeid, - scope="class", - autouse=True, - ) + self.obj.__pytest_setup_class = xunit_setup_class_fixture - def _register_setup_method_fixture(self) -> None: - """Register an autouse, function scoped fixture into the collected class object + def _inject_setup_method_fixture(self) -> None: + """Inject a hidden autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ + has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) + emit_nose_setup_warning = False + if setup_method is None and has_nose: + setup_name = "setup" + emit_nose_setup_warning = True + setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) + emit_nose_teardown_warning = False + if teardown_method is None and has_nose: + teardown_name = "teardown" + emit_nose_teardown_warning = True + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return - def xunit_setup_method_fixture(request) -> Generator[None]: - instance = request.instance - method = request.function - if setup_method is not None: - func = getattr(instance, setup_name) - _call_with_optional_argument(func, method) - yield - if teardown_method is not None: - func = getattr(instance, teardown_name) - _call_with_optional_argument(func, method) - - self.session._fixturemanager._register_fixture( + @fixtures.fixture( + autouse=True, + scope="function", # Use a unique name to speed up lookup. name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", - func=xunit_setup_method_fixture, - nodeid=self.nodeid, - scope="function", - autouse=True, ) + def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: + method = request.function + if setup_method is not None: + func = getattr(self, setup_name) + _call_with_optional_argument(func, method) + if emit_nose_setup_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="setup" + ), + stacklevel=2, + ) + yield + if teardown_method is not None: + func = getattr(self, teardown_name) + _call_with_optional_argument(func, method) + if emit_nose_teardown_warning: + warnings.warn( + NOSE_SUPPORT_METHOD.format( + nodeid=request.node.nodeid, method="teardown" + ), + stacklevel=2, + ) + + self.obj.__pytest_setup_method = xunit_setup_method_fixture + + +class InstanceDummy: + """Instance used to be a node type between Class and Function. It has been + removed in pytest 7.0. Some plugins exist which reference `pytest.Instance` + only to ignore it; this dummy class keeps them working. This will be removed + in pytest 8.""" + + +def __getattr__(name: str) -> object: + if name == "Instance": + warnings.warn(INSTANCE_COLLECTOR, 2) + return InstanceDummy + raise AttributeError(f"module {__name__} has no attribute {name}") def hasinit(obj: object) -> bool: @@ -869,12 +954,12 @@ class IdMaker: __slots__ = ( "argnames", - "config", - "func_name", + "parametersets", "idfn", "ids", + "config", "nodeid", - "parametersets", + "func_name", ) # The argnames of the parametrization. @@ -883,27 +968,24 @@ class IdMaker: parametersets: Sequence[ParameterSet] # Optionally, a user-provided callable to make IDs for parameters in a # ParameterSet. - idfn: Callable[[Any], object | None] | None + idfn: Optional[Callable[[Any], Optional[object]]] # Optionally, explicit IDs for ParameterSets by index. - ids: Sequence[object | None] | None + ids: Optional[Sequence[Optional[object]]] # Optionally, the pytest config. - # Used for controlling ASCII escaping, determining parametrization ID - # strictness, and for calling the :hook:`pytest_make_parametrize_id` hook. - config: Config | None + # Used for controlling ASCII escaping, and for calling the + # :hook:`pytest_make_parametrize_id` hook. + config: Optional[Config] # Optionally, the ID of the node being parametrized. # Used only for clearer error messages. - nodeid: str | None + nodeid: Optional[str] # Optionally, the ID of the function being parametrized. # Used only for clearer error messages. - func_name: str | None + func_name: Optional[str] - def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]: + def make_unique_parameterset_ids(self) -> List[str]: """Make a unique identifier for each ParameterSet, that may be used to identify the parametrization in a node ID. - If strict_parametrization_ids is enabled, and duplicates are detected, - raises CollectError. Otherwise makes the IDs unique as follows: - Format is -...-[counter], where prm_x_token is - user-provided id, if given - else an id derived from the value, applicable for certain types @@ -916,84 +998,29 @@ class IdMaker: if len(resolved_ids) != len(set(resolved_ids)): # Record the number of occurrences of each ID. id_counts = Counter(resolved_ids) - - if self._strict_parametrization_ids_enabled(): - parameters = ", ".join(self.argnames) - parametersets = ", ".join( - [saferepr(list(param.values)) for param in self.parametersets] - ) - ids = ", ".join( - id if id is not HIDDEN_PARAM else "" for id in resolved_ids - ) - duplicates = ", ".join( - id if id is not HIDDEN_PARAM else "" - for id, count in id_counts.items() - if count > 1 - ) - msg = textwrap.dedent(f""" - Duplicate parametrization IDs detected, but strict_parametrization_ids is set. - - Test name: {self.nodeid} - Parameters: {parameters} - Parameter sets: {parametersets} - IDs: {ids} - Duplicates: {duplicates} - - You can fix this problem using `@pytest.mark.parametrize(..., ids=...)` or `pytest.param(..., id=...)`. - """).strip() # noqa: E501 - raise nodes.Collector.CollectError(msg) - # Map the ID to its next suffix. - id_suffixes: dict[str, int] = defaultdict(int) + id_suffixes: Dict[str, int] = defaultdict(int) # Suffix non-unique IDs to make them unique. for index, id in enumerate(resolved_ids): if id_counts[id] > 1: - if id is HIDDEN_PARAM: - self._complain_multiple_hidden_parameter_sets() - suffix = "" - if id and id[-1].isdigit(): - suffix = "_" - new_id = f"{id}{suffix}{id_suffixes[id]}" - while new_id in set(resolved_ids): - id_suffixes[id] += 1 - new_id = f"{id}{suffix}{id_suffixes[id]}" - resolved_ids[index] = new_id + resolved_ids[index] = f"{id}{id_suffixes[id]}" id_suffixes[id] += 1 - assert len(resolved_ids) == len(set(resolved_ids)), ( - f"Internal error: {resolved_ids=}" - ) return resolved_ids - def _strict_parametrization_ids_enabled(self) -> bool: - if self.config is None: - return False - strict_parametrization_ids = self.config.getini("strict_parametrization_ids") - if strict_parametrization_ids is None: - strict_parametrization_ids = self.config.getini("strict") - return cast(bool, strict_parametrization_ids) - - def _resolve_ids(self) -> Iterable[str | _HiddenParam]: + def _resolve_ids(self) -> Iterable[str]: """Resolve IDs for all ParameterSets (may contain duplicates).""" for idx, parameterset in enumerate(self.parametersets): if parameterset.id is not None: # ID provided directly - pytest.param(..., id="...") - if parameterset.id is HIDDEN_PARAM: - yield HIDDEN_PARAM - else: - yield _ascii_escaped_by_config(parameterset.id, self.config) + yield parameterset.id elif self.ids and idx < len(self.ids) and self.ids[idx] is not None: # ID provided in the IDs list - parametrize(..., ids=[...]). - if self.ids[idx] is HIDDEN_PARAM: - yield HIDDEN_PARAM - else: - yield self._idval_from_value_required(self.ids[idx], idx) + yield self._idval_from_value_required(self.ids[idx], idx) else: # ID not provided - generate it. yield "-".join( self._idval(val, argname, idx) - for val, argname in zip( - parameterset.values, self.argnames, strict=True - ) + for val, argname in zip(parameterset.values, self.argnames) ) def _idval(self, val: object, argname: str, idx: int) -> str: @@ -1009,7 +1036,9 @@ class IdMaker: return idval return self._idval_from_argname(argname, idx) - def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: + def _idval_from_function( + self, val: object, argname: str, idx: int + ) -> Optional[str]: """Try to make an ID for a parameter in a ParameterSet using the user-provided id callable, if given.""" if self.idfn is None: @@ -1025,24 +1054,24 @@ class IdMaker: return None return self._idval_from_value(id) - def _idval_from_hook(self, val: object, argname: str) -> str | None: + def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: """Try to make an ID for a parameter in a ParameterSet by calling the :hook:`pytest_make_parametrize_id` hook.""" if self.config: - id: str | None = self.config.hook.pytest_make_parametrize_id( + id: Optional[str] = self.config.hook.pytest_make_parametrize_id( config=self.config, val=val, argname=argname ) return id return None - def _idval_from_value(self, val: object) -> str | None: + def _idval_from_value(self, val: object) -> Optional[str]: """Try to make an ID for a parameter in a ParameterSet from its value, if the value type is supported.""" - if isinstance(val, str | bytes): + if isinstance(val, STRING_TYPES): return _ascii_escaped_by_config(val, self.config) - elif val is None or isinstance(val, float | int | bool | complex): + elif val is None or isinstance(val, (float, int, bool, complex)): return str(val) - elif isinstance(val, re.Pattern): + elif isinstance(val, Pattern): return ascii_escaped(val.pattern) elif val is NOTSET: # Fallback to default. Note that NOTSET is an enum.Enum. @@ -1062,7 +1091,12 @@ class IdMaker: return id # Fail. - prefix = self._make_error_prefix() + if self.func_name is not None: + prefix = f"In {self.func_name}: " + elif self.nodeid is not None: + prefix = f"In {self.nodeid}: " + else: + prefix = "" msg = ( f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. " "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__." @@ -1075,21 +1109,6 @@ class IdMaker: and the index of the ParameterSet.""" return str(argname) + str(idx) - def _complain_multiple_hidden_parameter_sets(self) -> NoReturn: - fail( - f"{self._make_error_prefix()}multiple instances of HIDDEN_PARAM " - "cannot be used in the same parametrize call, " - "because the tests names need to be unique." - ) - - def _make_error_prefix(self) -> str: - if self.func_name is not None: - return f"In {self.func_name}: " - elif self.nodeid is not None: - return f"In {self.nodeid}: " - else: - return "" - @final @dataclasses.dataclass(frozen=True) @@ -1101,46 +1120,54 @@ class CallSpec2: and stored in item.callspec. """ - # arg name -> arg value which will be passed to a fixture or pseudo-fixture - # of the same name. (indirect or direct parametrization respectively) - params: dict[str, object] = dataclasses.field(default_factory=dict) + # arg name -> arg value which will be passed to the parametrized test + # function (direct parameterization). + funcargs: Dict[str, object] = dataclasses.field(default_factory=dict) + # arg name -> arg value which will be passed to a fixture of the same name + # (indirect parametrization). + params: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: dict[str, int] = dataclasses.field(default_factory=dict) - # arg name -> parameter scope. + indices: Dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. - _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) + _arg2scope: Dict[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) + _idlist: List[str] = dataclasses.field(default_factory=list) # Marks which will be applied to the item. - marks: list[Mark] = dataclasses.field(default_factory=list) + marks: List[Mark] = dataclasses.field(default_factory=list) def setmulti( self, *, + valtypes: Mapping[str, "Literal['params', 'funcargs']"], argnames: Iterable[str], valset: Iterable[object], - id: str | _HiddenParam, - marks: Iterable[Mark | MarkDecorator], + id: str, + marks: Iterable[Union[Mark, MarkDecorator]], scope: Scope, param_index: int, - nodeid: str, - ) -> CallSpec2: + ) -> "CallSpec2": + funcargs = self.funcargs.copy() params = self.params.copy() indices = self.indices.copy() - arg2scope = dict(self._arg2scope) - for arg, val in zip(argnames, valset, strict=True): - if arg in params: - raise nodes.Collector.CollectError( - f"{nodeid}: duplicate parametrization of {arg!r}" - ) - params[arg] = val + arg2scope = self._arg2scope.copy() + for arg, val in zip(argnames, valset): + if arg in params or arg in funcargs: + raise ValueError(f"duplicate parametrization of {arg!r}") + valtype_for_arg = valtypes[arg] + if valtype_for_arg == "params": + params[arg] = val + elif valtype_for_arg == "funcargs": + funcargs[arg] = val + else: + assert_never(valtype_for_arg) indices[arg] = param_index arg2scope[arg] = scope return CallSpec2( + funcargs=funcargs, params=params, indices=indices, _arg2scope=arg2scope, - _idlist=self._idlist if id is HIDDEN_PARAM else [*self._idlist, id], + _idlist=[*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], ) @@ -1155,14 +1182,6 @@ class CallSpec2: return "-".join(self._idlist) -def get_direct_param_fixture_func(request: FixtureRequest) -> Any: - return request.param - - -# Used for storing pseudo fixturedefs for direct parametrization. -name2pseudofixturedef_key = StashKey[dict[str, FixtureDef[Any]]]() - - @final class Metafunc: """Objects passed to the :hook:`pytest_generate_tests` hook. @@ -1174,7 +1193,7 @@ class Metafunc: def __init__( self, - definition: FunctionDefinition, + definition: "FunctionDefinition", fixtureinfo: fixtures.FuncFixtureInfo, config: Config, cls=None, @@ -1205,24 +1224,24 @@ class Metafunc: self._arg2fixturedefs = fixtureinfo.name2fixturedefs # Result of parametrize(). - self._calls: list[CallSpec2] = [] - - self._params_directness: dict[str, Literal["indirect", "direct"]] = {} + self._calls: List[CallSpec2] = [] def parametrize( self, - argnames: str | Sequence[str], - argvalues: Iterable[ParameterSet | Sequence[object] | object], - indirect: bool | Sequence[str] = False, - ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, - scope: _ScopeName | None = None, + argnames: Union[str, Sequence[str]], + argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + indirect: Union[bool, Sequence[str]] = False, + ids: Optional[ + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] + ] = None, + scope: "Optional[_ScopeName]" = None, *, - _param_mark: Mark | None = None, + _param_mark: Optional[Mark] = None, ) -> None: """Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources - see about setting ``indirect`` to do it at test setup time instead. + see about setting indirect to do it rather than at test setup time. Can be called multiple times per test function (but only on different argument names), in which case each call parametrizes all previous @@ -1265,11 +1284,6 @@ class Metafunc: They are mapped to the corresponding index in ``argvalues``. ``None`` means to use the auto-generated id. - .. versionadded:: 8.4 - :ref:`hidden-param` means to hide the parameter set - from the test name. Can only be used at most 1 time, as - test names need to be unique. - If it is a callable it will be called for each entry in ``argvalues``, and the return value is used as part of the auto-generated id for the whole set (where parts are joined with @@ -1286,8 +1300,6 @@ class Metafunc: It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ - nodeid = self.definition.nodeid - argnames, parametersets = ParameterSet._for_parametrize( argnames, argvalues, @@ -1299,7 +1311,7 @@ class Metafunc: if "request" in argnames: fail( - f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", pytrace=False, ) @@ -1312,6 +1324,8 @@ class Metafunc: self._validate_if_using_arg_names(argnames, indirect) + arg_values_types = self._resolve_arg_value_types(argnames, indirect) + # Use any already (possibly) generated ids with parametrize Marks. if _param_mark and _param_mark._param_ids_from: generated_ids = _param_mark._param_ids_from._param_ids_generated @@ -1326,75 +1340,22 @@ class Metafunc: if _param_mark and _param_mark._param_ids_from and generated_ids is None: object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) - # Calculate directness. - arg_directness = self._resolve_args_directness(argnames, indirect) - self._params_directness.update(arg_directness) - - # Add direct parametrizations as fixturedefs to arg2fixturedefs by - # registering artificial "pseudo" FixtureDef's such that later at test - # setup time we can rely on FixtureDefs to exist for all argnames. - node = None - # For scopes higher than function, a "pseudo" FixtureDef might have - # already been created for the scope. We thus store and cache the - # FixtureDef on the node related to the scope. - if scope_ is Scope.Function: - name2pseudofixturedef = None - else: - collector = self.definition.parent - assert collector is not None - node = get_scope_node(collector, scope_) - if node is None: - # If used class scope and there is no class, use module-level - # collector (for now). - if scope_ is Scope.Class: - assert isinstance(collector, Module) - node = collector - # If used package scope and there is no package, use session - # (for now). - elif scope_ is Scope.Package: - node = collector.session - else: - assert False, f"Unhandled missing scope: {scope}" - default: dict[str, FixtureDef[Any]] = {} - name2pseudofixturedef = node.stash.setdefault( - name2pseudofixturedef_key, default - ) - for argname in argnames: - if arg_directness[argname] == "indirect": - continue - if name2pseudofixturedef is not None and argname in name2pseudofixturedef: - fixturedef = name2pseudofixturedef[argname] - else: - fixturedef = FixtureDef( - config=self.config, - baseid="", - argname=argname, - func=get_direct_param_fixture_func, - scope=scope_, - params=None, - ids=None, - _ispytest=True, - ) - if name2pseudofixturedef is not None: - name2pseudofixturedef[argname] = fixturedef - self._arg2fixturedefs[argname] = [fixturedef] - # Create the new calls: if we are parametrize() multiple times (by applying the decorator # more than once) then we accumulate those calls generating the cartesian product # of all calls. newcalls = [] for callspec in self._calls or [CallSpec2()]: for param_index, (param_id, param_set) in enumerate( - zip(ids, parametersets, strict=True) + zip(ids, parametersets) ): newcallspec = callspec.setmulti( + valtypes=arg_values_types, argnames=argnames, valset=param_set.values, id=param_id, marks=param_set.marks, scope=scope_, param_index=param_index, - nodeid=nodeid, ) newcalls.append(newcallspec) self._calls = newcalls @@ -1402,10 +1363,12 @@ class Metafunc: def _resolve_parameter_set_ids( self, argnames: Sequence[str], - ids: Iterable[object | None] | Callable[[Any], object | None] | None, + ids: Optional[ + Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] + ], parametersets: Sequence[ParameterSet], nodeid: str, - ) -> list[str | _HiddenParam]: + ) -> List[str]: """Resolve the actual ids for the given parameter sets. :param argnames: @@ -1443,10 +1406,10 @@ class Metafunc: def _validate_ids( self, - ids: Iterable[object | None], + ids: Iterable[Optional[object]], parametersets: Sequence[ParameterSet], func_name: str, - ) -> list[object | None]: + ) -> List[Optional[object]]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1463,49 +1426,50 @@ class Metafunc: return list(itertools.islice(ids, num_ids)) - def _resolve_args_directness( + def _resolve_arg_value_types( self, argnames: Sequence[str], - indirect: bool | Sequence[str], - ) -> dict[str, Literal["indirect", "direct"]]: - """Resolve if each parametrized argument must be considered an indirect - parameter to a fixture of the same name, or a direct parameter to the - parametrized function, based on the ``indirect`` parameter of the - parametrized() call. + indirect: Union[bool, Sequence[str]], + ) -> Dict[str, "Literal['params', 'funcargs']"]: + """Resolve if each parametrized argument must be considered a + parameter to a fixture or a "funcarg" to the function, based on the + ``indirect`` parameter of the parametrized() call. - :param argnames: - List of argument names passed to ``parametrize()``. - :param indirect: - Same as the ``indirect`` parameter of ``parametrize()``. - :returns - A dict mapping each arg name to either "indirect" or "direct". + :param List[str] argnames: List of argument names passed to ``parametrize()``. + :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. + :rtype: Dict[str, str] + A dict mapping each arg name to either: + * "params" if the argname should be the parameter of a fixture of the same name. + * "funcargs" if the argname should be a parameter to the parametrized test function. """ - arg_directness: dict[str, Literal["indirect", "direct"]] if isinstance(indirect, bool): - arg_directness = dict.fromkeys( - argnames, "indirect" if indirect else "direct" + valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys( + argnames, "params" if indirect else "funcargs" ) elif isinstance(indirect, Sequence): - arg_directness = dict.fromkeys(argnames, "direct") + valtypes = dict.fromkeys(argnames, "funcargs") for arg in indirect: if arg not in argnames: fail( - f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist", + "In {}: indirect fixture '{}' doesn't exist".format( + self.function.__name__, arg + ), pytrace=False, ) - arg_directness[arg] = "indirect" + valtypes[arg] = "params" else: fail( - f"In {self.function.__name__}: expected Sequence or boolean" - f" for indirect, got {type(indirect).__name__}", + "In {func}: expected Sequence or boolean for indirect, got {type}".format( + type=type(indirect).__name__, func=self.function.__name__ + ), pytrace=False, ) - return arg_directness + return valtypes def _validate_if_using_arg_names( self, argnames: Sequence[str], - indirect: bool | Sequence[str], + indirect: Union[bool, Sequence[str]], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1519,7 +1483,9 @@ class Metafunc: if arg not in self.fixturenames: if arg in default_arg_names: fail( - f"In {func_name}: function already takes an argument '{arg}' with a default value", + "In {}: function already takes an argument '{}' with a default value".format( + func_name, arg + ), pytrace=False, ) else: @@ -1532,17 +1498,11 @@ class Metafunc: pytrace=False, ) - def _recompute_direct_params_indices(self) -> None: - for argname, param_type in self._params_directness.items(): - if param_type == "direct": - for i, callspec in enumerate(self._calls): - callspec.indices[argname] = i - def _find_parametrized_scope( argnames: Sequence[str], arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: bool | Sequence[str], + indirect: Union[bool, Sequence[str]], ) -> Scope: """Find the most appropriate scope for a parametrized call based on its arguments. @@ -1561,7 +1521,7 @@ def _find_parametrized_scope( if all_arguments_are_fixtures: fixturedefs = arg2fixturedefs or {} used_scopes = [ - fixturedef[-1]._scope + fixturedef[0]._scope for name, fixturedef in fixturedefs.items() if name in argnames ] @@ -1571,7 +1531,7 @@ def _find_parametrized_scope( return Scope.Function -def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: +def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: if config is None: escape_option = False else: @@ -1584,6 +1544,138 @@ def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: return val if escape_option else ascii_escaped(val) # type: ignore +def _pretty_fixture_path(func) -> str: + cwd = Path.cwd() + loc = Path(getlocation(func, str(cwd))) + prefix = Path("...", "_pytest") + try: + return str(prefix / loc.relative_to(_PYTEST_DIR)) + except ValueError: + return bestrelpath(cwd, loc) + + +def show_fixtures_per_test(config): + from _pytest.main import wrap_session + + return wrap_session(config, _show_fixtures_per_test) + + +def _show_fixtures_per_test(config: Config, session: Session) -> None: + import _pytest.config + + session.perform_collect() + curdir = Path.cwd() + tw = _pytest.config.create_terminal_writer(config) + verbose = config.getvalue("verbose") + + def get_best_relpath(func) -> str: + loc = getlocation(func, str(curdir)) + return bestrelpath(curdir, Path(loc)) + + def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: + argname = fixture_def.argname + if verbose <= 0 and argname.startswith("_"): + return + prettypath = _pretty_fixture_path(fixture_def.func) + tw.write(f"{argname}", green=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + fixture_doc = inspect.getdoc(fixture_def.func) + if fixture_doc: + write_docstring( + tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc + ) + else: + tw.line(" no docstring available", red=True) + + def write_item(item: nodes.Item) -> None: + # Not all items have _fixtureinfo attribute. + info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) + if info is None or not info.name2fixturedefs: + # This test item does not use any fixtures. + return + tw.line() + tw.sep("-", f"fixtures used by {item.name}") + # TODO: Fix this type ignore. + tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined] + # dict key not used in loop but needed for sorting. + for _, fixturedefs in sorted(info.name2fixturedefs.items()): + assert fixturedefs is not None + if not fixturedefs: + continue + # Last item is expected to be the one used by the test item. + write_fixture(fixturedefs[-1]) + + for session_item in session.items: + write_item(session_item) + + +def showfixtures(config: Config) -> Union[int, ExitCode]: + from _pytest.main import wrap_session + + return wrap_session(config, _showfixtures_main) + + +def _showfixtures_main(config: Config, session: Session) -> None: + import _pytest.config + + session.perform_collect() + curdir = Path.cwd() + tw = _pytest.config.create_terminal_writer(config) + verbose = config.getvalue("verbose") + + fm = session._fixturemanager + + available = [] + seen: Set[Tuple[str, str]] = set() + + for argname, fixturedefs in fm._arg2fixturedefs.items(): + assert fixturedefs is not None + if not fixturedefs: + continue + for fixturedef in fixturedefs: + loc = getlocation(fixturedef.func, str(curdir)) + if (fixturedef.argname, loc) in seen: + continue + seen.add((fixturedef.argname, loc)) + available.append( + ( + len(fixturedef.baseid), + fixturedef.func.__module__, + _pretty_fixture_path(fixturedef.func), + fixturedef.argname, + fixturedef, + ) + ) + + available.sort() + currentmodule = None + for baseid, module, prettypath, argname, fixturedef in available: + if currentmodule != module: + if not module.startswith("_pytest."): + tw.line() + tw.sep("-", f"fixtures defined from {module}") + currentmodule = module + if verbose <= 0 and argname.startswith("_"): + continue + tw.write(f"{argname}", green=True) + if fixturedef.scope != "function": + tw.write(" [%s scope]" % fixturedef.scope, cyan=True) + tw.write(f" -- {prettypath}", yellow=True) + tw.write("\n") + doc = inspect.getdoc(fixturedef.func) + if doc: + write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc) + else: + tw.line(" no docstring available", red=True) + tw.line() + + +def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: + for line in doc.split("\n"): + tw.line(indent + line) + + class Function(PyobjMixin, nodes.Item): """Item responsible for setting up and executing a Python test function. @@ -1595,7 +1687,7 @@ class Function(PyobjMixin, nodes.Item): :param config: The pytest Config object. :param callspec: - If given, this function has been parametrized and the callspec contains + If given, this is function has been parametrized and the callspec contains meta information about the parametrization. :param callobj: If given, the object which will be called when the Function is invoked, @@ -1620,19 +1712,18 @@ class Function(PyobjMixin, nodes.Item): self, name: str, parent, - config: Config | None = None, - callspec: CallSpec2 | None = None, + config: Optional[Config] = None, + callspec: Optional[CallSpec2] = None, callobj=NOTSET, - keywords: Mapping[str, Any] | None = None, - session: Session | None = None, - fixtureinfo: FuncFixtureInfo | None = None, - originalname: str | None = None, + keywords: Optional[Mapping[str, Any]] = None, + session: Optional[Session] = None, + fixtureinfo: Optional[FuncFixtureInfo] = None, + originalname: Optional[str] = None, ) -> None: super().__init__(name, parent, config=config, session=session) if callobj is not NOTSET: - self._obj = callobj - self._instance = getattr(callobj, "__self__", None) + self.obj = callobj #: Original function name, without any decorations (for example #: parametrization adds a ``"[...]"`` suffix to function names), used to access @@ -1661,52 +1752,33 @@ class Function(PyobjMixin, nodes.Item): self.keywords.update(keywords) if fixtureinfo is None: - fm = self.session._fixturemanager - fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls) + fixtureinfo = self.session._fixturemanager.getfixtureinfo( + self, self.obj, self.cls, funcargs=True + ) self._fixtureinfo: FuncFixtureInfo = fixtureinfo self.fixturenames = fixtureinfo.names_closure self._initrequest() - # todo: determine sound type limitations @classmethod - def from_parent(cls, parent, **kw) -> Self: + def from_parent(cls, parent, **kw): # todo: determine sound type limitations """The public constructor.""" return super().from_parent(parent=parent, **kw) def _initrequest(self) -> None: - self.funcargs: dict[str, object] = {} - self._request = fixtures.TopRequest(self, _ispytest=True) + self.funcargs: Dict[str, object] = {} + self._request = fixtures.FixtureRequest(self, _ispytest=True) @property def function(self): """Underlying python 'function' object.""" return getimfunc(self.obj) - @property - def instance(self): - try: - return self._instance - except AttributeError: - if isinstance(self.parent, Class): - # Each Function gets a fresh class instance. - self._instance = self._getinstance() - else: - self._instance = None - return self._instance - - def _getinstance(self): + def _getobj(self): + assert self.parent is not None if isinstance(self.parent, Class): # Each Function gets a fresh class instance. - return self.parent.newinstance() + parent_obj = self.parent.newinstance() else: - return None - - def _getobj(self): - instance = self.instance - if instance is not None: - parent_obj = instance - else: - assert self.parent is not None parent_obj = self.parent.obj # type: ignore[attr-defined] return getattr(parent_obj, self.originalname) @@ -1741,11 +1813,10 @@ class Function(PyobjMixin, nodes.Item): if self.config.getoption("tbstyle", "auto") == "auto": if len(ntraceback) > 2: ntraceback = Traceback( - ( - ntraceback[0], - *(t.with_repr_style("short") for t in ntraceback[1:-1]), - ntraceback[-1], - ) + entry + if i == 0 or i == len(ntraceback) - 1 + else entry.with_repr_style("short") + for i, entry in enumerate(ntraceback) ) return ntraceback @@ -1755,7 +1826,7 @@ class Function(PyobjMixin, nodes.Item): def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> str | TerminalRepr: + ) -> Union[str, TerminalRepr]: style = self.config.getoption("tbstyle", "auto") if style == "auto": style = "long" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/python_api.py b/Backend/venv/lib/python3.12/site-packages/_pytest/python_api.py index 1e389eb0..18335610 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/python_api.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/python_api.py @@ -1,31 +1,53 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - +import math +import pprint from collections.abc import Collection -from collections.abc import Mapping -from collections.abc import Sequence from collections.abc import Sized from decimal import Decimal -import math from numbers import Complex -import pprint -import sys +from types import TracebackType from typing import Any +from typing import Callable +from typing import cast +from typing import ContextManager +from typing import List +from typing import Mapping +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING - +from typing import TypeVar +from typing import Union if TYPE_CHECKING: from numpy import ndarray +import _pytest._code +from _pytest.compat import final +from _pytest.compat import STRING_TYPES +from _pytest.compat import overload +from _pytest.outcomes import fail + + +def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: + at_str = f" at {at}" if at else "" + return TypeError( + "cannot make approximate comparisons to non-numeric values: {!r} {}".format( + value, at_str + ) + ) + + def _compare_approx( full_object: object, - message_data: Sequence[tuple[str, str, str]], + message_data: Sequence[Tuple[str, str, str]], number_of_elements: int, different_ids: Sequence[object], max_abs_diff: float, max_rel_diff: float, -) -> list[str]: +) -> List[str]: message_list = list(message_data) message_list.insert(0, ("Index", "Obtained", "Expected")) max_sizes = [0, 0, 0] @@ -66,7 +88,7 @@ class ApproxBase: def __repr__(self) -> str: raise NotImplementedError - def _repr_compare(self, other_side: Any) -> list[str]: + def _repr_compare(self, other_side: Any) -> List[str]: return [ "comparison failed", f"Obtained: {other_side}", @@ -90,7 +112,7 @@ class ApproxBase: def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> ApproxScalar: + def _approx_scalar(self, x) -> "ApproxScalar": if isinstance(x, Decimal): return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) @@ -113,11 +135,9 @@ class ApproxBase: def _recursive_sequence_map(f, x): """Recursively map a function over a sequence of arbitrary depth""" - if isinstance(x, list | tuple): + if isinstance(x, (list, tuple)): seq_type = type(x) return seq_type(_recursive_sequence_map(f, xi) for xi in x) - elif _is_sequence_like(x): - return [_recursive_sequence_map(f, xi) for xi in x] else: return f(x) @@ -131,12 +151,12 @@ class ApproxNumpy(ApproxBase): ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]: + def _repr_compare(self, other_side: "ndarray") -> List[str]: import itertools import math def get_value_from_nested_list( - nested_list: list[Any], nd_index: tuple[Any, ...] + nested_list: List[Any], nd_index: Tuple[Any, ...] ) -> Any: """ Helper function to get the value out of a nested list, given an n-dimensional index. @@ -152,14 +172,10 @@ class ApproxNumpy(ApproxBase): self._approx_scalar, self.expected.tolist() ) - # convert other_side to numpy array to ensure shape attribute is available - other_side_as_array = _as_numpy_array(other_side) - assert other_side_as_array is not None - - if np_array_shape != other_side_as_array.shape: + if np_array_shape != other_side.shape: return [ "Impossible to compare arrays with different shapes.", - f"Shapes: {np_array_shape} and {other_side_as_array.shape}", + f"Shapes: {np_array_shape} and {other_side.shape}", ] number_of_elements = self.expected.size @@ -168,7 +184,7 @@ class ApproxNumpy(ApproxBase): different_ids = [] for index in itertools.product(*(range(i) for i in np_array_shape)): approx_value = get_value_from_nested_list(approx_side_as_seq, index) - other_value = get_value_from_nested_list(other_side_as_array, index) + other_value = get_value_from_nested_list(other_side, index) if approx_value != other_value: abs_diff = abs(approx_value.expected - other_value) max_abs_diff = max(max_abs_diff, abs_diff) @@ -181,7 +197,7 @@ class ApproxNumpy(ApproxBase): message_data = [ ( str(index), - str(get_value_from_nested_list(other_side_as_array, index)), + str(get_value_from_nested_list(other_side, index)), str(get_value_from_nested_list(approx_side_as_seq, index)), ) for index in different_ids @@ -231,23 +247,13 @@ class ApproxMapping(ApproxBase): with numeric values (the keys can be anything).""" def __repr__(self) -> str: - return f"approx({ ({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" + return "approx({!r})".format( + {k: self._approx_scalar(v) for k, v in self.expected.items()} + ) - def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: + def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: import math - if len(self.expected) != len(other_side): - return [ - "Impossible to compare mappings with different sizes.", - f"Lengths: {len(self.expected)} and {len(other_side)}", - ] - - if set(self.expected.keys()) != set(other_side.keys()): - return [ - "comparison failed.", - f"Mappings has different keys: expected {self.expected.keys()} but got {other_side.keys()}", - ] - approx_side_as_map = { k: self._approx_scalar(v) for k, v in self.expected.items() } @@ -257,26 +263,23 @@ class ApproxMapping(ApproxBase): max_rel_diff = -math.inf different_ids = [] for (approx_key, approx_value), other_value in zip( - approx_side_as_map.items(), other_side.values(), strict=True + approx_side_as_map.items(), other_side.values() ): if approx_value != other_value: if approx_value.expected is not None and other_value is not None: - try: - max_abs_diff = max( - max_abs_diff, abs(approx_value.expected - other_value) + max_abs_diff = max( + max_abs_diff, abs(approx_value.expected - other_value) + ) + if approx_value.expected == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), ) - if approx_value.expected == 0.0: - max_rel_diff = math.inf - else: - max_rel_diff = max( - max_rel_diff, - abs( - (approx_value.expected - other_value) - / approx_value.expected - ), - ) - except ZeroDivisionError: - pass different_ids.append(approx_key) message_data = [ @@ -321,9 +324,11 @@ class ApproxSequenceLike(ApproxBase): seq_type = type(self.expected) if seq_type not in (tuple, list): seq_type = list - return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" + return "approx({!r})".format( + seq_type(self._approx_scalar(x) for x in self.expected) + ) - def _repr_compare(self, other_side: Sequence[float]) -> list[str]: + def _repr_compare(self, other_side: Sequence[float]) -> List[str]: import math if len(self.expected) != len(other_side): @@ -339,21 +344,17 @@ class ApproxSequenceLike(ApproxBase): max_rel_diff = -math.inf different_ids = [] for i, (approx_value, other_value) in enumerate( - zip(approx_side_as_map, other_side, strict=True) + zip(approx_side_as_map, other_side) ): if approx_value != other_value: - try: - abs_diff = abs(approx_value.expected - other_value) - max_abs_diff = max(max_abs_diff, abs_diff) - # Ignore non-numbers for the diff calculations (#13012). - except TypeError: - pass + abs_diff = abs(approx_value.expected - other_value) + max_abs_diff = max(max_abs_diff, abs_diff) + if other_value == 0.0: + max_rel_diff = math.inf else: - if other_value == 0.0: - max_rel_diff = math.inf - else: - max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) + max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value)) different_ids.append(i) + message_data = [ (str(i), str(other_side[i]), str(approx_side_as_map[i])) for i in different_ids @@ -377,7 +378,7 @@ class ApproxSequenceLike(ApproxBase): return super().__eq__(actual) def _yield_comparisons(self, actual): - return zip(actual, self.expected, strict=True) + return zip(actual, self.expected) def _check_type(self) -> None: __tracebackhide__ = True @@ -392,8 +393,8 @@ class ApproxScalar(ApproxBase): # Using Real should be better than this Union, but not possible yet: # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 + DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 def __repr__(self) -> str: """Return a string communicating both the expected value and the @@ -404,21 +405,15 @@ class ApproxScalar(ApproxBase): # Don't show a tolerance for values that aren't compared using # tolerances, i.e. non-numerics and infinities. Need to call abs to # handle complex numbers, e.g. (inf + 1j). - if ( - isinstance(self.expected, bool) - or (not isinstance(self.expected, Complex | Decimal)) - or math.isinf(abs(self.expected) or isinstance(self.expected, bool)) + if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf( + abs(self.expected) # type: ignore[arg-type] ): return str(self.expected) # If a sensible tolerance can't be calculated, self.tolerance will # raise a ValueError. In this case, display '???'. try: - if 1e-3 <= self.tolerance < 1e3: - vetted_tolerance = f"{self.tolerance:n}" - else: - vetted_tolerance = f"{self.tolerance:.1e}" - + vetted_tolerance = f"{self.tolerance:.1e}" if ( isinstance(self.expected, Complex) and self.expected.imag @@ -433,42 +428,30 @@ class ApproxScalar(ApproxBase): def __eq__(self, actual) -> bool: """Return whether the given value is equal to the expected value within the pre-specified tolerance.""" - - def is_bool(val: Any) -> bool: - # Check if `val` is a native bool or numpy bool. - if isinstance(val, bool): - return True - if np := sys.modules.get("numpy"): - return isinstance(val, np.bool_) - return False - asarray = _as_numpy_array(actual) if asarray is not None: # Call ``__eq__()`` manually to prevent infinite-recursion with # numpy<1.13. See #3748. return all(self.__eq__(a) for a in asarray.flat) - # Short-circuit exact equality, except for bool and np.bool_ - if is_bool(self.expected) and not is_bool(actual): - return False - elif actual == self.expected: + # Short-circuit exact equality. + if actual == self.expected: return True # If either type is non-numeric, fall back to strict equality. # NB: we need Complex, rather than just Number, to ensure that __abs__, - # __sub__, and __float__ are defined. Also, consider bool to be - # non-numeric, even though it has the required arithmetic. - if is_bool(self.expected) or not ( - isinstance(self.expected, Complex | Decimal) - and isinstance(actual, Complex | Decimal) + # __sub__, and __float__ are defined. + if not ( + isinstance(self.expected, (Complex, Decimal)) + and isinstance(actual, (Complex, Decimal)) ): return False # Allow the user to control whether NaNs are considered equal to each # other or not. The abs() calls are for compatibility with complex # numbers. - if math.isnan(abs(self.expected)): - return self.nan_ok and math.isnan(abs(actual)) + if math.isnan(abs(self.expected)): # type: ignore[arg-type] + return self.nan_ok and math.isnan(abs(actual)) # type: ignore[arg-type] # Infinity shouldn't be approximately equal to anything but itself, but # if there's a relative tolerance, it will be infinite and infinity @@ -476,14 +459,15 @@ class ApproxScalar(ApproxBase): # case would have been short circuited above, so here we can just # return false if the expected value is infinite. The abs() call is # for compatibility with complex numbers. - if math.isinf(abs(self.expected)): + if math.isinf(abs(self.expected)): # type: ignore[arg-type] return False # Return true if the two numbers are within the tolerance. result: bool = abs(self.expected - actual) <= self.tolerance return result - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore @property def tolerance(self): @@ -539,25 +523,6 @@ class ApproxDecimal(ApproxScalar): DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") - def __repr__(self) -> str: - if isinstance(self.rel, float): - rel = Decimal.from_float(self.rel) - else: - rel = self.rel - - if isinstance(self.abs, float): - abs_ = Decimal.from_float(self.abs) - else: - abs_ = self.abs - - tol_str = "???" - if rel is not None and Decimal("1e-3") <= rel <= Decimal("1e3"): - tol_str = f"{rel:.1e}" - elif abs_ is not None: - tol_str = f"{abs_:.1e}" - - return f"{self.expected} ± {tol_str}" - def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: """Assert that two numbers (or two ordered sequences of numbers) are equal to each other @@ -659,10 +624,8 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) True - **Non-numeric types** - - You can also use ``approx`` to compare non-numeric types, or dicts and - sequences containing non-numeric types, in which case it falls back to + You can also use ``approx`` to compare nonnumeric types, or dicts and + sequences containing nonnumeric types, in which case it falls back to strict equality. This can be useful for comparing dicts and sequences that can contain optional values:: @@ -719,15 +682,6 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: from the `re_assert package `_. - - .. note:: - - Unlike built-in equality, this function considers - booleans unequal to numeric zero or one. For example:: - - >>> 1 == approx(True) - False - .. warning:: .. versionchanged:: 3.2 @@ -746,12 +700,13 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: .. versionchanged:: 3.7.1 ``approx`` raises ``TypeError`` when it encounters a dict value or - sequence element of non-numeric type. + sequence element of nonnumeric type. .. versionchanged:: 6.1.0 - ``approx`` falls back to strict equality for non-numeric types instead + ``approx`` falls back to strict equality for nonnumeric types instead of raising ``TypeError``. """ + # Delegate the comparison to a class that knows how to deal with the type # of the expected value (e.g. int, float, list, dict, numpy.array, etc). # @@ -770,16 +725,25 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: type[ApproxBase] = ApproxDecimal + cls: Type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): expected = _as_numpy_array(expected) cls = ApproxNumpy - elif _is_sequence_like(expected): + elif ( + hasattr(expected, "__getitem__") + and isinstance(expected, Sized) + # Type ignored because the error is wrong -- not unreachable. + and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] + ): cls = ApproxSequenceLike - elif isinstance(expected, Collection) and not isinstance(expected, str | bytes): - msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" + elif ( + isinstance(expected, Collection) + # Type ignored because the error is wrong -- not unreachable. + and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] + ): + msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}" raise TypeError(msg) else: cls = ApproxScalar @@ -787,14 +751,6 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: return cls(expected, rel, abs, nan_ok) -def _is_sequence_like(expected: object) -> bool: - return ( - hasattr(expected, "__getitem__") - and isinstance(expected, Sized) - and not isinstance(expected, str | bytes) - ) - - def _is_numpy_array(obj: object) -> bool: """ Return true if the given object is implicitly convertible to ndarray, @@ -803,11 +759,13 @@ def _is_numpy_array(obj: object) -> bool: return _as_numpy_array(obj) is not None -def _as_numpy_array(obj: object) -> ndarray | None: +def _as_numpy_array(obj: object) -> Optional["ndarray"]: """ Return an ndarray if the given object is implicitly convertible to ndarray, and numpy is already imported, otherwise None. """ + import sys + np: Any = sys.modules.get("numpy") if np is not None: # avoid infinite recursion on numpy scalars, which have __array__ @@ -818,3 +776,221 @@ def _as_numpy_array(obj: object) -> ndarray | None: elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"): return np.asarray(obj) return None + + +# builtin pytest.raises helper + +E = TypeVar("E", bound=BaseException) + + +@overload +def raises( + expected_exception: Union[Type[E], Tuple[Type[E], ...]], + *, + match: Optional[Union[str, Pattern[str]]] = ..., +) -> "RaisesContext[E]": + ... + + +@overload +def raises( # noqa: F811 + expected_exception: Union[Type[E], Tuple[Type[E], ...]], + func: Callable[..., Any], + *args: Any, + **kwargs: Any, +) -> _pytest._code.ExceptionInfo[E]: + ... + + +def raises( # noqa: F811 + expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any +) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: + r"""Assert that a code block/function call raises an exception. + + :param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception: + The expected exception type, or a tuple if one of multiple possible + exception types are expected. + :kwparam str | typing.Pattern[str] | None match: + If specified, a string containing a regular expression, + or a regular expression object, that is tested against the string + representation of the exception using :func:`re.search`. + + To match a literal string that may contain :ref:`special characters + `, the pattern can first be escaped with :func:`re.escape`. + + (This is only used when :py:func:`pytest.raises` is used as a context manager, + and passed through to the function otherwise. + When using :py:func:`pytest.raises` as a function, you can use: + ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) + + .. currentmodule:: _pytest._code + + Use ``pytest.raises`` as a context manager, which will capture the exception of the given + type:: + + >>> import pytest + >>> with pytest.raises(ZeroDivisionError): + ... 1/0 + + If the code block does not raise the expected exception (``ZeroDivisionError`` in the example + above), or no exception at all, the check will fail instead. + + You can also use the keyword argument ``match`` to assert that the + exception matches a text or regex:: + + >>> with pytest.raises(ValueError, match='must be 0 or None'): + ... raise ValueError("value must be 0 or None") + + >>> with pytest.raises(ValueError, match=r'must be \d+$'): + ... raise ValueError("value must be 42") + + The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the + details of the captured exception:: + + >>> with pytest.raises(ValueError) as exc_info: + ... raise ValueError("value must be 42") + >>> assert exc_info.type is ValueError + >>> assert exc_info.value.args[0] == "value must be 42" + + .. note:: + + When using ``pytest.raises`` as a context manager, it's worthwhile to + note that normal context manager rules apply and that the exception + raised *must* be the final line in the scope of the context manager. + Lines of code after that, within the scope of the context manager will + not be executed. For example:: + + >>> value = 15 + >>> with pytest.raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... assert exc_info.type is ValueError # this will not execute + + Instead, the following approach must be taken (note the difference in + scope):: + + >>> with pytest.raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... + >>> assert exc_info.type is ValueError + + **Using with** ``pytest.mark.parametrize`` + + When using :ref:`pytest.mark.parametrize ref` + it is possible to parametrize tests such that + some runs raise an exception and others do not. + + See :ref:`parametrizing_conditional_raising` for an example. + + **Legacy form** + + It is possible to specify a callable by passing a to-be-called lambda:: + + >>> raises(ZeroDivisionError, lambda: 1/0) + + + or you can specify an arbitrary callable with arguments:: + + >>> def f(x): return 1/x + ... + >>> raises(ZeroDivisionError, f, 0) + + >>> raises(ZeroDivisionError, f, x=0) + + + The form above is fully supported but discouraged for new code because the + context manager form is regarded as more readable and less error-prone. + + .. note:: + Similar to caught exception objects in Python, explicitly clearing + local references to returned ``ExceptionInfo`` objects can + help the Python interpreter speed up its garbage collection. + + Clearing those references breaks a reference cycle + (``ExceptionInfo`` --> caught exception --> frame stack raising + the exception --> current frame stack --> local variables --> + ``ExceptionInfo``) which makes Python keep all objects referenced + from that cycle (including all local variables in the current + frame) alive until the next cyclic garbage collection run. + More detailed information can be found in the official Python + documentation for :ref:`the try statement `. + """ + __tracebackhide__ = True + + if not expected_exception: + raise ValueError( + f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " + f"Raising exceptions is already understood as failing the test, so you don't need " + f"any special code to say 'this should never raise an exception'." + ) + if isinstance(expected_exception, type): + expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) + else: + expected_exceptions = expected_exception + for exc in expected_exceptions: + if not isinstance(exc, type) or not issubclass(exc, BaseException): + msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] + not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ + raise TypeError(msg.format(not_a)) + + message = f"DID NOT RAISE {expected_exception}" + + if not args: + match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) + if kwargs: + msg = "Unexpected keyword arguments passed to pytest.raises: " + msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" + raise TypeError(msg) + return RaisesContext(expected_exception, message, match) + else: + func = args[0] + if not callable(func): + raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") + try: + func(*args[1:], **kwargs) + except expected_exception as e: + return _pytest._code.ExceptionInfo.from_exception(e) + fail(message) + + +# This doesn't work with mypy for now. Use fail.Exception instead. +raises.Exception = fail.Exception # type: ignore + + +@final +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): + def __init__( + self, + expected_exception: Union[Type[E], Tuple[Type[E], ...]], + message: str, + match_expr: Optional[Union[str, Pattern[str]]] = None, + ) -> None: + self.expected_exception = expected_exception + self.message = message + self.match_expr = match_expr + self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None + + def __enter__(self) -> _pytest._code.ExceptionInfo[E]: + self.excinfo = _pytest._code.ExceptionInfo.for_later() + return self.excinfo + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: + __tracebackhide__ = True + if exc_type is None: + fail(self.message) + assert self.excinfo is not None + if not issubclass(exc_type, self.expected_exception): + return False + # Cast to narrow the exception type now that it's verified. + exc_info = cast(Tuple[Type[E], E, TracebackType], (exc_type, exc_val, exc_tb)) + self.excinfo.fill_unfilled(exc_info) + if self.match_expr is not None: + self.excinfo.match(self.match_expr) + return True diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/python_path.py b/Backend/venv/lib/python3.12/site-packages/_pytest/python_path.py new file mode 100644 index 00000000..cceabbca --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/python_path.py @@ -0,0 +1,24 @@ +import sys + +import pytest +from pytest import Config +from pytest import Parser + + +def pytest_addoption(parser: Parser) -> None: + parser.addini("pythonpath", type="paths", help="Add paths to sys.path", default=[]) + + +@pytest.hookimpl(tryfirst=True) +def pytest_load_initial_conftests(early_config: Config) -> None: + # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]` + for path in reversed(early_config.getini("pythonpath")): + sys.path.insert(0, str(path)) + + +@pytest.hookimpl(trylast=True) +def pytest_unconfigure(config: Config) -> None: + for path in config.getini("pythonpath"): + path_str = str(path) + if path_str in sys.path: + sys.path.remove(path_str) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/raises.py b/Backend/venv/lib/python3.12/site-packages/_pytest/raises.py deleted file mode 100644 index 7c246fde..00000000 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/raises.py +++ /dev/null @@ -1,1517 +0,0 @@ -from __future__ import annotations - -from abc import ABC -from abc import abstractmethod -import re -from re import Pattern -import sys -from textwrap import indent -from typing import Any -from typing import cast -from typing import final -from typing import Generic -from typing import get_args -from typing import get_origin -from typing import Literal -from typing import overload -from typing import TYPE_CHECKING -import warnings - -from _pytest._code import ExceptionInfo -from _pytest._code.code import stringify_exception -from _pytest.outcomes import fail -from _pytest.warning_types import PytestWarning - - -if TYPE_CHECKING: - from collections.abc import Callable - from collections.abc import Sequence - - # for some reason Sphinx does not play well with 'from types import TracebackType' - import types - from typing import TypeGuard - - from typing_extensions import ParamSpec - from typing_extensions import TypeVar - - P = ParamSpec("P") - - # this conditional definition is because we want to allow a TypeVar default - BaseExcT_co_default = TypeVar( - "BaseExcT_co_default", - bound=BaseException, - default=BaseException, - covariant=True, - ) - - # Use short name because it shows up in docs. - E = TypeVar("E", bound=BaseException, default=BaseException) -else: - from typing import TypeVar - - BaseExcT_co_default = TypeVar( - "BaseExcT_co_default", bound=BaseException, covariant=True - ) - -# RaisesGroup doesn't work with a default. -BaseExcT_co = TypeVar("BaseExcT_co", bound=BaseException, covariant=True) -BaseExcT_1 = TypeVar("BaseExcT_1", bound=BaseException) -BaseExcT_2 = TypeVar("BaseExcT_2", bound=BaseException) -ExcT_1 = TypeVar("ExcT_1", bound=Exception) -ExcT_2 = TypeVar("ExcT_2", bound=Exception) - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - from exceptiongroup import ExceptionGroup - - -# String patterns default to including the unicode flag. -_REGEX_NO_FLAGS = re.compile(r"").flags - - -# pytest.raises helper -@overload -def raises( - expected_exception: type[E] | tuple[type[E], ...], - *, - match: str | re.Pattern[str] | None = ..., - check: Callable[[E], bool] = ..., -) -> RaisesExc[E]: ... - - -@overload -def raises( - *, - match: str | re.Pattern[str], - # If exception_type is not provided, check() must do any typechecks itself. - check: Callable[[BaseException], bool] = ..., -) -> RaisesExc[BaseException]: ... - - -@overload -def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException]: ... - - -@overload -def raises( - expected_exception: type[E] | tuple[type[E], ...], - func: Callable[..., Any], - *args: Any, - **kwargs: Any, -) -> ExceptionInfo[E]: ... - - -def raises( - expected_exception: type[E] | tuple[type[E], ...] | None = None, - *args: Any, - **kwargs: Any, -) -> RaisesExc[BaseException] | ExceptionInfo[E]: - r"""Assert that a code block/function call raises an exception type, or one of its subclasses. - - :param expected_exception: - The expected exception type, or a tuple if one of multiple possible - exception types are expected. Note that subclasses of the passed exceptions - will also match. - - This is not a required parameter, you may opt to only use ``match`` and/or - ``check`` for verifying the raised exception. - - :kwparam str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception and its :pep:`678` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - (This is only used when ``pytest.raises`` is used as a context manager, - and passed through to the function otherwise. - When using ``pytest.raises`` as a function, you can use: - ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - - :kwparam Callable[[BaseException], bool] check: - - .. versionadded:: 8.4 - - If specified, a callable that will be called with the exception as a parameter - after checking the type and the match regex if specified. - If it returns ``True`` it will be considered a match, if not it will - be considered a failed match. - - - Use ``pytest.raises`` as a context manager, which will capture the exception of the given - type, or any of its subclasses:: - - >>> import pytest - >>> with pytest.raises(ZeroDivisionError): - ... 1/0 - - If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example - above), or no exception at all, the check will fail instead. - - You can also use the keyword argument ``match`` to assert that the - exception matches a text or regex:: - - >>> with pytest.raises(ValueError, match='must be 0 or None'): - ... raise ValueError("value must be 0 or None") - - >>> with pytest.raises(ValueError, match=r'must be \d+$'): - ... raise ValueError("value must be 42") - - The ``match`` argument searches the formatted exception string, which includes any - `PEP-678 `__ ``__notes__``: - - >>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP - ... e = ValueError("value must be 42") - ... e.add_note("had a note added") - ... raise e - - The ``check`` argument, if provided, must return True when passed the raised exception - for the match to be successful, otherwise an :exc:`AssertionError` is raised. - - >>> import errno - >>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES): - ... raise OSError(errno.EACCES, "no permission to view") - - The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the - details of the captured exception:: - - >>> with pytest.raises(ValueError) as exc_info: - ... raise ValueError("value must be 42") - >>> assert exc_info.type is ValueError - >>> assert exc_info.value.args[0] == "value must be 42" - - .. warning:: - - Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this:: - - # Careful, this will catch ANY exception raised. - with pytest.raises(Exception): - some_function() - - Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide - real bugs, where the user wrote this expecting a specific exception, but some other exception is being - raised due to a bug introduced during a refactoring. - - Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch - **any** exception raised. - - .. note:: - - When using ``pytest.raises`` as a context manager, it's worthwhile to - note that normal context manager rules apply and that the exception - raised *must* be the final line in the scope of the context manager. - Lines of code after that, within the scope of the context manager will - not be executed. For example:: - - >>> value = 15 - >>> with pytest.raises(ValueError) as exc_info: - ... if value > 10: - ... raise ValueError("value must be <= 10") - ... assert exc_info.type is ValueError # This will not execute. - - Instead, the following approach must be taken (note the difference in - scope):: - - >>> with pytest.raises(ValueError) as exc_info: - ... if value > 10: - ... raise ValueError("value must be <= 10") - ... - >>> assert exc_info.type is ValueError - - **Expecting exception groups** - - When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or - :exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`. - - **Using with** ``pytest.mark.parametrize`` - - When using :ref:`pytest.mark.parametrize ref` - it is possible to parametrize tests such that - some runs raise an exception and others do not. - - See :ref:`parametrizing_conditional_raising` for an example. - - .. seealso:: - - :ref:`assertraises` for more examples and detailed discussion. - - **Legacy form** - - It is possible to specify a callable by passing a to-be-called lambda:: - - >>> raises(ZeroDivisionError, lambda: 1/0) - - - or you can specify an arbitrary callable with arguments:: - - >>> def f(x): return 1/x - ... - >>> raises(ZeroDivisionError, f, 0) - - >>> raises(ZeroDivisionError, f, x=0) - - - The form above is fully supported but discouraged for new code because the - context manager form is regarded as more readable and less error-prone. - - .. note:: - Similar to caught exception objects in Python, explicitly clearing - local references to returned ``ExceptionInfo`` objects can - help the Python interpreter speed up its garbage collection. - - Clearing those references breaks a reference cycle - (``ExceptionInfo`` --> caught exception --> frame stack raising - the exception --> current frame stack --> local variables --> - ``ExceptionInfo``) which makes Python keep all objects referenced - from that cycle (including all local variables in the current - frame) alive until the next cyclic garbage collection run. - More detailed information can be found in the official Python - documentation for :ref:`the try statement `. - """ - __tracebackhide__ = True - - if not args: - if set(kwargs) - {"match", "check", "expected_exception"}: - msg = "Unexpected keyword arguments passed to pytest.raises: " - msg += ", ".join(sorted(kwargs)) - msg += "\nUse context-manager form instead?" - raise TypeError(msg) - - if expected_exception is None: - return RaisesExc(**kwargs) - return RaisesExc(expected_exception, **kwargs) - - if not expected_exception: - raise ValueError( - f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. " - f"Raising exceptions is already understood as failing the test, so you don't need " - f"any special code to say 'this should never raise an exception'." - ) - func = args[0] - if not callable(func): - raise TypeError(f"{func!r} object (type: {type(func)}) must be callable") - with RaisesExc(expected_exception) as excinfo: - func(*args[1:], **kwargs) - try: - return excinfo - finally: - del excinfo - - -# note: RaisesExc/RaisesGroup uses fail() internally, so this alias -# indicates (to [internal] plugins?) that `pytest.raises` will -# raise `_pytest.outcomes.Failed`, where -# `outcomes.Failed is outcomes.fail.Exception is raises.Exception` -# note: this is *not* the same as `_pytest.main.Failed` -# note: mypy does not recognize this attribute, and it's not possible -# to use a protocol/decorator like the others in outcomes due to -# https://github.com/python/mypy/issues/18715 -raises.Exception = fail.Exception # type: ignore[attr-defined] - - -def _match_pattern(match: Pattern[str]) -> str | Pattern[str]: - """Helper function to remove redundant `re.compile` calls when printing regex""" - return match.pattern if match.flags == _REGEX_NO_FLAGS else match - - -def repr_callable(fun: Callable[[BaseExcT_1], bool]) -> str: - """Get the repr of a ``check`` parameter. - - Split out so it can be monkeypatched (e.g. by hypothesis) - """ - return repr(fun) - - -def backquote(s: str) -> str: - return "`" + s + "`" - - -def _exception_type_name( - e: type[BaseException] | tuple[type[BaseException], ...], -) -> str: - if isinstance(e, type): - return e.__name__ - if len(e) == 1: - return e[0].__name__ - return "(" + ", ".join(ee.__name__ for ee in e) + ")" - - -def _check_raw_type( - expected_type: type[BaseException] | tuple[type[BaseException], ...] | None, - exception: BaseException, -) -> str | None: - if expected_type is None or expected_type == (): - return None - - if not isinstance( - exception, - expected_type, - ): - actual_type_str = backquote(_exception_type_name(type(exception)) + "()") - expected_type_str = backquote(_exception_type_name(expected_type)) - if ( - isinstance(exception, BaseExceptionGroup) - and isinstance(expected_type, type) - and not issubclass(expected_type, BaseExceptionGroup) - ): - return f"Unexpected nested {actual_type_str}, expected {expected_type_str}" - return f"{actual_type_str} is not an instance of {expected_type_str}" - return None - - -def is_fully_escaped(s: str) -> bool: - # we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped - metacharacters = "{}()+.*?^$[]" - return not any( - c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s) - ) - - -def unescape(s: str) -> str: - return re.sub(r"\\([{}()+-.*?^$\[\]\s\\])", r"\1", s) - - -# These classes conceptually differ from ExceptionInfo in that ExceptionInfo is tied, and -# constructed from, a particular exception - whereas these are constructed with expected -# exceptions, and later allow matching towards particular exceptions. -# But there's overlap in `ExceptionInfo.match` and `AbstractRaises._check_match`, as with -# `AbstractRaises.matches` and `ExceptionInfo.errisinstance`+`ExceptionInfo.group_contains`. -# The interaction between these classes should perhaps be improved. -class AbstractRaises(ABC, Generic[BaseExcT_co]): - """ABC with common functionality shared between RaisesExc and RaisesGroup""" - - def __init__( - self, - *, - match: str | Pattern[str] | None, - check: Callable[[BaseExcT_co], bool] | None, - ) -> None: - if isinstance(match, str): - # juggle error in order to avoid context to fail (necessary?) - re_error = None - try: - self.match: Pattern[str] | None = re.compile(match) - except re.error as e: - re_error = e - if re_error is not None: - fail(f"Invalid regex pattern provided to 'match': {re_error}") - if match == "": - warnings.warn( - PytestWarning( - "matching against an empty string will *always* pass. If you want " - "to check for an empty message you need to pass '^$'. If you don't " - "want to match you should pass `None` or leave out the parameter." - ), - stacklevel=2, - ) - else: - self.match = match - - # check if this is a fully escaped regex and has ^$ to match fully - # in which case we can do a proper diff on error - self.rawmatch: str | None = None - if isinstance(match, str) or ( - isinstance(match, Pattern) and match.flags == _REGEX_NO_FLAGS - ): - if isinstance(match, Pattern): - match = match.pattern - if ( - match - and match[0] == "^" - and match[-1] == "$" - and is_fully_escaped(match[1:-1]) - ): - self.rawmatch = unescape(match[1:-1]) - - self.check = check - self._fail_reason: str | None = None - - # used to suppress repeated printing of `repr(self.check)` - self._nested: bool = False - - # set in self._parse_exc - self.is_baseexception = False - - def _parse_exc( - self, exc: type[BaseExcT_1] | types.GenericAlias, expected: str - ) -> type[BaseExcT_1]: - if isinstance(exc, type) and issubclass(exc, BaseException): - if not issubclass(exc, Exception): - self.is_baseexception = True - return exc - # because RaisesGroup does not support variable number of exceptions there's - # still a use for RaisesExc(ExceptionGroup[Exception]). - origin_exc: type[BaseException] | None = get_origin(exc) - if origin_exc and issubclass(origin_exc, BaseExceptionGroup): - exc_type = get_args(exc)[0] - if ( - issubclass(origin_exc, ExceptionGroup) and exc_type in (Exception, Any) - ) or ( - issubclass(origin_exc, BaseExceptionGroup) - and exc_type in (BaseException, Any) - ): - if not issubclass(origin_exc, ExceptionGroup): - self.is_baseexception = True - return cast(type[BaseExcT_1], origin_exc) - else: - raise ValueError( - f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseException]` " - f"are accepted as generic types but got `{exc}`. " - f"As `raises` will catch all instances of the specified group regardless of the " - f"generic argument specific nested exceptions has to be checked " - f"with `RaisesGroup`." - ) - # unclear if the Type/ValueError distinction is even helpful here - msg = f"Expected {expected}, but got " - if isinstance(exc, type): # type: ignore[unreachable] - raise ValueError(msg + f"{exc.__name__!r}") - if isinstance(exc, BaseException): # type: ignore[unreachable] - raise TypeError(msg + f"an exception instance: {type(exc).__name__}") - raise TypeError(msg + repr(type(exc).__name__)) - - @property - def fail_reason(self) -> str | None: - """Set after a call to :meth:`matches` to give a human-readable reason for why the match failed. - When used as a context manager the string will be printed as the reason for the - test failing.""" - return self._fail_reason - - def _check_check( - self: AbstractRaises[BaseExcT_1], - exception: BaseExcT_1, - ) -> bool: - if self.check is None: - return True - - if self.check(exception): - return True - - check_repr = "" if self._nested else " " + repr_callable(self.check) - self._fail_reason = f"check{check_repr} did not return True" - return False - - # TODO: harmonize with ExceptionInfo.match - def _check_match(self, e: BaseException) -> bool: - if self.match is None or re.search( - self.match, - stringified_exception := stringify_exception( - e, include_subexception_msg=False - ), - ): - return True - - # if we're matching a group, make sure we're explicit to reduce confusion - # if they're trying to match an exception contained within the group - maybe_specify_type = ( - f" the `{_exception_type_name(type(e))}()`" - if isinstance(e, BaseExceptionGroup) - else "" - ) - if isinstance(self.rawmatch, str): - # TODO: it instructs to use `-v` to print leading text, but that doesn't work - # I also don't know if this is the proper entry point, or tool to use at all - from _pytest.assertion.util import _diff_text - from _pytest.assertion.util import dummy_highlighter - - diff = _diff_text(self.rawmatch, stringified_exception, dummy_highlighter) - self._fail_reason = ("\n" if diff[0][0] == "-" else "") + "\n".join(diff) - return False - - self._fail_reason = ( - f"Regex pattern did not match{maybe_specify_type}.\n" - f" Expected regex: {_match_pattern(self.match)!r}\n" - f" Actual message: {stringified_exception!r}" - ) - if _match_pattern(self.match) == stringified_exception: - self._fail_reason += "\n Did you mean to `re.escape()` the regex?" - return False - - @abstractmethod - def matches( - self: AbstractRaises[BaseExcT_1], exception: BaseException - ) -> TypeGuard[BaseExcT_1]: - """Check if an exception matches the requirements of this AbstractRaises. - If it fails, :meth:`AbstractRaises.fail_reason` should be set. - """ - - -@final -class RaisesExc(AbstractRaises[BaseExcT_co_default]): - """ - .. versionadded:: 8.4 - - - This is the class constructed when calling :func:`pytest.raises`, but may be used - directly as a helper class with :class:`RaisesGroup` when you want to specify - requirements on sub-exceptions. - - You don't need this if you only want to specify the type, since :class:`RaisesGroup` - accepts ``type[BaseException]``. - - :param type[BaseException] | tuple[type[BaseException]] | None expected_exception: - The expected type, or one of several possible types. - May be ``None`` in order to only make use of ``match`` and/or ``check`` - - The type is checked with :func:`isinstance`, and does not need to be an exact match. - If that is wanted you can use the ``check`` parameter. - - :kwparam str | Pattern[str] match: - A regex to match. - - :kwparam Callable[[BaseException], bool] check: - If specified, a callable that will be called with the exception as a parameter - after checking the type and the match regex if specified. - If it returns ``True`` it will be considered a match, if not it will - be considered a failed match. - - :meth:`RaisesExc.matches` can also be used standalone to check individual exceptions. - - Examples:: - - with RaisesGroup(RaisesExc(ValueError, match="string")) - ... - with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))): - ... - with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)): - ... - """ - - # Trio bundled hypothesis monkeypatching, we will probably instead assume that - # hypothesis will handle that in their pytest plugin by the time this is released. - # Alternatively we could add a version of get_pretty_function_description ourselves - # https://github.com/HypothesisWorks/hypothesis/blob/8ced2f59f5c7bea3344e35d2d53e1f8f8eb9fcd8/hypothesis-python/src/hypothesis/internal/reflection.py#L439 - - # At least one of the three parameters must be passed. - @overload - def __init__( - self, - expected_exception: ( - type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] - ), - /, - *, - match: str | Pattern[str] | None = ..., - check: Callable[[BaseExcT_co_default], bool] | None = ..., - ) -> None: ... - - @overload - def __init__( - self: RaisesExc[BaseException], # Give E a value. - /, - *, - match: str | Pattern[str] | None, - # If exception_type is not provided, check() must do any typechecks itself. - check: Callable[[BaseException], bool] | None = ..., - ) -> None: ... - - @overload - def __init__(self, /, *, check: Callable[[BaseException], bool]) -> None: ... - - def __init__( - self, - expected_exception: ( - type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None - ) = None, - /, - *, - match: str | Pattern[str] | None = None, - check: Callable[[BaseExcT_co_default], bool] | None = None, - ): - super().__init__(match=match, check=check) - if isinstance(expected_exception, tuple): - expected_exceptions = expected_exception - elif expected_exception is None: - expected_exceptions = () - else: - expected_exceptions = (expected_exception,) - - if (expected_exceptions == ()) and match is None and check is None: - raise ValueError("You must specify at least one parameter to match on.") - - self.expected_exceptions = tuple( - self._parse_exc(e, expected="a BaseException type") - for e in expected_exceptions - ) - - self._just_propagate = False - - def matches( - self, - exception: BaseException | None, - ) -> TypeGuard[BaseExcT_co_default]: - """Check if an exception matches the requirements of this :class:`RaisesExc`. - If it fails, :attr:`RaisesExc.fail_reason` will be set. - - Examples:: - - assert RaisesExc(ValueError).matches(my_exception): - # is equivalent to - assert isinstance(my_exception, ValueError) - - # this can be useful when checking e.g. the ``__cause__`` of an exception. - with pytest.raises(ValueError) as excinfo: - ... - assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__) - # above line is equivalent to - assert isinstance(excinfo.value.__cause__, SyntaxError) - assert re.search("foo", str(excinfo.value.__cause__) - - """ - self._just_propagate = False - if exception is None: - self._fail_reason = "exception is None" - return False - if not self._check_type(exception): - self._just_propagate = True - return False - - if not self._check_match(exception): - return False - - return self._check_check(exception) - - def __repr__(self) -> str: - parameters = [] - if self.expected_exceptions: - parameters.append(_exception_type_name(self.expected_exceptions)) - if self.match is not None: - # If no flags were specified, discard the redundant re.compile() here. - parameters.append( - f"match={_match_pattern(self.match)!r}", - ) - if self.check is not None: - parameters.append(f"check={repr_callable(self.check)}") - return f"RaisesExc({', '.join(parameters)})" - - def _check_type(self, exception: BaseException) -> TypeGuard[BaseExcT_co_default]: - self._fail_reason = _check_raw_type(self.expected_exceptions, exception) - return self._fail_reason is None - - def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]: - self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later() - return self.excinfo - - # TODO: move common code into superclass - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_type is None: - if not self.expected_exceptions: - fail("DID NOT RAISE any exception") - if len(self.expected_exceptions) > 1: - fail(f"DID NOT RAISE any of {self.expected_exceptions!r}") - - fail(f"DID NOT RAISE {self.expected_exceptions[0]!r}") - - assert self.excinfo is not None, ( - "Internal error - should have been constructed in __enter__" - ) - - if not self.matches(exc_val): - if self._just_propagate: - return False - raise AssertionError(self._fail_reason) - - # Cast to narrow the exception type now that it's verified.... - # even though the TypeGuard in self.matches should be narrowing - exc_info = cast( - "tuple[type[BaseExcT_co_default], BaseExcT_co_default, types.TracebackType]", - (exc_type, exc_val, exc_tb), - ) - self.excinfo.fill_unfilled(exc_info) - return True - - -@final -class RaisesGroup(AbstractRaises[BaseExceptionGroup[BaseExcT_co]]): - """ - .. versionadded:: 8.4 - - Contextmanager for checking for an expected :exc:`ExceptionGroup`. - This works similar to :func:`pytest.raises`, but allows for specifying the structure of an :exc:`ExceptionGroup`. - :meth:`ExceptionInfo.group_contains` also tries to handle exception groups, - but it is very bad at checking that you *didn't* get unexpected exceptions. - - The catching behaviour differs from :ref:`except* `, being much - stricter about the structure by default. - By using ``allow_unwrapped=True`` and ``flatten_subgroups=True`` you can match - :ref:`except* ` fully when expecting a single exception. - - :param args: - Any number of exception types, :class:`RaisesGroup` or :class:`RaisesExc` - to specify the exceptions contained in this exception. - All specified exceptions must be present in the raised group, *and no others*. - - If you expect a variable number of exceptions you need to use - :func:`pytest.raises(ExceptionGroup) ` and manually check - the contained exceptions. Consider making use of :meth:`RaisesExc.matches`. - - It does not care about the order of the exceptions, so - ``RaisesGroup(ValueError, TypeError)`` - is equivalent to - ``RaisesGroup(TypeError, ValueError)``. - :kwparam str | re.Pattern[str] | None match: - If specified, a string containing a regular expression, - or a regular expression object, that is tested against the string - representation of the exception group and its :pep:`678` `__notes__` - using :func:`re.search`. - - To match a literal string that may contain :ref:`special characters - `, the pattern can first be escaped with :func:`re.escape`. - - Note that " (5 subgroups)" will be stripped from the ``repr`` before matching. - :kwparam Callable[[E], bool] check: - If specified, a callable that will be called with the group as a parameter - after successfully matching the expected exceptions. If it returns ``True`` - it will be considered a match, if not it will be considered a failed match. - :kwparam bool allow_unwrapped: - If expecting a single exception or :class:`RaisesExc` it will match even - if the exception is not inside an exceptiongroup. - - Using this together with ``match``, ``check`` or expecting multiple exceptions - will raise an error. - :kwparam bool flatten_subgroups: - "flatten" any groups inside the raised exception group, extracting all exceptions - inside any nested groups, before matching. Without this it expects you to - fully specify the nesting structure by passing :class:`RaisesGroup` as expected - parameter. - - Examples:: - - with RaisesGroup(ValueError): - raise ExceptionGroup("", (ValueError(),)) - # match - with RaisesGroup( - ValueError, - ValueError, - RaisesExc(TypeError, match="^expected int$"), - match="^my group$", - ): - raise ExceptionGroup( - "my group", - [ - ValueError(), - TypeError("expected int"), - ValueError(), - ], - ) - # check - with RaisesGroup( - KeyboardInterrupt, - match="^hello$", - check=lambda x: isinstance(x.__cause__, ValueError), - ): - raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError - # nested groups - with RaisesGroup(RaisesGroup(ValueError)): - raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) - - # flatten_subgroups - with RaisesGroup(ValueError, flatten_subgroups=True): - raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) - - # allow_unwrapped - with RaisesGroup(ValueError, allow_unwrapped=True): - raise ValueError - - - :meth:`RaisesGroup.matches` can also be used directly to check a standalone exception group. - - - The matching algorithm is greedy, which means cases such as this may fail:: - - with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")): - raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye"))) - - even though it generally does not care about the order of the exceptions in the group. - To avoid the above you should specify the first :exc:`ValueError` with a :class:`RaisesExc` as well. - - .. note:: - When raised exceptions don't match the expected ones, you'll get a detailed error - message explaining why. This includes ``repr(check)`` if set, which in Python can be - overly verbose, showing memory locations etc etc. - - If installed and imported (in e.g. ``conftest.py``), the ``hypothesis`` library will - monkeypatch this output to provide shorter & more readable repr's. - """ - - # allow_unwrapped=True requires: singular exception, exception not being - # RaisesGroup instance, match is None, check is None - @overload - def __init__( - self, - expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - /, - *, - allow_unwrapped: Literal[True], - flatten_subgroups: bool = False, - ) -> None: ... - - # flatten_subgroups = True also requires no nested RaisesGroup - @overload - def __init__( - self, - expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - /, - *other_exceptions: type[BaseExcT_co] | RaisesExc[BaseExcT_co], - flatten_subgroups: Literal[True], - match: str | Pattern[str] | None = None, - check: Callable[[BaseExceptionGroup[BaseExcT_co]], bool] | None = None, - ) -> None: ... - - # simplify the typevars if possible (the following 3 are equivalent but go simpler->complicated) - # ... the first handles RaisesGroup[ValueError], the second RaisesGroup[ExceptionGroup[ValueError]], - # the third RaisesGroup[ValueError | ExceptionGroup[ValueError]]. - # ... otherwise, we will get results like RaisesGroup[ValueError | ExceptionGroup[Never]] (I think) - # (technically correct but misleading) - @overload - def __init__( - self: RaisesGroup[ExcT_1], - expected_exception: type[ExcT_1] | RaisesExc[ExcT_1], - /, - *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1], - match: str | Pattern[str] | None = None, - check: Callable[[ExceptionGroup[ExcT_1]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[ExceptionGroup[ExcT_2]], - expected_exception: RaisesGroup[ExcT_2], - /, - *other_exceptions: RaisesGroup[ExcT_2], - match: str | Pattern[str] | None = None, - check: Callable[[ExceptionGroup[ExceptionGroup[ExcT_2]]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[ExcT_1 | ExceptionGroup[ExcT_2]], - expected_exception: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], - /, - *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[[ExceptionGroup[ExcT_1 | ExceptionGroup[ExcT_2]]], bool] | None - ) = None, - ) -> None: ... - - # same as the above 3 but handling BaseException - @overload - def __init__( - self: RaisesGroup[BaseExcT_1], - expected_exception: type[BaseExcT_1] | RaisesExc[BaseExcT_1], - /, - *other_exceptions: type[BaseExcT_1] | RaisesExc[BaseExcT_1], - match: str | Pattern[str] | None = None, - check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | None = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[BaseExceptionGroup[BaseExcT_2]], - expected_exception: RaisesGroup[BaseExcT_2], - /, - *other_exceptions: RaisesGroup[BaseExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[[BaseExceptionGroup[BaseExceptionGroup[BaseExcT_2]]], bool] | None - ) = None, - ) -> None: ... - - @overload - def __init__( - self: RaisesGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], - expected_exception: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - /, - *other_exceptions: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - match: str | Pattern[str] | None = None, - check: ( - Callable[ - [BaseExceptionGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]]], - bool, - ] - | None - ) = None, - ) -> None: ... - - def __init__( - self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]], - expected_exception: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - /, - *other_exceptions: type[BaseExcT_1] - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2], - allow_unwrapped: bool = False, - flatten_subgroups: bool = False, - match: str | Pattern[str] | None = None, - check: ( - Callable[[BaseExceptionGroup[BaseExcT_1]], bool] - | Callable[[ExceptionGroup[ExcT_1]], bool] - | None - ) = None, - ): - # The type hint on the `self` and `check` parameters uses different formats - # that are *very* hard to reconcile while adhering to the overloads, so we cast - # it to avoid an error when passing it to super().__init__ - check = cast( - "Callable[[BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]], bool]", - check, - ) - super().__init__(match=match, check=check) - self.allow_unwrapped = allow_unwrapped - self.flatten_subgroups: bool = flatten_subgroups - self.is_baseexception = False - - if allow_unwrapped and other_exceptions: - raise ValueError( - "You cannot specify multiple exceptions with `allow_unwrapped=True.`" - " If you want to match one of multiple possible exceptions you should" - " use a `RaisesExc`." - " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`", - ) - if allow_unwrapped and isinstance(expected_exception, RaisesGroup): - raise ValueError( - "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`." - " You might want it in the expected `RaisesGroup`, or" - " `flatten_subgroups=True` if you don't care about the structure.", - ) - if allow_unwrapped and (match is not None or check is not None): - raise ValueError( - "`allow_unwrapped=True` bypasses the `match` and `check` parameters" - " if the exception is unwrapped. If you intended to match/check the" - " exception you should use a `RaisesExc` object. If you want to match/check" - " the exceptiongroup when the exception *is* wrapped you need to" - " do e.g. `if isinstance(exc.value, ExceptionGroup):" - " assert RaisesGroup(...).matches(exc.value)` afterwards.", - ) - - self.expected_exceptions: tuple[ - type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ... - ] = tuple( - self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup") - for e in ( - expected_exception, - *other_exceptions, - ) - ) - - def _parse_excgroup( - self, - exc: ( - type[BaseExcT_co] - | types.GenericAlias - | RaisesExc[BaseExcT_1] - | RaisesGroup[BaseExcT_2] - ), - expected: str, - ) -> type[BaseExcT_co] | RaisesExc[BaseExcT_1] | RaisesGroup[BaseExcT_2]: - # verify exception type and set `self.is_baseexception` - if isinstance(exc, RaisesGroup): - if self.flatten_subgroups: - raise ValueError( - "You cannot specify a nested structure inside a RaisesGroup with" - " `flatten_subgroups=True`. The parameter will flatten subgroups" - " in the raised exceptiongroup before matching, which would never" - " match a nested structure.", - ) - self.is_baseexception |= exc.is_baseexception - exc._nested = True - return exc - elif isinstance(exc, RaisesExc): - self.is_baseexception |= exc.is_baseexception - exc._nested = True - return exc - elif isinstance(exc, tuple): - raise TypeError( - f"Expected {expected}, but got {type(exc).__name__!r}.\n" - "RaisesGroup does not support tuples of exception types when expecting one of " - "several possible exception types like RaisesExc.\n" - "If you meant to expect a group with multiple exceptions, list them as separate arguments." - ) - else: - return super()._parse_exc(exc, expected) - - @overload - def __enter__( - self: RaisesGroup[ExcT_1], - ) -> ExceptionInfo[ExceptionGroup[ExcT_1]]: ... - @overload - def __enter__( - self: RaisesGroup[BaseExcT_1], - ) -> ExceptionInfo[BaseExceptionGroup[BaseExcT_1]]: ... - - def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[BaseException]]: - self.excinfo: ExceptionInfo[BaseExceptionGroup[BaseExcT_co]] = ( - ExceptionInfo.for_later() - ) - return self.excinfo - - def __repr__(self) -> str: - reqs = [ - e.__name__ if isinstance(e, type) else repr(e) - for e in self.expected_exceptions - ] - if self.allow_unwrapped: - reqs.append(f"allow_unwrapped={self.allow_unwrapped}") - if self.flatten_subgroups: - reqs.append(f"flatten_subgroups={self.flatten_subgroups}") - if self.match is not None: - # If no flags were specified, discard the redundant re.compile() here. - reqs.append(f"match={_match_pattern(self.match)!r}") - if self.check is not None: - reqs.append(f"check={repr_callable(self.check)}") - return f"RaisesGroup({', '.join(reqs)})" - - def _unroll_exceptions( - self, - exceptions: Sequence[BaseException], - ) -> Sequence[BaseException]: - """Used if `flatten_subgroups=True`.""" - res: list[BaseException] = [] - for exc in exceptions: - if isinstance(exc, BaseExceptionGroup): - res.extend(self._unroll_exceptions(exc.exceptions)) - - else: - res.append(exc) - return res - - @overload - def matches( - self: RaisesGroup[ExcT_1], - exception: BaseException | None, - ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... - @overload - def matches( - self: RaisesGroup[BaseExcT_1], - exception: BaseException | None, - ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... - - def matches( - self, - exception: BaseException | None, - ) -> bool: - """Check if an exception matches the requirements of this RaisesGroup. - If it fails, `RaisesGroup.fail_reason` will be set. - - Example:: - - with pytest.raises(TypeError) as excinfo: - ... - assert RaisesGroup(ValueError).matches(excinfo.value.__cause__) - # the above line is equivalent to - myexc = excinfo.value.__cause - assert isinstance(myexc, BaseExceptionGroup) - assert len(myexc.exceptions) == 1 - assert isinstance(myexc.exceptions[0], ValueError) - """ - self._fail_reason = None - if exception is None: - self._fail_reason = "exception is None" - return False - if not isinstance(exception, BaseExceptionGroup): - # we opt to only print type of the exception here, as the repr would - # likely be quite long - not_group_msg = f"`{type(exception).__name__}()` is not an exception group" - if len(self.expected_exceptions) > 1: - self._fail_reason = not_group_msg - return False - # if we have 1 expected exception, check if it would work even if - # allow_unwrapped is not set - res = self._check_expected(self.expected_exceptions[0], exception) - if res is None and self.allow_unwrapped: - return True - - if res is None: - self._fail_reason = ( - f"{not_group_msg}, but would match with `allow_unwrapped=True`" - ) - elif self.allow_unwrapped: - self._fail_reason = res - else: - self._fail_reason = not_group_msg - return False - - actual_exceptions: Sequence[BaseException] = exception.exceptions - if self.flatten_subgroups: - actual_exceptions = self._unroll_exceptions(actual_exceptions) - - if not self._check_match(exception): - self._fail_reason = cast(str, self._fail_reason) - old_reason = self._fail_reason - if ( - len(actual_exceptions) == len(self.expected_exceptions) == 1 - and isinstance(expected := self.expected_exceptions[0], type) - and isinstance(actual := actual_exceptions[0], expected) - and self._check_match(actual) - ): - assert self.match is not None, "can't be None if _check_match failed" - assert self._fail_reason is old_reason is not None - self._fail_reason += ( - f"\n" - f" but matched the expected `{self._repr_expected(expected)}`.\n" - f" You might want " - f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`" - ) - else: - self._fail_reason = old_reason - return False - - # do the full check on expected exceptions - if not self._check_exceptions( - exception, - actual_exceptions, - ): - self._fail_reason = cast(str, self._fail_reason) - assert self._fail_reason is not None - old_reason = self._fail_reason - # if we're not expecting a nested structure, and there is one, do a second - # pass where we try flattening it - if ( - not self.flatten_subgroups - and not any( - isinstance(e, RaisesGroup) for e in self.expected_exceptions - ) - and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions) - and self._check_exceptions( - exception, - self._unroll_exceptions(exception.exceptions), - ) - ): - # only indent if it's a single-line reason. In a multi-line there's already - # indented lines that this does not belong to. - indent = " " if "\n" not in self._fail_reason else "" - self._fail_reason = ( - old_reason - + f"\n{indent}Did you mean to use `flatten_subgroups=True`?" - ) - else: - self._fail_reason = old_reason - return False - - # Only run `self.check` once we know `exception` is of the correct type. - if not self._check_check(exception): - reason = ( - cast(str, self._fail_reason) + f" on the {type(exception).__name__}" - ) - if ( - len(actual_exceptions) == len(self.expected_exceptions) == 1 - and isinstance(expected := self.expected_exceptions[0], type) - # we explicitly break typing here :) - and self._check_check(actual_exceptions[0]) # type: ignore[arg-type] - ): - self._fail_reason = reason + ( - f", but did return True for the expected {self._repr_expected(expected)}." - f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))" - ) - else: - self._fail_reason = reason - return False - - return True - - @staticmethod - def _check_expected( - expected_type: ( - type[BaseException] | RaisesExc[BaseException] | RaisesGroup[BaseException] - ), - exception: BaseException, - ) -> str | None: - """Helper method for `RaisesGroup.matches` and `RaisesGroup._check_exceptions` - to check one of potentially several expected exceptions.""" - if isinstance(expected_type, type): - return _check_raw_type(expected_type, exception) - res = expected_type.matches(exception) - if res: - return None - assert expected_type.fail_reason is not None - if expected_type.fail_reason.startswith("\n"): - return f"\n{expected_type!r}: {indent(expected_type.fail_reason, ' ')}" - return f"{expected_type!r}: {expected_type.fail_reason}" - - @staticmethod - def _repr_expected(e: type[BaseException] | AbstractRaises[BaseException]) -> str: - """Get the repr of an expected type/RaisesExc/RaisesGroup, but we only want - the name if it's a type""" - if isinstance(e, type): - return _exception_type_name(e) - return repr(e) - - @overload - def _check_exceptions( - self: RaisesGroup[ExcT_1], - _exception: Exception, - actual_exceptions: Sequence[Exception], - ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ... - @overload - def _check_exceptions( - self: RaisesGroup[BaseExcT_1], - _exception: BaseException, - actual_exceptions: Sequence[BaseException], - ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ... - - def _check_exceptions( - self, - _exception: BaseException, - actual_exceptions: Sequence[BaseException], - ) -> bool: - """Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions""" - # The _exception parameter is not used, but necessary for the TypeGuard - - # full table with all results - results = ResultHolder(self.expected_exceptions, actual_exceptions) - - # (indexes of) raised exceptions that haven't (yet) found an expected - remaining_actual = list(range(len(actual_exceptions))) - # (indexes of) expected exceptions that haven't found a matching raised - failed_expected: list[int] = [] - # successful greedy matches - matches: dict[int, int] = {} - - # loop over expected exceptions first to get a more predictable result - for i_exp, expected in enumerate(self.expected_exceptions): - for i_rem in remaining_actual: - res = self._check_expected(expected, actual_exceptions[i_rem]) - results.set_result(i_exp, i_rem, res) - if res is None: - remaining_actual.remove(i_rem) - matches[i_exp] = i_rem - break - else: - failed_expected.append(i_exp) - - # All exceptions matched up successfully - if not remaining_actual and not failed_expected: - return True - - # in case of a single expected and single raised we simplify the output - if 1 == len(actual_exceptions) == len(self.expected_exceptions): - assert not matches - self._fail_reason = res - return False - - # The test case is failing, so we can do a slow and exhaustive check to find - # duplicate matches etc that will be helpful in debugging - for i_exp, expected in enumerate(self.expected_exceptions): - for i_actual, actual in enumerate(actual_exceptions): - if results.has_result(i_exp, i_actual): - continue - results.set_result( - i_exp, i_actual, self._check_expected(expected, actual) - ) - - successful_str = ( - f"{len(matches)} matched exception{'s' if len(matches) > 1 else ''}. " - if matches - else "" - ) - - # all expected were found - if not failed_expected and results.no_match_for_actual(remaining_actual): - self._fail_reason = ( - f"{successful_str}Unexpected exception(s):" - f" {[actual_exceptions[i] for i in remaining_actual]!r}" - ) - return False - # all raised exceptions were expected - if not remaining_actual and results.no_match_for_expected(failed_expected): - no_match_for_str = ", ".join( - self._repr_expected(self.expected_exceptions[i]) - for i in failed_expected - ) - self._fail_reason = f"{successful_str}Too few exceptions raised, found no match for: [{no_match_for_str}]" - return False - - # if there's only one remaining and one failed, and the unmatched didn't match anything else, - # we elect to only print why the remaining and the failed didn't match. - if ( - 1 == len(remaining_actual) == len(failed_expected) - and results.no_match_for_actual(remaining_actual) - and results.no_match_for_expected(failed_expected) - ): - self._fail_reason = f"{successful_str}{results.get_result(failed_expected[0], remaining_actual[0])}" - return False - - # there's both expected and raised exceptions without matches - s = "" - if matches: - s += f"\n{successful_str}" - indent_1 = " " * 2 - indent_2 = " " * 4 - - if not remaining_actual: - s += "\nToo few exceptions raised!" - elif not failed_expected: - s += "\nUnexpected exception(s)!" - - if failed_expected: - s += "\nThe following expected exceptions did not find a match:" - rev_matches = {v: k for k, v in matches.items()} - for i_failed in failed_expected: - s += ( - f"\n{indent_1}{self._repr_expected(self.expected_exceptions[i_failed])}" - ) - for i_actual, actual in enumerate(actual_exceptions): - if results.get_result(i_exp, i_actual) is None: - # we print full repr of match target - s += ( - f"\n{indent_2}It matches {backquote(repr(actual))} which was paired with " - + backquote( - self._repr_expected( - self.expected_exceptions[rev_matches[i_actual]] - ) - ) - ) - - if remaining_actual: - s += "\nThe following raised exceptions did not find a match" - for i_actual in remaining_actual: - s += f"\n{indent_1}{actual_exceptions[i_actual]!r}:" - for i_exp, expected in enumerate(self.expected_exceptions): - res = results.get_result(i_exp, i_actual) - if i_exp in failed_expected: - assert res is not None - if res[0] != "\n": - s += "\n" - s += indent(res, indent_2) - if res is None: - # we print full repr of match target - s += ( - f"\n{indent_2}It matches {backquote(self._repr_expected(expected))} " - f"which was paired with {backquote(repr(actual_exceptions[matches[i_exp]]))}" - ) - - if len(self.expected_exceptions) == len(actual_exceptions) and possible_match( - results - ): - s += ( - "\nThere exist a possible match when attempting an exhaustive check," - " but RaisesGroup uses a greedy algorithm. " - "Please make your expected exceptions more stringent with `RaisesExc` etc" - " so the greedy algorithm can function." - ) - self._fail_reason = s - return False - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_type is None: - fail(f"DID NOT RAISE any exception, expected `{self.expected_type()}`") - - assert self.excinfo is not None, ( - "Internal error - should have been constructed in __enter__" - ) - - # group_str is the only thing that differs between RaisesExc and RaisesGroup... - # I might just scrap it? Or make it part of fail_reason - group_str = ( - "(group)" - if self.allow_unwrapped and not issubclass(exc_type, BaseExceptionGroup) - else "group" - ) - - if not self.matches(exc_val): - fail(f"Raised exception {group_str} did not match: {self._fail_reason}") - - # Cast to narrow the exception type now that it's verified.... - # even though the TypeGuard in self.matches should be narrowing - exc_info = cast( - "tuple[type[BaseExceptionGroup[BaseExcT_co]], BaseExceptionGroup[BaseExcT_co], types.TracebackType]", - (exc_type, exc_val, exc_tb), - ) - self.excinfo.fill_unfilled(exc_info) - return True - - def expected_type(self) -> str: - subexcs = [] - for e in self.expected_exceptions: - if isinstance(e, RaisesExc): - subexcs.append(repr(e)) - elif isinstance(e, RaisesGroup): - subexcs.append(e.expected_type()) - elif isinstance(e, type): - subexcs.append(e.__name__) - else: # pragma: no cover - raise AssertionError("unknown type") - group_type = "Base" if self.is_baseexception else "" - return f"{group_type}ExceptionGroup({', '.join(subexcs)})" - - -@final -class NotChecked: - """Singleton for unchecked values in ResultHolder""" - - -class ResultHolder: - """Container for results of checking exceptions. - Used in RaisesGroup._check_exceptions and possible_match. - """ - - def __init__( - self, - expected_exceptions: tuple[ - type[BaseException] | AbstractRaises[BaseException], ... - ], - actual_exceptions: Sequence[BaseException], - ) -> None: - self.results: list[list[str | type[NotChecked] | None]] = [ - [NotChecked for _ in expected_exceptions] for _ in actual_exceptions - ] - - def set_result(self, expected: int, actual: int, result: str | None) -> None: - self.results[actual][expected] = result - - def get_result(self, expected: int, actual: int) -> str | None: - res = self.results[actual][expected] - assert res is not NotChecked - # mypy doesn't support identity checking against anything but None - return res # type: ignore[return-value] - - def has_result(self, expected: int, actual: int) -> bool: - return self.results[actual][expected] is not NotChecked - - def no_match_for_expected(self, expected: list[int]) -> bool: - for i in expected: - for actual_results in self.results: - assert actual_results[i] is not NotChecked - if actual_results[i] is None: - return False - return True - - def no_match_for_actual(self, actual: list[int]) -> bool: - for i in actual: - for res in self.results[i]: - assert res is not NotChecked - if res is None: - return False - return True - - -def possible_match(results: ResultHolder, used: set[int] | None = None) -> bool: - if used is None: - used = set() - curr_row = len(used) - if curr_row == len(results.results): - return True - return any( - val is None and i not in used and possible_match(results, used | {i}) - for (i, val) in enumerate(results.results[curr_row]) - ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/recwarn.py b/Backend/venv/lib/python3.12/site-packages/_pytest/recwarn.py index e3db717b..d76ea020 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/recwarn.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/recwarn.py @@ -1,29 +1,25 @@ -# mypy: allow-untyped-defs """Record warnings during test function execution.""" - -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterator -from pprint import pformat import re +import warnings +from pprint import pformat from types import TracebackType from typing import Any -from typing import final -from typing import overload -from typing import TYPE_CHECKING +from typing import Callable +from typing import Generator +from typing import Iterator +from typing import List +from typing import Optional +from typing import Pattern +from typing import Tuple +from typing import Type from typing import TypeVar +from typing import Union - -if TYPE_CHECKING: - from typing_extensions import Self - -import warnings - +from _pytest.compat import final +from _pytest.compat import overload from _pytest.deprecated import check_ispytest +from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture -from _pytest.outcomes import Exit from _pytest.outcomes import fail @@ -31,10 +27,11 @@ T = TypeVar("T") @fixture -def recwarn() -> Generator[WarningsRecorder]: +def recwarn() -> Generator["WarningsRecorder", None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See :ref:`warnings` for information on warning categories. + See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information + on warning categories. """ wrec = WarningsRecorder(_ispytest=True) with wrec: @@ -44,18 +41,22 @@ def recwarn() -> Generator[WarningsRecorder]: @overload def deprecated_call( - *, match: str | re.Pattern[str] | None = ... -) -> WarningsRecorder: ... + *, match: Optional[Union[str, Pattern[str]]] = ... +) -> "WarningsRecorder": + ... @overload -def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... +def deprecated_call( # noqa: F811 + func: Callable[..., T], *args: Any, **kwargs: Any +) -> T: + ... -def deprecated_call( - func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any -) -> WarningsRecorder | Any: - """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. +def deprecated_call( # noqa: F811 + func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any +) -> Union["WarningsRecorder", Any]: + """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``. This function can be used as a context manager:: @@ -80,46 +81,46 @@ def deprecated_call( """ __tracebackhide__ = True if func is not None: - args = (func, *args) - return warns( - (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs - ) + args = (func,) + args + return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs) @overload def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., + expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., *, - match: str | re.Pattern[str] | None = ..., -) -> WarningsChecker: ... + match: Optional[Union[str, Pattern[str]]] = ..., +) -> "WarningsChecker": + ... @overload -def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...], +def warns( # noqa: F811 + expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], func: Callable[..., T], *args: Any, **kwargs: Any, -) -> T: ... +) -> T: + ... -def warns( - expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, +def warns( # noqa: F811 + expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, *args: Any, - match: str | re.Pattern[str] | None = None, + match: Optional[Union[str, Pattern[str]]] = None, **kwargs: Any, -) -> WarningsChecker | Any: +) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. - Specifically, the parameter ``expected_warning`` can be a warning class or tuple + Specifically, the parameter ``expected_warning`` can be a warning class or sequence of warning classes, and the code inside the ``with`` block must issue at least one warning of that class or classes. This helper produces a list of :class:`warnings.WarningMessage` objects, one for - each warning emitted (regardless of whether it is an ``expected_warning`` or not). - Since pytest 8.0, unmatched warnings are also re-emitted when the context closes. + each warning raised (regardless of whether it is an ``expected_warning`` or not). - This function can be used as a context manager:: + This function can be used as a context manager, which will capture all the raised + warnings inside it:: >>> import pytest >>> with pytest.warns(RuntimeWarning): @@ -134,9 +135,8 @@ def warns( >>> with pytest.warns(UserWarning, match=r'must be \d+$'): ... warnings.warn("value must be 42", UserWarning) - >>> with pytest.warns(UserWarning): # catch re-emitted warning - ... with pytest.warns(UserWarning, match=r'must be \d+$'): - ... warnings.warn("this is not here", UserWarning) + >>> with pytest.warns(UserWarning, match=r'must be \d+$'): + ... warnings.warn("this is not here", UserWarning) Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... @@ -167,7 +167,7 @@ def warns( return func(*args[1:], **kwargs) -class WarningsRecorder(warnings.catch_warnings): +class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] """A context manager to record raised warnings. Each recorded warning is an instance of :class:`warnings.WarningMessage`. @@ -182,20 +182,21 @@ class WarningsRecorder(warnings.catch_warnings): def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - super().__init__(record=True) + # Type ignored due to the way typeshed handles warnings.catch_warnings. + super().__init__(record=True) # type: ignore[call-arg] self._entered = False - self._list: list[warnings.WarningMessage] = [] + self._list: List[warnings.WarningMessage] = [] @property - def list(self) -> list[warnings.WarningMessage]: + def list(self) -> List["warnings.WarningMessage"]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i: int) -> warnings.WarningMessage: + def __getitem__(self, i: int) -> "warnings.WarningMessage": """Get a recorded warning by index.""" return self._list[i] - def __iter__(self) -> Iterator[warnings.WarningMessage]: + def __iter__(self) -> Iterator["warnings.WarningMessage"]: """Iterate through the recorded warnings.""" return iter(self._list) @@ -203,22 +204,11 @@ class WarningsRecorder(warnings.catch_warnings): """The number of recorded warnings.""" return len(self._list) - def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: - """Pop the first recorded warning which is an instance of ``cls``, - but not an instance of a child class of any other match. - Raises ``AssertionError`` if there is no match. - """ - best_idx: int | None = None + def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": + """Pop the first recorded warning, raise exception if not exists.""" for i, w in enumerate(self._list): - if w.category == cls: - return self._list.pop(i) # exact match, stop looking - if issubclass(w.category, cls) and ( - best_idx is None - or not issubclass(w.category, self._list[best_idx].category) - ): - best_idx = i - if best_idx is not None: - return self._list.pop(best_idx) + if issubclass(w.category, cls): + return self._list.pop(i) __tracebackhide__ = True raise AssertionError(f"{cls!r} not found in warning list") @@ -226,9 +216,9 @@ class WarningsRecorder(warnings.catch_warnings): """Clear the list of recorded warnings.""" self._list[:] = [] - # Type ignored because we basically want the `catch_warnings` generic type - # parameter to be ourselves but that is not possible(?). - def __enter__(self) -> Self: # type: ignore[override] + # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ + # -- it returns a List but we only emulate one. + def __enter__(self) -> "WarningsRecorder": # type: ignore if self._entered: __tracebackhide__ = True raise RuntimeError(f"Cannot enter {self!r} twice") @@ -241,9 +231,9 @@ class WarningsRecorder(warnings.catch_warnings): def __exit__( self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], ) -> None: if not self._entered: __tracebackhide__ = True @@ -260,8 +250,10 @@ class WarningsRecorder(warnings.catch_warnings): class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, - match_expr: str | re.Pattern[str] | None = None, + expected_warning: Optional[ + Union[Type[Warning], Tuple[Type[Warning], ...]] + ] = Warning, + match_expr: Optional[Union[str, Pattern[str]]] = None, *, _ispytest: bool = False, ) -> None: @@ -269,14 +261,15 @@ class WarningsChecker(WarningsRecorder): super().__init__(_ispytest=True) msg = "exceptions must be derived from Warning, not %s" - if isinstance(expected_warning, tuple): + if expected_warning is None: + warnings.warn(WARNS_NONE_ARG, stacklevel=4) + expected_warning_tup = None + elif isinstance(expected_warning, tuple): for exc in expected_warning: if not issubclass(exc, Warning): raise TypeError(msg % type(exc)) expected_warning_tup = expected_warning - elif isinstance(expected_warning, type) and issubclass( - expected_warning, Warning - ): + elif issubclass(expected_warning, Warning): expected_warning_tup = (expected_warning,) else: raise TypeError(msg % type(expected_warning)) @@ -284,84 +277,37 @@ class WarningsChecker(WarningsRecorder): self.expected_warning = expected_warning_tup self.match_expr = match_expr - def matches(self, warning: warnings.WarningMessage) -> bool: - assert self.expected_warning is not None - return issubclass(warning.category, self.expected_warning) and bool( - self.match_expr is None or re.search(self.match_expr, str(warning.message)) - ) - def __exit__( self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], ) -> None: super().__exit__(exc_type, exc_val, exc_tb) __tracebackhide__ = True - # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within - # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed - # when the warning doesn't happen. Control-flow exceptions should always - # propagate. - if exc_val is not None and ( - not isinstance(exc_val, Exception) - # Exit is an Exception, not a BaseException, for some reason. - or isinstance(exc_val, Exit) - ): - return - - def found_str() -> str: + def found_str(): return pformat([record.message for record in self], indent=2) - try: - if not any(issubclass(w.category, self.expected_warning) for w in self): - fail( - f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" - f" Emitted warnings: {found_str()}." - ) - elif not any(self.matches(w) for w in self): - fail( - f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n" - f" Regex: {self.match_expr}\n" - f" Emitted warnings: {found_str()}." - ) - finally: - # Whether or not any warnings matched, we want to re-emit all unmatched warnings. - for w in self: - if not self.matches(w): - warnings.warn_explicit( - message=w.message, - category=w.category, - filename=w.filename, - lineno=w.lineno, - module=w.__module__, - source=w.source, + # only check if we're not currently handling an exception + if exc_type is None and exc_val is None and exc_tb is None: + if self.expected_warning is not None: + if not any(issubclass(r.category, self.expected_warning) for r in self): + __tracebackhide__ = True + fail( + f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n" + f"The list of emitted warnings is: {found_str()}." ) - - # Currently in Python it is possible to pass other types than an - # `str` message when creating `Warning` instances, however this - # causes an exception when :func:`warnings.filterwarnings` is used - # to filter those warnings. See - # https://github.com/python/cpython/issues/103577 for a discussion. - # While this can be considered a bug in CPython, we put guards in - # pytest as the error message produced without this check in place - # is confusing (#10865). - for w in self: - if type(w.message) is not UserWarning: - # If the warning was of an incorrect type then `warnings.warn()` - # creates a UserWarning. Any other warning must have been specified - # explicitly. - continue - if not w.message.args: - # UserWarning() without arguments must have been specified explicitly. - continue - msg = w.message.args[0] - if isinstance(msg, str): - continue - # It's possible that UserWarning was explicitly specified, and - # its first argument was not a string. But that case can't be - # distinguished from an invalid type. - raise TypeError( - f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})" - ) + elif self.match_expr is not None: + for r in self: + if issubclass(r.category, self.expected_warning): + if re.compile(self.match_expr).search(str(r.message)): + break + else: + fail( + f"""\ +DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted. + Regex: {self.match_expr} + Emitted warnings: {found_str()}""" + ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/reports.py b/Backend/venv/lib/python3.12/site-packages/_pytest/reports.py index 011a69db..74e8794b 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/reports.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/reports.py @@ -1,21 +1,21 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from collections.abc import Sequence import dataclasses -from io import StringIO import os +from io import StringIO from pprint import pprint -import sys from typing import Any from typing import cast -from typing import final -from typing import Literal +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping from typing import NoReturn +from typing import Optional +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -29,19 +29,14 @@ from _pytest._code.code import ReprLocals from _pytest._code.code import ReprTraceback from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter +from _pytest.compat import final from _pytest.config import Config from _pytest.nodes import Collector from _pytest.nodes import Item -from _pytest.outcomes import fail from _pytest.outcomes import skip - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - - if TYPE_CHECKING: - from typing_extensions import Self + from typing_extensions import Literal from _pytest.runner import CallInfo @@ -51,29 +46,33 @@ def getworkerinfoline(node): return node._workerinfocache except AttributeError: d = node.workerinfo - ver = "{}.{}.{}".format(*d["version_info"][:3]) + ver = "%s.%s.%s" % d["version_info"][:3] node._workerinfocache = s = "[{}] {} -- Python {} {}".format( d["id"], d["sysplatform"], ver, d["executable"] ) return s +_R = TypeVar("_R", bound="BaseReport") + + class BaseReport: - when: str | None - location: tuple[str, int | None, str] | None - longrepr: ( - None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr - ) - sections: list[tuple[str, str]] + when: Optional[str] + location: Optional[Tuple[str, Optional[int], str]] + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ] + sections: List[Tuple[str, str]] nodeid: str - outcome: Literal["passed", "failed", "skipped"] + outcome: "Literal['passed', 'failed', 'skipped']" def __init__(self, **kw: Any) -> None: self.__dict__.update(kw) if TYPE_CHECKING: # Can have arbitrary fields given to __init__(). - def __getattr__(self, key: str) -> Any: ... + def __getattr__(self, key: str) -> Any: + ... def toterminal(self, out: TerminalWriter) -> None: if hasattr(self, "node"): @@ -95,7 +94,7 @@ class BaseReport: s = "" out.line(s) - def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: + def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @@ -177,7 +176,7 @@ class BaseReport: return True @property - def head_line(self) -> str | None: + def head_line(self) -> Optional[str]: """**Experimental** The head line shown with longrepr output for this report, more commonly during traceback representation during failures:: @@ -193,32 +192,17 @@ class BaseReport: even in patch releases. """ if self.location is not None: - _fspath, _lineno, domain = self.location + fspath, lineno, domain = self.location return domain return None - def _get_verbose_word_with_markup( - self, config: Config, default_markup: Mapping[str, bool] - ) -> tuple[str, Mapping[str, bool]]: + def _get_verbose_word(self, config: Config): _category, _short, verbose = config.hook.pytest_report_teststatus( report=self, config=config ) + return verbose - if isinstance(verbose, str): - return verbose, default_markup - - if isinstance(verbose, Sequence) and len(verbose) == 2: - word, markup = verbose - if isinstance(word, str) and isinstance(markup, Mapping): - return word, markup - - fail( # pragma: no cover - "pytest_report_teststatus() hook (from a plugin) returned " - f"an invalid verbose value: {verbose!r}.\nExpected either a string " - "or a tuple of (word, markup)." - ) - - def _to_json(self) -> dict[str, Any]: + def _to_json(self) -> Dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -229,7 +213,7 @@ class BaseReport: return _report_to_json(self) @classmethod - def _from_json(cls, reportdict: dict[str, object]) -> Self: + def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: """Create either a TestReport or CollectReport, depending on the calling class. It is the callers responsibility to know which class to pass here. @@ -243,65 +227,20 @@ class BaseReport: def _report_unserialization_failure( - type_name: str, report_class: type[BaseReport], reportdict + type_name: str, report_class: Type[BaseReport], reportdict ) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() pprint("-" * 100, stream=stream) - pprint(f"INTERNALERROR: Unknown entry type returned: {type_name}", stream=stream) - pprint(f"report_name: {report_class}", stream=stream) + pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream) + pprint("report_name: %s" % report_class, stream=stream) pprint(reportdict, stream=stream) - pprint(f"Please report this bug at {url}", stream=stream) + pprint("Please report this bug at %s" % url, stream=stream) pprint("-" * 100, stream=stream) raise RuntimeError(stream.getvalue()) -def _format_failed_longrepr( - item: Item, call: CallInfo[None], excinfo: ExceptionInfo[BaseException] -): - if call.when == "call": - longrepr = item.repr_failure(excinfo) - else: - # Exception in setup or teardown. - longrepr = item._repr_failure_py( - excinfo, style=item.config.getoption("tbstyle", "auto") - ) - return longrepr - - -def _format_exception_group_all_skipped_longrepr( - item: Item, - excinfo: ExceptionInfo[BaseExceptionGroup[BaseException | BaseExceptionGroup]], -) -> tuple[str, int, str]: - r = excinfo._getreprcrash() - assert r is not None, ( - "There should always be a traceback entry for skipping a test." - ) - if all( - getattr(skip, "_use_item_location", False) for skip in excinfo.value.exceptions - ): - path, line = item.reportinfo()[:2] - assert line is not None - loc = (os.fspath(path), line + 1) - default_msg = "skipped" - else: - loc = (str(r.path), r.lineno) - default_msg = r.message - - # Get all unique skip messages. - msgs: list[str] = [] - for exception in excinfo.value.exceptions: - m = getattr(exception, "msg", None) or ( - exception.args[0] if exception.args else None - ) - if m and m not in msgs: - msgs.append(m) - - reason = "; ".join(msgs) if msgs else default_msg - longrepr = (*loc, reason) - return longrepr - - +@final class TestReport(BaseReport): """Basic test report object (also used for setup and teardown calls if they fail). @@ -311,27 +250,21 @@ class TestReport(BaseReport): __test__ = False - # Defined by skipping plugin. - # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish. - wasxfail: str - def __init__( self, nodeid: str, - location: tuple[str, int | None, str], + location: Tuple[str, Optional[int], str], keywords: Mapping[str, Any], - outcome: Literal["passed", "failed", "skipped"], - longrepr: None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr, - when: Literal["setup", "call", "teardown"], - sections: Iterable[tuple[str, str]] = (), + outcome: "Literal['passed', 'failed', 'skipped']", + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ], + when: "Literal['setup', 'call', 'teardown']", + sections: Iterable[Tuple[str, str]] = (), duration: float = 0, start: float = 0, stop: float = 0, - user_properties: Iterable[tuple[str, object]] | None = None, + user_properties: Optional[Iterable[Tuple[str, object]]] = None, **extra, ) -> None: #: Normalized collection nodeid. @@ -342,7 +275,7 @@ class TestReport(BaseReport): #: collected one e.g. if a method is inherited from a different module. #: The filesystempath may be relative to ``config.rootdir``. #: The line number is 0-based. - self.location: tuple[str, int | None, str] = location + self.location: Tuple[str, Optional[int], str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. @@ -355,7 +288,7 @@ class TestReport(BaseReport): self.longrepr = longrepr #: One of 'setup', 'call', 'teardown' to indicate runtest phase. - self.when: Literal["setup", "call", "teardown"] = when + self.when = when #: User properties is a list of tuples (name, value) that holds user #: defined properties of the test. @@ -378,10 +311,12 @@ class TestReport(BaseReport): self.__dict__.update(extra) def __repr__(self) -> str: - return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" + return "<{} {!r} when={!r} outcome={!r}>".format( + self.__class__.__name__, self.nodeid, self.when, self.outcome + ) @classmethod - def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: + def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": """Create and fill a TestReport with standard item and call info. :param item: The item. @@ -398,13 +333,13 @@ class TestReport(BaseReport): sections = [] if not call.excinfo: outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: ( - None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr - ) = None + longrepr: Union[ + None, + ExceptionInfo[BaseException], + Tuple[str, int, str], + str, + TerminalRepr, + ] = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" @@ -412,30 +347,23 @@ class TestReport(BaseReport): elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() - assert r is not None, ( - "There should always be a traceback entry for skipping a test." - ) + assert ( + r is not None + ), "There should always be a traceback entry for skipping a test." if excinfo.value._use_item_location: path, line = item.reportinfo()[:2] assert line is not None - longrepr = (os.fspath(path), line + 1, r.message) + longrepr = os.fspath(path), line + 1, r.message else: longrepr = (str(r.path), r.lineno, r.message) - elif isinstance(excinfo.value, BaseExceptionGroup) and ( - excinfo.value.split(skip.Exception)[1] is None - ): - # All exceptions in the group are skip exceptions. - outcome = "skipped" - excinfo = cast( - ExceptionInfo[ - BaseExceptionGroup[BaseException | BaseExceptionGroup] - ], - excinfo, - ) - longrepr = _format_exception_group_all_skipped_longrepr(item, excinfo) else: outcome = "failed" - longrepr = _format_failed_longrepr(item, call, excinfo) + if call.when == "call": + longrepr = item.repr_failure(excinfo) + else: # exception in setup or teardown + longrepr = item._repr_failure_py( + excinfo, style=item.config.getoption("tbstyle", "auto") + ) for rwhen, key, content in item._report_sections: sections.append((f"Captured {key} {rwhen}", content)) return cls( @@ -465,14 +393,12 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: Literal["passed", "failed", "skipped"], - longrepr: None - | ExceptionInfo[BaseException] - | tuple[str, int, str] - | str - | TerminalRepr, - result: list[Item | Collector] | None, - sections: Iterable[tuple[str, str]] = (), + outcome: "Literal['passed', 'failed', 'skipped']", + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ], + result: Optional[List[Union[Item, Collector]]], + sections: Iterable[Tuple[str, str]] = (), **extra, ) -> None: #: Normalized collection nodeid. @@ -498,11 +424,13 @@ class CollectReport(BaseReport): @property def location( # type:ignore[override] self, - ) -> tuple[str, int | None, str] | None: + ) -> Optional[Tuple[str, Optional[int], str]]: return (self.fspath, None, self.fspath) def __repr__(self) -> str: - return f"" + return "".format( + self.nodeid, len(self.result), self.outcome + ) class CollectErrorRepr(TerminalRepr): @@ -514,9 +442,9 @@ class CollectErrorRepr(TerminalRepr): def pytest_report_to_serializable( - report: CollectReport | TestReport, -) -> dict[str, Any] | None: - if isinstance(report, TestReport | CollectReport): + report: Union[CollectReport, TestReport] +) -> Optional[Dict[str, Any]]: + if isinstance(report, (TestReport, CollectReport)): data = report._to_json() data["$report_type"] = report.__class__.__name__ return data @@ -525,8 +453,8 @@ def pytest_report_to_serializable( def pytest_report_from_serializable( - data: dict[str, Any], -) -> CollectReport | TestReport | None: + data: Dict[str, Any], +) -> Optional[Union[CollectReport, TestReport]]: if "$report_type" in data: if data["$report_type"] == "TestReport": return TestReport._from_json(data) @@ -538,7 +466,7 @@ def pytest_report_from_serializable( return None -def _report_to_json(report: BaseReport) -> dict[str, Any]: +def _report_to_json(report: BaseReport) -> Dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -546,8 +474,8 @@ def _report_to_json(report: BaseReport) -> dict[str, Any]: """ def serialize_repr_entry( - entry: ReprEntry | ReprEntryNative, - ) -> dict[str, Any]: + entry: Union[ReprEntry, ReprEntryNative] + ) -> Dict[str, Any]: data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): @@ -555,7 +483,7 @@ def _report_to_json(report: BaseReport) -> dict[str, Any]: entry_data = {"type": type(entry).__name__, "data": data} return entry_data - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries @@ -563,18 +491,18 @@ def _report_to_json(report: BaseReport) -> dict[str, Any]: return result def serialize_repr_crash( - reprcrash: ReprFileLocation | None, - ) -> dict[str, Any] | None: + reprcrash: Optional[ReprFileLocation], + ) -> Optional[Dict[str, Any]]: if reprcrash is not None: return dataclasses.asdict(reprcrash) else: return None - def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: + def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: assert rep.longrepr is not None # TODO: Investigate whether the duck typing is really necessary here. longrepr = cast(ExceptionRepr, rep.longrepr) - result: dict[str, Any] = { + result: Dict[str, Any] = { "reprcrash": serialize_repr_crash(longrepr.reprcrash), "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), "sections": longrepr.sections, @@ -611,7 +539,7 @@ def _report_to_json(report: BaseReport) -> dict[str, Any]: return d -def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: +def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: """Return **kwargs that can be used to construct a TestReport or CollectReport instance. @@ -632,7 +560,7 @@ def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: if data["reprlocals"]: reprlocals = ReprLocals(data["reprlocals"]["lines"]) - reprentry: ReprEntry | ReprEntryNative = ReprEntry( + reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry( lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, @@ -651,7 +579,7 @@ def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): + def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): if repr_crash_dict is not None: return ReprFileLocation(**repr_crash_dict) else: @@ -678,9 +606,9 @@ def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: description, ) ) - exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( - chain - ) + exception_info: Union[ + ExceptionChainRepr, ReprExceptionInfo + ] = ExceptionChainRepr(chain) else: exception_info = ReprExceptionInfo( reprtraceback=reprtraceback, diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/runner.py b/Backend/venv/lib/python3.12/site-packages/_pytest/runner.py index 9c20ff9e..f861c05a 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/runner.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/runner.py @@ -1,22 +1,20 @@ -# mypy: allow-untyped-defs """Basic collect and runtest protocol implementations.""" - -from __future__ import annotations - import bdb -from collections.abc import Callable import dataclasses import os import sys -import types +from typing import Callable from typing import cast -from typing import final +from typing import Dict from typing import Generic -from typing import Literal +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import TypeVar +from typing import Union -from .config import Config from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport @@ -25,10 +23,10 @@ from _pytest import timing from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr +from _pytest.compat import final from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.nodes import Collector -from _pytest.nodes import Directory from _pytest.nodes import Item from _pytest.nodes import Node from _pytest.outcomes import Exit @@ -36,11 +34,12 @@ from _pytest.outcomes import OutcomeException from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME - -if sys.version_info < (3, 11): +if sys.version_info[:2] < (3, 11): from exceptiongroup import BaseExceptionGroup if TYPE_CHECKING: + from typing_extensions import Literal + from _pytest.main import Session from _pytest.terminal import TerminalReporter @@ -62,21 +61,19 @@ def pytest_addoption(parser: Parser) -> None: "--durations-min", action="store", type=float, - default=None, + default=0.005, metavar="N", help="Minimal duration in seconds for inclusion in slowest list. " - "Default: 0.005 (or 0.0 if -vv is given).", + "Default: 0.005.", ) -def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: +def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: durations = terminalreporter.config.option.durations durations_min = terminalreporter.config.option.durations_min - verbose = terminalreporter.config.get_verbosity() + verbose = terminalreporter.config.getvalue("verbose") if durations is None: return - if durations_min is None: - durations_min = 0.005 if verbose < 2 else 0.0 tr = terminalreporter dlist = [] for replist in tr.stats.values(): @@ -85,34 +82,33 @@ def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: dlist.append(rep) if not dlist: return - dlist.sort(key=lambda x: x.duration, reverse=True) + dlist.sort(key=lambda x: x.duration, reverse=True) # type: ignore[no-any-return] if not durations: tr.write_sep("=", "slowest durations") else: - tr.write_sep("=", f"slowest {durations} durations") + tr.write_sep("=", "slowest %s durations" % durations) dlist = dlist[:durations] for i, rep in enumerate(dlist): - if rep.duration < durations_min: + if verbose < 2 and rep.duration < durations_min: tr.write_line("") - message = f"({len(dlist) - i} durations < {durations_min:g}s hidden." - if terminalreporter.config.option.durations_min is None: - message += " Use -vv to show these durations." - message += ")" - tr.write_line(message) + tr.write_line( + "(%s durations < %gs hidden. Use -vv to show these durations.)" + % (len(dlist) - i, durations_min) + ) break tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") -def pytest_sessionstart(session: Session) -> None: +def pytest_sessionstart(session: "Session") -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session: Session) -> None: +def pytest_sessionfinish(session: "Session") -> None: session._setupstate.teardown_exact(None) -def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: +def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: ihook = item.ihook ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) runtestprotocol(item, nextitem=nextitem) @@ -121,8 +117,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: def runtestprotocol( - item: Item, log: bool = True, nextitem: Item | None = None -) -> list[TestReport]: + item: Item, log: bool = True, nextitem: Optional[Item] = None +) -> List[TestReport]: hasrequest = hasattr(item, "_request") if hasrequest and not item._request: # type: ignore[attr-defined] # This only happens if the item is re-run, as is done by @@ -135,10 +131,6 @@ def runtestprotocol( show_test_item(item) if not item.config.getoption("setuponly", False): reports.append(call_and_report(item, "call", log)) - # If the session is about to fail or stop, teardown everything - this is - # necessary to correctly report fixture teardown errors (see #11706) - if item.session.shouldfail or item.session.shouldstop: - nextitem = None reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # After all teardown hooks have been called # want funcargs and request info to go away. @@ -171,8 +163,6 @@ def pytest_runtest_call(item: Item) -> None: del sys.last_type del sys.last_value del sys.last_traceback - if sys.version_info >= (3, 12, 0): - del sys.last_exc # type:ignore[attr-defined] except AttributeError: pass try: @@ -181,22 +171,20 @@ def pytest_runtest_call(item: Item) -> None: # Store trace info to allow postmortem debugging sys.last_type = type(e) sys.last_value = e - if sys.version_info >= (3, 12, 0): - sys.last_exc = e # type:ignore[attr-defined] assert e.__traceback__ is not None # Skip *this* frame sys.last_traceback = e.__traceback__.tb_next - raise + raise e -def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: _update_current_test_var(item, "teardown") item.session._setupstate.teardown_exact(nextitem) _update_current_test_var(item, None) def _update_current_test_var( - item: Item, when: Literal["setup", "call", "teardown"] | None + item: Item, when: Optional["Literal['setup', 'call', 'teardown']"] ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -212,7 +200,7 @@ def _update_current_test_var( os.environ.pop(var_name) -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: +def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -229,40 +217,19 @@ def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: def call_and_report( - item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds + item: Item, when: "Literal['setup', 'call', 'teardown']", log: bool = True, **kwds ) -> TestReport: - ihook = item.ihook - if when == "setup": - runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup - elif when == "call": - runtest_hook = ihook.pytest_runtest_call - elif when == "teardown": - runtest_hook = ihook.pytest_runtest_teardown - else: - assert False, f"Unhandled runtest hook case: {when}" - - call = CallInfo.from_call( - lambda: runtest_hook(item=item, **kwds), - when=when, - reraise=get_reraise_exceptions(item.config), - ) - report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call) + call = call_runtest_hook(item, when, **kwds) + hook = item.ihook + report: TestReport = hook.pytest_runtest_makereport(item=item, call=call) if log: - ihook.pytest_runtest_logreport(report=report) + hook.pytest_runtest_logreport(report=report) if check_interactive_exception(call, report): - ihook.pytest_exception_interact(node=item, call=call, report=report) + hook.pytest_exception_interact(node=item, call=call, report=report) return report -def get_reraise_exceptions(config: Config) -> tuple[type[BaseException], ...]: - """Return exception types that should not be suppressed in general.""" - reraise: tuple[type[BaseException], ...] = (Exit,) - if not config.getoption("usepdb", False): - reraise += (KeyboardInterrupt,) - return reraise - - -def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: +def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: """Check whether the call raised an exception that should be reported as interactive.""" if call.excinfo is None: @@ -271,12 +238,31 @@ def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> b if hasattr(report, "wasxfail"): # Exception was expected. return False - if isinstance(call.excinfo.value, Skipped | bdb.BdbQuit): + if isinstance(call.excinfo.value, (Skipped, bdb.BdbQuit)): # Special control flow exception. return False return True +def call_runtest_hook( + item: Item, when: "Literal['setup', 'call', 'teardown']", **kwds +) -> "CallInfo[None]": + if when == "setup": + ihook: Callable[..., None] = item.ihook.pytest_runtest_setup + elif when == "call": + ihook = item.ihook.pytest_runtest_call + elif when == "teardown": + ihook = item.ihook.pytest_runtest_teardown + else: + assert False, f"Unhandled runtest hook case: {when}" + reraise: Tuple[Type[BaseException], ...] = (Exit,) + if not item.config.getoption("usepdb", False): + reraise += (KeyboardInterrupt,) + return CallInfo.from_call( + lambda: ihook(item=item, **kwds), when=when, reraise=reraise + ) + + TResult = TypeVar("TResult", covariant=True) @@ -285,9 +271,9 @@ TResult = TypeVar("TResult", covariant=True) class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" - _result: TResult | None + _result: Optional[TResult] #: The captured exception of the call, if it raised. - excinfo: ExceptionInfo[BaseException] | None + excinfo: Optional[ExceptionInfo[BaseException]] #: The system time when the call started, in seconds since the epoch. start: float #: The system time when the call ended, in seconds since the epoch. @@ -295,16 +281,16 @@ class CallInfo(Generic[TResult]): #: The call duration, in seconds. duration: float #: The context of invocation: "collect", "setup", "call" or "teardown". - when: Literal["collect", "setup", "call", "teardown"] + when: "Literal['collect', 'setup', 'call', 'teardown']" def __init__( self, - result: TResult | None, - excinfo: ExceptionInfo[BaseException] | None, + result: Optional[TResult], + excinfo: Optional[ExceptionInfo[BaseException]], start: float, stop: float, duration: float, - when: Literal["collect", "setup", "call", "teardown"], + when: "Literal['collect', 'setup', 'call', 'teardown']", *, _ispytest: bool = False, ) -> None: @@ -332,15 +318,16 @@ class CallInfo(Generic[TResult]): @classmethod def from_call( cls, - func: Callable[[], TResult], - when: Literal["collect", "setup", "call", "teardown"], - reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, - ) -> CallInfo[TResult]: + func: "Callable[[], TResult]", + when: "Literal['collect', 'setup', 'call', 'teardown']", + reraise: Optional[ + Union[Type[BaseException], Tuple[Type[BaseException], ...]] + ] = None, + ) -> "CallInfo[TResult]": """Call func, wrapping the result in a CallInfo. :param func: The function to call. Called without arguments. - :type func: Callable[[], _pytest.runner.TResult] :param when: The phase in which the function is called. :param reraise: @@ -348,19 +335,23 @@ class CallInfo(Generic[TResult]): function, instead of being wrapped in the CallInfo. """ excinfo = None - instant = timing.Instant() + start = timing.time() + precise_start = timing.perf_counter() try: - result: TResult | None = func() + result: Optional[TResult] = func() except BaseException: excinfo = ExceptionInfo.from_current() if reraise is not None and isinstance(excinfo.value, reraise): raise result = None - duration = instant.elapsed() + # use the perf counter + precise_stop = timing.perf_counter() + duration = precise_stop - precise_start + stop = timing.time() return cls( - start=duration.start.time, - stop=duration.stop.time, - duration=duration.seconds, + start=start, + stop=stop, + duration=duration, when=when, result=result, excinfo=excinfo, @@ -378,36 +369,16 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - def collect() -> list[Item | Collector]: - # Before collecting, if this is a Directory, load the conftests. - # If a conftest import fails to load, it is considered a collection - # error of the Directory collector. This is why it's done inside of the - # CallInfo wrapper. - # - # Note: initial conftests are loaded early, not here. - if isinstance(collector, Directory): - collector.config.pluginmanager._loadconftestmodules( - collector.path, - collector.config.getoption("importmode"), - rootpath=collector.config.rootpath, - consider_namespace_packages=collector.config.getini( - "consider_namespace_packages" - ), - ) - - return list(collector.collect()) - - call = CallInfo.from_call( - collect, "collect", reraise=(KeyboardInterrupt, SystemExit) - ) - longrepr: None | tuple[str, int, str] | str | TerminalRepr = None + call = CallInfo.from_call(lambda: list(collector.collect()), "collect") + longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" else: skip_exceptions = [Skipped] unittest = sys.modules.get("unittest") if unittest is not None: - skip_exceptions.append(unittest.SkipTest) + # Type ignored because unittest is loaded dynamically. + skip_exceptions.append(unittest.SkipTest) # type: ignore if isinstance(call.excinfo.value, tuple(skip_exceptions)): outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") @@ -494,13 +465,13 @@ class SetupState: def __init__(self) -> None: # The stack is in the dict insertion order. - self.stack: dict[ + self.stack: Dict[ Node, - tuple[ + Tuple[ # Node's finalizers. - list[Callable[[], object]], - # Node's exception and original traceback, if its setup raised. - tuple[OutcomeException | Exception, types.TracebackType | None] | None, + List[Callable[[], object]], + # Node's exception, if its setup raised. + Optional[Union[OutcomeException, Exception]], ], ] = {} @@ -513,7 +484,7 @@ class SetupState: for col, (finalizers, exc) in self.stack.items(): assert col in needed_collectors, "previous item was not torn down properly" if exc: - raise exc[0].with_traceback(exc[1]) + raise exc for col in needed_collectors[len(self.stack) :]: assert col not in self.stack @@ -522,8 +493,8 @@ class SetupState: try: col.setup() except TEST_OUTCOME as exc: - self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__)) - raise + self.stack[col] = (self.stack[col][0], exc) + raise exc def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: """Attach a finalizer to the given node. @@ -535,15 +506,15 @@ class SetupState: assert node in self.stack, (node, self.stack) self.stack[node][0].append(finalizer) - def teardown_exact(self, nextitem: Item | None) -> None: + def teardown_exact(self, nextitem: Optional[Item]) -> None: """Teardown the current stack up until reaching nodes that nextitem also descends from. When nextitem is None (meaning we're at the last item), the entire stack is torn down. """ - needed_collectors = (nextitem and nextitem.listchain()) or [] - exceptions: list[BaseException] = [] + needed_collectors = nextitem and nextitem.listchain() or [] + exceptions: List[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/scope.py b/Backend/venv/lib/python3.12/site-packages/_pytest/scope.py index 2b007e87..7a746fb9 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/scope.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/scope.py @@ -7,15 +7,15 @@ would cause circular references. Also this makes the module light to import, as it should. """ - -from __future__ import annotations - from enum import Enum from functools import total_ordering -from typing import Literal +from typing import Optional +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from typing_extensions import Literal -_ScopeName = Literal["session", "package", "module", "class", "function"] + _ScopeName = Literal["session", "package", "module", "class", "function"] @total_ordering @@ -33,35 +33,35 @@ class Scope(Enum): """ # Scopes need to be listed from lower to higher. - Function = "function" - Class = "class" - Module = "module" - Package = "package" - Session = "session" + Function: "_ScopeName" = "function" + Class: "_ScopeName" = "class" + Module: "_ScopeName" = "module" + Package: "_ScopeName" = "package" + Session: "_ScopeName" = "session" - def next_lower(self) -> Scope: + def next_lower(self) -> "Scope": """Return the next lower scope.""" index = _SCOPE_INDICES[self] if index == 0: raise ValueError(f"{self} is the lower-most scope") return _ALL_SCOPES[index - 1] - def next_higher(self) -> Scope: + def next_higher(self) -> "Scope": """Return the next higher scope.""" index = _SCOPE_INDICES[self] if index == len(_SCOPE_INDICES) - 1: raise ValueError(f"{self} is the upper-most scope") return _ALL_SCOPES[index + 1] - def __lt__(self, other: Scope) -> bool: + def __lt__(self, other: "Scope") -> bool: self_index = _SCOPE_INDICES[self] other_index = _SCOPE_INDICES[other] return self_index < other_index @classmethod def from_user( - cls, scope_name: _ScopeName, descr: str, where: str | None = None - ) -> Scope: + cls, scope_name: "_ScopeName", descr: str, where: Optional[str] = None + ) -> "Scope": """ Given a scope name from the user, return the equivalent Scope enum. Should be used whenever we want to convert a user provided scope name to its enum object. diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/setuponly.py b/Backend/venv/lib/python3.12/site-packages/_pytest/setuponly.py index 7e6b46bc..583590d6 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/setuponly.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/setuponly.py @@ -1,7 +1,8 @@ -from __future__ import annotations - -from collections.abc import Generator +from typing import Generator +from typing import Optional +from typing import Union +import pytest from _pytest._io.saferepr import saferepr from _pytest.config import Config from _pytest.config import ExitCode @@ -9,7 +10,6 @@ from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureDef from _pytest.fixtures import SubRequest from _pytest.scope import Scope -import pytest def pytest_addoption(parser: Parser) -> None: @@ -28,42 +28,37 @@ def pytest_addoption(parser: Parser) -> None: ) -@pytest.hookimpl(wrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Generator[None, object, object]: - try: - return (yield) - finally: - if request.config.option.setupshow: - if hasattr(request, "param"): - # Save the fixture parameter so ._show_fixture_action() can - # display it now and during the teardown (in .finish()). - if fixturedef.ids: - if callable(fixturedef.ids): - param = fixturedef.ids(request.param) - else: - param = fixturedef.ids[request.param_index] +) -> Generator[None, None, None]: + yield + if request.config.option.setupshow: + if hasattr(request, "param"): + # Save the fixture parameter so ._show_fixture_action() can + # display it now and during the teardown (in .finish()). + if fixturedef.ids: + if callable(fixturedef.ids): + param = fixturedef.ids(request.param) else: - param = request.param - fixturedef.cached_param = param # type: ignore[attr-defined] - _show_fixture_action(fixturedef, request.config, "SETUP") + param = fixturedef.ids[request.param_index] + else: + param = request.param + fixturedef.cached_param = param # type: ignore[attr-defined] + _show_fixture_action(fixturedef, "SETUP") -def pytest_fixture_post_finalizer( - fixturedef: FixtureDef[object], request: SubRequest -) -> None: +def pytest_fixture_post_finalizer(fixturedef: FixtureDef[object]) -> None: if fixturedef.cached_result is not None: - config = request.config + config = fixturedef._fixturemanager.config if config.option.setupshow: - _show_fixture_action(fixturedef, request.config, "TEARDOWN") + _show_fixture_action(fixturedef, "TEARDOWN") if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param + del fixturedef.cached_param # type: ignore[attr-defined] -def _show_fixture_action( - fixturedef: FixtureDef[object], config: Config, msg: str -) -> None: +def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: + config = fixturedef._fixturemanager.config capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() @@ -73,9 +68,13 @@ def _show_fixture_action( # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc. scope_indent = list(reversed(Scope)).index(fixturedef._scope) tw.write(" " * 2 * scope_indent) - - scopename = fixturedef.scope[0].upper() - tw.write(f"{msg:<8} {scopename} {fixturedef.argname}") + tw.write( + "{step} {scope} {fixture}".format( + step=msg.ljust(8), # align the output to TEARDOWN + scope=fixturedef.scope[0].upper(), + fixture=fixturedef.argname, + ) + ) if msg == "SETUP": deps = sorted(arg for arg in fixturedef.argnames if arg != "request") @@ -83,7 +82,7 @@ def _show_fixture_action( tw.write(" (fixtures used: {})".format(", ".join(deps))) if hasattr(fixturedef, "cached_param"): - tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") + tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]") # type: ignore[attr-defined] tw.flush() @@ -92,7 +91,7 @@ def _show_fixture_action( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setuponly: config.option.setupshow = True return None diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/setupplan.py b/Backend/venv/lib/python3.12/site-packages/_pytest/setupplan.py index 4e124cce..1a4ebdd9 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/setupplan.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/setupplan.py @@ -1,11 +1,12 @@ -from __future__ import annotations +from typing import Optional +from typing import Union +import pytest from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureDef from _pytest.fixtures import SubRequest -import pytest def pytest_addoption(parser: Parser) -> None: @@ -22,7 +23,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> object | None: +) -> Optional[object]: # Will return a dummy fixture if the setuponly option is provided. if request.config.option.setupplan: my_cache_key = fixturedef.cache_key(request) @@ -32,7 +33,7 @@ def pytest_fixture_setup( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> int | ExitCode | None: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/skipping.py b/Backend/venv/lib/python3.12/site-packages/_pytest/skipping.py index 3b067629..26ce7375 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/skipping.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/skipping.py @@ -1,15 +1,14 @@ -# mypy: allow-untyped-defs """Support for skip/xfail functions and markers.""" - -from __future__ import annotations - -from collections.abc import Generator -from collections.abc import Mapping import dataclasses import os import platform import sys import traceback +from collections.abc import Mapping +from typing import Generator +from typing import Optional +from typing import Tuple +from typing import Type from _pytest.config import Config from _pytest.config import hookimpl @@ -19,9 +18,7 @@ from _pytest.nodes import Item from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail -from _pytest.raises import AbstractRaises from _pytest.reports import BaseReport -from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.stash import StashKey @@ -37,13 +34,11 @@ def pytest_addoption(parser: Parser) -> None: ) parser.addini( - "strict_xfail", + "xfail_strict", "Default for the strict parameter of xfail " - "markers when not given explicitly (default: False) (alias: xfail_strict)", + "markers when not given explicitly (default: False)", + default=False, type="bool", - # None => fallback to `strict`. - default=None, - aliases=["xfail_strict"], ) @@ -76,7 +71,7 @@ def pytest_configure(config: Config) -> None: ) config.addinivalue_line( "markers", - "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=strict_xfail): " + "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): " "mark the test function as an expected failure if any of the conditions " "evaluate to True. Optionally specify a reason for better reporting " "and run=False if you don't even want to execute the test function. " @@ -86,7 +81,7 @@ def pytest_configure(config: Config) -> None: ) -def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: +def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: """Evaluate a single skipif/xfail condition. If an old-style string condition is given, it is eval()'d, otherwise the @@ -108,18 +103,20 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, ): if not isinstance(dictionary, Mapping): raise ValueError( - f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}" + "pytest_markeval_namespace() needs to return a dict, got {!r}".format( + dictionary + ) ) globals_.update(dictionary) if hasattr(item, "obj"): - globals_.update(item.obj.__globals__) + globals_.update(item.obj.__globals__) # type: ignore[attr-defined] try: filename = f"<{mark.name} condition>" condition_code = compile(condition, filename, "eval") result = eval(condition_code, globals_) except SyntaxError as exc: msglines = [ - f"Error evaluating {mark.name!r} condition", + "Error evaluating %r condition" % mark.name, " " + condition, " " + " " * (exc.offset or 0) + "^", "SyntaxError: invalid syntax", @@ -127,7 +124,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, fail("\n".join(msglines), pytrace=False) except Exception as exc: msglines = [ - f"Error evaluating {mark.name!r} condition", + "Error evaluating %r condition" % mark.name, " " + condition, *traceback.format_exception_only(type(exc), exc), ] @@ -139,7 +136,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, result = bool(condition) except Exception as exc: msglines = [ - f"Error evaluating {mark.name!r} condition as a boolean", + "Error evaluating %r condition as a boolean" % mark.name, *traceback.format_exception_only(type(exc), exc), ] fail("\n".join(msglines), pytrace=False) @@ -151,7 +148,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, else: # XXX better be checked at collection time msg = ( - f"Error evaluating {mark.name!r}: " + "Error evaluating %r: " % mark.name + "you need to specify reason=STRING when using booleans as conditions." ) fail(msg, pytrace=False) @@ -166,7 +163,7 @@ class Skip: reason: str = "unconditional skip" -def evaluate_skip_marks(item: Item) -> Skip | None: +def evaluate_skip_marks(item: Item) -> Optional[Skip]: """Evaluate skip and skipif marks on item, returning Skip if triggered.""" for mark in item.iter_markers(name="skipif"): if "condition" not in mark.kwargs: @@ -198,28 +195,19 @@ def evaluate_skip_marks(item: Item) -> Skip | None: class Xfail: """The result of evaluate_xfail_marks().""" - __slots__ = ("raises", "reason", "run", "strict") + __slots__ = ("reason", "run", "strict", "raises") reason: str run: bool strict: bool - raises: ( - type[BaseException] - | tuple[type[BaseException], ...] - | AbstractRaises[BaseException] - | None - ) + raises: Optional[Tuple[Type[BaseException], ...]] -def evaluate_xfail_marks(item: Item) -> Xfail | None: +def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: """Evaluate xfail marks on item, returning Xfail if triggered.""" for mark in item.iter_markers(name="xfail"): run = mark.kwargs.get("run", True) - strict = mark.kwargs.get("strict") - if strict is None: - strict = item.config.getini("strict_xfail") - if strict is None: - strict = item.config.getini("strict") + strict = mark.kwargs.get("strict", item.config.getini("xfail_strict")) raises = mark.kwargs.get("raises", None) if "condition" not in mark.kwargs: conditions = mark.args @@ -241,7 +229,7 @@ def evaluate_xfail_marks(item: Item) -> Xfail | None: # Saves the xfail mark evaluation. Can be refreshed during call if None. -xfailed_key = StashKey[Xfail | None]() +xfailed_key = StashKey[Optional[Xfail]]() @hookimpl(tryfirst=True) @@ -255,8 +243,8 @@ def pytest_runtest_setup(item: Item) -> None: xfail("[NOTRUN] " + xfailed.reason) -@hookimpl(wrapper=True) -def pytest_runtest_call(item: Item) -> Generator[None]: +@hookimpl(hookwrapper=True) +def pytest_runtest_call(item: Item) -> Generator[None, None, None]: xfailed = item.stash.get(xfailed_key, None) if xfailed is None: item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) @@ -264,44 +252,33 @@ def pytest_runtest_call(item: Item) -> Generator[None]: if xfailed and not item.config.option.runxfail and not xfailed.run: xfail("[NOTRUN] " + xfailed.reason) - try: - return (yield) - finally: - # The test run may have added an xfail mark dynamically. - xfailed = item.stash.get(xfailed_key, None) - if xfailed is None: - item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) + yield + + # The test run may have added an xfail mark dynamically. + xfailed = item.stash.get(xfailed_key, None) + if xfailed is None: + item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item) -@hookimpl(wrapper=True) -def pytest_runtest_makereport( - item: Item, call: CallInfo[None] -) -> Generator[None, TestReport, TestReport]: - rep = yield +@hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item: Item, call: CallInfo[None]): + outcome = yield + rep = outcome.get_result() xfailed = item.stash.get(xfailed_key, None) if item.config.option.runxfail: pass # don't interfere elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception): assert call.excinfo.value.msg is not None - rep.wasxfail = call.excinfo.value.msg + rep.wasxfail = "reason: " + call.excinfo.value.msg rep.outcome = "skipped" elif not rep.skipped and xfailed: if call.excinfo: raises = xfailed.raises - if raises is None or ( - ( - isinstance(raises, type | tuple) - and isinstance(call.excinfo.value, raises) - ) - or ( - isinstance(raises, AbstractRaises) - and raises.matches(call.excinfo.value) - ) - ): + if raises is not None and not isinstance(call.excinfo.value, raises): + rep.outcome = "failed" + else: rep.outcome = "skipped" rep.wasxfail = xfailed.reason - else: - rep.outcome = "failed" elif call.when == "call": if xfailed.strict: rep.outcome = "failed" @@ -309,10 +286,9 @@ def pytest_runtest_makereport( else: rep.outcome = "passed" rep.wasxfail = xfailed.reason - return rep -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: +def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/stash.py b/Backend/venv/lib/python3.12/site-packages/_pytest/stash.py index 6a9ff884..e61d75b9 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/stash.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/stash.py @@ -1,9 +1,9 @@ -from __future__ import annotations - from typing import Any from typing import cast +from typing import Dict from typing import Generic from typing import TypeVar +from typing import Union __all__ = ["Stash", "StashKey"] @@ -19,8 +19,6 @@ class StashKey(Generic[T]): A ``StashKey`` is associated with the type ``T`` of the value of the key. A ``StashKey`` is unique and cannot conflict with another key. - - .. versionadded:: 7.0 """ __slots__ = () @@ -63,14 +61,12 @@ class Stash: some_str = stash[some_str_key] # The static type of some_bool is bool. some_bool = stash[some_bool_key] - - .. versionadded:: 7.0 """ __slots__ = ("_storage",) def __init__(self) -> None: - self._storage: dict[StashKey[Any], object] = {} + self._storage: Dict[StashKey[Any], object] = {} def __setitem__(self, key: StashKey[T], value: T) -> None: """Set a value for key.""" @@ -83,7 +79,7 @@ class Stash: """ return cast(T, self._storage[key]) - def get(self, key: StashKey[T], default: D) -> T | D: + def get(self, key: StashKey[T], default: D) -> Union[T, D]: """Get the value for key, or return default if the key wasn't set before.""" try: diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/stepwise.py b/Backend/venv/lib/python3.12/site-packages/_pytest/stepwise.py index 8901540e..74ad9dbd 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/stepwise.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/stepwise.py @@ -1,21 +1,16 @@ -from __future__ import annotations - -import dataclasses -from datetime import datetime -from datetime import timedelta -from typing import Any +from typing import List +from typing import Optional from typing import TYPE_CHECKING +import pytest from _pytest import nodes -from _pytest.cacheprovider import Cache from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.main import Session from _pytest.reports import TestReport - if TYPE_CHECKING: - from typing_extensions import Self + from _pytest.cacheprovider import Cache STEPWISE_CACHE_DIR = "cache/stepwise" @@ -39,20 +34,12 @@ def pytest_addoption(parser: Parser) -> None: help="Ignore the first failing test but stop on the next failing test. " "Implicitly enables --stepwise.", ) - group.addoption( - "--sw-reset", - "--stepwise-reset", - action="store_true", - default=False, - dest="stepwise_reset", - help="Resets stepwise state, restarting the stepwise workflow. " - "Implicitly enables --stepwise.", - ) +@pytest.hookimpl def pytest_configure(config: Config) -> None: - # --stepwise-skip/--stepwise-reset implies stepwise. - if config.option.stepwise_skip or config.option.stepwise_reset: + if config.option.stepwise_skip: + # allow --stepwise-skip to work on it's own merits. config.option.stepwise = True if config.getoption("stepwise"): config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") @@ -65,108 +52,43 @@ def pytest_sessionfinish(session: Session) -> None: # Do not update cache if this process is a xdist worker to prevent # race conditions (#10641). return - - -@dataclasses.dataclass -class StepwiseCacheInfo: - # The nodeid of the last failed test. - last_failed: str | None - - # The number of tests in the last time --stepwise was run. - # We use this information as a simple way to invalidate the cache information, avoiding - # confusing behavior in case the cache is stale. - last_test_count: int | None - - # The date when the cache was last updated, for information purposes only. - last_cache_date_str: str - - @property - def last_cache_date(self) -> datetime: - return datetime.fromisoformat(self.last_cache_date_str) - - @classmethod - def empty(cls) -> Self: - return cls( - last_failed=None, - last_test_count=None, - last_cache_date_str=datetime.now().isoformat(), - ) - - def update_date_to_now(self) -> None: - self.last_cache_date_str = datetime.now().isoformat() + # Clear the list of failing tests if the plugin is not active. + session.config.cache.set(STEPWISE_CACHE_DIR, []) class StepwisePlugin: def __init__(self, config: Config) -> None: self.config = config - self.session: Session | None = None - self.report_status: list[str] = [] + self.session: Optional[Session] = None + self.report_status = "" assert config.cache is not None self.cache: Cache = config.cache + self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None) self.skip: bool = config.getoption("stepwise_skip") - self.reset: bool = config.getoption("stepwise_reset") - self.cached_info = self._load_cached_info() - - def _load_cached_info(self) -> StepwiseCacheInfo: - cached_dict: dict[str, Any] | None = self.cache.get(STEPWISE_CACHE_DIR, None) - if cached_dict: - try: - return StepwiseCacheInfo( - cached_dict["last_failed"], - cached_dict["last_test_count"], - cached_dict["last_cache_date_str"], - ) - except (KeyError, TypeError) as e: - error = f"{type(e).__name__}: {e}" - self.report_status.append(f"error reading cache, discarding ({error})") - - # Cache not found or error during load, return a new cache. - return StepwiseCacheInfo.empty() def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems( - self, config: Config, items: list[nodes.Item] + self, config: Config, items: List[nodes.Item] ) -> None: - last_test_count = self.cached_info.last_test_count - self.cached_info.last_test_count = len(items) - - if self.reset: - self.report_status.append("resetting state, not skipping.") - self.cached_info.last_failed = None + if not self.lastfailed: + self.report_status = "no previously failed tests, not skipping." return - if not self.cached_info.last_failed: - self.report_status.append("no previously failed tests, not skipping.") - return - - if last_test_count is not None and last_test_count != len(items): - self.report_status.append( - f"test count changed, not skipping (now {len(items)} tests, previously {last_test_count})." - ) - self.cached_info.last_failed = None - return - - # Check all item nodes until we find a match on last failed. + # check all item nodes until we find a match on last failed failed_index = None for index, item in enumerate(items): - if item.nodeid == self.cached_info.last_failed: + if item.nodeid == self.lastfailed: failed_index = index break # If the previously failed test was not found among the test items, # do not skip any tests. if failed_index is None: - self.report_status.append("previously failed test not found, not skipping.") + self.report_status = "previously failed test not found, not skipping." else: - cache_age = datetime.now() - self.cached_info.last_cache_date - # Round up to avoid showing microseconds. - cache_age = timedelta(seconds=int(cache_age.total_seconds())) - self.report_status.append( - f"skipping {failed_index} already passed items (cache from {cache_age} ago," - f" use --sw-reset to discard)." - ) + self.report_status = f"skipping {failed_index} already passed items." deselected = items[:failed_index] del items[:failed_index] config.hook.pytest_deselected(items=deselected) @@ -176,13 +98,13 @@ class StepwisePlugin: if self.skip: # Remove test from the failed ones (if it exists) and unset the skip option # to make sure the following tests will not be skipped. - if report.nodeid == self.cached_info.last_failed: - self.cached_info.last_failed = None + if report.nodeid == self.lastfailed: + self.lastfailed = None self.skip = False else: # Mark test as the last failing and interrupt the test session. - self.cached_info.last_failed = report.nodeid + self.lastfailed = report.nodeid assert self.session is not None self.session.shouldstop = ( "Test failed, continuing from this test next run." @@ -192,12 +114,12 @@ class StepwisePlugin: # If the test was actually run and did pass. if report.when == "call": # Remove test from the failed ones, if exists. - if report.nodeid == self.cached_info.last_failed: - self.cached_info.last_failed = None + if report.nodeid == self.lastfailed: + self.lastfailed = None - def pytest_report_collectionfinish(self) -> list[str] | None: - if self.config.get_verbosity() >= 0 and self.report_status: - return [f"stepwise: {x}" for x in self.report_status] + def pytest_report_collectionfinish(self) -> Optional[str]: + if self.config.getoption("verbose") >= 0 and self.report_status: + return f"stepwise: {self.report_status}" return None def pytest_sessionfinish(self) -> None: @@ -205,5 +127,4 @@ class StepwisePlugin: # Do not update cache if this process is a xdist worker to prevent # race conditions (#10641). return - self.cached_info.update_date_to_now() - self.cache.set(STEPWISE_CACHE_DIR, dataclasses.asdict(self.cached_info)) + self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/subtests.py b/Backend/venv/lib/python3.12/site-packages/_pytest/subtests.py deleted file mode 100644 index e0ceb27f..00000000 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/subtests.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Builtin plugin that adds subtests support.""" - -from __future__ import annotations - -from collections import defaultdict -from collections.abc import Callable -from collections.abc import Iterator -from collections.abc import Mapping -from contextlib import AbstractContextManager -from contextlib import contextmanager -from contextlib import ExitStack -from contextlib import nullcontext -import dataclasses -import time -from types import TracebackType -from typing import Any -from typing import TYPE_CHECKING - -import pluggy - -from _pytest._code import ExceptionInfo -from _pytest._io.saferepr import saferepr -from _pytest.capture import CaptureFixture -from _pytest.capture import FDCapture -from _pytest.capture import SysCapture -from _pytest.config import Config -from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser -from _pytest.deprecated import check_ispytest -from _pytest.fixtures import fixture -from _pytest.fixtures import SubRequest -from _pytest.logging import catching_logs -from _pytest.logging import LogCaptureHandler -from _pytest.logging import LoggingPlugin -from _pytest.reports import TestReport -from _pytest.runner import CallInfo -from _pytest.runner import check_interactive_exception -from _pytest.runner import get_reraise_exceptions -from _pytest.stash import StashKey - - -if TYPE_CHECKING: - from typing_extensions import Self - - -def pytest_addoption(parser: Parser) -> None: - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_SUBTESTS, - help=( - "Specify verbosity level for subtests. " - "Higher levels will generate output for passed subtests. Failed subtests are always reported." - ), - ) - - -@dataclasses.dataclass(frozen=True, slots=True, kw_only=True) -class SubtestContext: - """The values passed to Subtests.test() that are included in the test report.""" - - msg: str | None - kwargs: Mapping[str, Any] - - def _to_json(self) -> dict[str, Any]: - return dataclasses.asdict(self) - - @classmethod - def _from_json(cls, d: dict[str, Any]) -> Self: - return cls(msg=d["msg"], kwargs=d["kwargs"]) - - -@dataclasses.dataclass(init=False) -class SubtestReport(TestReport): - context: SubtestContext - - @property - def head_line(self) -> str: - _, _, domain = self.location - return f"{domain} {self._sub_test_description()}" - - def _sub_test_description(self) -> str: - parts = [] - if self.context.msg is not None: - parts.append(f"[{self.context.msg}]") - if self.context.kwargs: - params_desc = ", ".join( - f"{k}={saferepr(v)}" for (k, v) in self.context.kwargs.items() - ) - parts.append(f"({params_desc})") - return " ".join(parts) or "()" - - def _to_json(self) -> dict[str, Any]: - data = super()._to_json() - del data["context"] - data["_report_type"] = "SubTestReport" - data["_subtest.context"] = self.context._to_json() - return data - - @classmethod - def _from_json(cls, reportdict: dict[str, Any]) -> SubtestReport: - report = super()._from_json(reportdict) - report.context = SubtestContext._from_json(reportdict["_subtest.context"]) - return report - - @classmethod - def _new( - cls, - test_report: TestReport, - context: SubtestContext, - captured_output: Captured | None, - captured_logs: CapturedLogs | None, - ) -> Self: - result = super()._from_json(test_report._to_json()) - result.context = context - - if captured_output: - if captured_output.out: - result.sections.append(("Captured stdout call", captured_output.out)) - if captured_output.err: - result.sections.append(("Captured stderr call", captured_output.err)) - - if captured_logs and (log := captured_logs.handler.stream.getvalue()): - result.sections.append(("Captured log call", log)) - - return result - - -@fixture -def subtests(request: SubRequest) -> Subtests: - """Provides subtests functionality.""" - capmam = request.node.config.pluginmanager.get_plugin("capturemanager") - suspend_capture_ctx = ( - capmam.global_and_fixture_disabled if capmam is not None else nullcontext - ) - return Subtests(request.node.ihook, suspend_capture_ctx, request, _ispytest=True) - - -class Subtests: - """Subtests fixture, enables declaring subtests inside test functions via the :meth:`test` method.""" - - def __init__( - self, - ihook: pluggy.HookRelay, - suspend_capture_ctx: Callable[[], AbstractContextManager[None]], - request: SubRequest, - *, - _ispytest: bool = False, - ) -> None: - check_ispytest(_ispytest) - self._ihook = ihook - self._suspend_capture_ctx = suspend_capture_ctx - self._request = request - - def test( - self, - msg: str | None = None, - **kwargs: Any, - ) -> _SubTestContextManager: - """ - Context manager for subtests, capturing exceptions raised inside the subtest scope and - reporting assertion failures and errors individually. - - Usage - ----- - - .. code-block:: python - - def test(subtests): - for i in range(5): - with subtests.test("custom message", i=i): - assert i % 2 == 0 - - :param msg: - If given, the message will be shown in the test report in case of subtest failure. - - :param kwargs: - Arbitrary values that are also added to the subtest report. - """ - return _SubTestContextManager( - self._ihook, - msg, - kwargs, - request=self._request, - suspend_capture_ctx=self._suspend_capture_ctx, - config=self._request.config, - ) - - -@dataclasses.dataclass -class _SubTestContextManager: - """ - Context manager for subtests, capturing exceptions raised inside the subtest scope and handling - them through the pytest machinery. - """ - - # Note: initially the logic for this context manager was implemented directly - # in Subtests.test() as a @contextmanager, however, it is not possible to control the output fully when - # exiting from it due to an exception when in `--exitfirst` mode, so this was refactored into an - # explicit context manager class (pytest-dev/pytest-subtests#134). - - ihook: pluggy.HookRelay - msg: str | None - kwargs: dict[str, Any] - suspend_capture_ctx: Callable[[], AbstractContextManager[None]] - request: SubRequest - config: Config - - def __enter__(self) -> None: - __tracebackhide__ = True - - self._start = time.time() - self._precise_start = time.perf_counter() - self._exc_info = None - - self._exit_stack = ExitStack() - self._captured_output = self._exit_stack.enter_context( - capturing_output(self.request) - ) - self._captured_logs = self._exit_stack.enter_context( - capturing_logs(self.request) - ) - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - __tracebackhide__ = True - if exc_val is not None: - exc_info = ExceptionInfo.from_exception(exc_val) - else: - exc_info = None - - self._exit_stack.close() - - precise_stop = time.perf_counter() - duration = precise_stop - self._precise_start - stop = time.time() - - call_info = CallInfo[None]( - None, - exc_info, - start=self._start, - stop=stop, - duration=duration, - when="call", - _ispytest=True, - ) - report = self.ihook.pytest_runtest_makereport( - item=self.request.node, call=call_info - ) - sub_report = SubtestReport._new( - report, - SubtestContext(msg=self.msg, kwargs=self.kwargs), - captured_output=self._captured_output, - captured_logs=self._captured_logs, - ) - - if sub_report.failed: - failed_subtests = self.config.stash[failed_subtests_key] - failed_subtests[self.request.node.nodeid] += 1 - - with self.suspend_capture_ctx(): - self.ihook.pytest_runtest_logreport(report=sub_report) - - if check_interactive_exception(call_info, sub_report): - self.ihook.pytest_exception_interact( - node=self.request.node, call=call_info, report=sub_report - ) - - if exc_val is not None: - if isinstance(exc_val, get_reraise_exceptions(self.config)): - return False - if self.request.session.shouldfail: - return False - return True - - -@contextmanager -def capturing_output(request: SubRequest) -> Iterator[Captured]: - option = request.config.getoption("capture", None) - - capman = request.config.pluginmanager.getplugin("capturemanager") - if getattr(capman, "_capture_fixture", None): - # capsys or capfd are active, subtest should not capture. - fixture = None - elif option == "sys": - fixture = CaptureFixture(SysCapture, request, _ispytest=True) - elif option == "fd": - fixture = CaptureFixture(FDCapture, request, _ispytest=True) - else: - fixture = None - - if fixture is not None: - fixture._start() - - captured = Captured() - try: - yield captured - finally: - if fixture is not None: - out, err = fixture.readouterr() - fixture.close() - captured.out = out - captured.err = err - - -@contextmanager -def capturing_logs( - request: SubRequest, -) -> Iterator[CapturedLogs | None]: - logging_plugin: LoggingPlugin | None = request.config.pluginmanager.getplugin( - "logging-plugin" - ) - if logging_plugin is None: - yield None - else: - handler = LogCaptureHandler() - handler.setFormatter(logging_plugin.formatter) - - captured_logs = CapturedLogs(handler) - with catching_logs(handler, level=logging_plugin.log_level): - yield captured_logs - - -@dataclasses.dataclass -class Captured: - out: str = "" - err: str = "" - - -@dataclasses.dataclass -class CapturedLogs: - handler: LogCaptureHandler - - -def pytest_report_to_serializable(report: TestReport) -> dict[str, Any] | None: - if isinstance(report, SubtestReport): - return report._to_json() - return None - - -def pytest_report_from_serializable(data: dict[str, Any]) -> SubtestReport | None: - if data.get("_report_type") == "SubTestReport": - return SubtestReport._from_json(data) - return None - - -# Dict of nodeid -> number of failed subtests. -# Used to fail top-level tests that passed but contain failed subtests. -failed_subtests_key = StashKey[defaultdict[str, int]]() - - -def pytest_configure(config: Config) -> None: - config.stash[failed_subtests_key] = defaultdict(lambda: 0) - - -@hookimpl(tryfirst=True) -def pytest_report_teststatus( - report: TestReport, - config: Config, -) -> tuple[str, str, str | Mapping[str, bool]] | None: - if report.when != "call": - return None - - quiet = config.get_verbosity(Config.VERBOSITY_SUBTESTS) == 0 - if isinstance(report, SubtestReport): - outcome = report.outcome - description = report._sub_test_description() - - if hasattr(report, "wasxfail"): - if quiet: - return "", "", "" - elif outcome == "skipped": - category = "xfailed" - short = "y" # x letter is used for regular xfail, y for subtest xfail - status = "SUBXFAIL" - # outcome == "passed" in an xfail is only possible via a @pytest.mark.xfail mark, which - # is not applicable to a subtest, which only handles pytest.xfail(). - else: # pragma: no cover - # This should not normally happen, unless some plugin is setting wasxfail without - # the correct outcome. Pytest expects the call outcome to be either skipped or - # passed in case of xfail. - # Let's pass this report to the next hook. - return None - return category, short, f"{status}{description}" - - if report.failed: - return outcome, "u", f"SUBFAILED{description}" - else: - if report.passed: - if quiet: - return "", "", "" - else: - return f"subtests {outcome}", "u", f"SUBPASSED{description}" - elif report.skipped: - if quiet: - return "", "", "" - else: - return outcome, "-", f"SUBSKIPPED{description}" - - else: - failed_subtests_count = config.stash[failed_subtests_key][report.nodeid] - # Top-level test, fail if it contains failed subtests and it has passed. - if report.passed and failed_subtests_count > 0: - report.outcome = "failed" - suffix = "s" if failed_subtests_count > 1 else "" - report.longrepr = f"contains {failed_subtests_count} failed subtest{suffix}" - - return None diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/terminal.py b/Backend/venv/lib/python3.12/site-packages/_pytest/terminal.py index 4517b05b..b0cdb58c 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/terminal.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/terminal.py @@ -1,46 +1,46 @@ -# mypy: allow-untyped-defs """Terminal reporting of the full testing process. This is a good source for looking at the various reporting hooks. """ - -from __future__ import annotations - import argparse -from collections import Counter -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Mapping -from collections.abc import Sequence import dataclasses import datetime -from functools import partial import inspect -import os -from pathlib import Path import platform import sys import textwrap -from typing import Any -from typing import ClassVar -from typing import final -from typing import Literal -from typing import NamedTuple -from typing import TextIO -from typing import TYPE_CHECKING import warnings +from collections import Counter +from functools import partial +from pathlib import Path +from typing import Any +from typing import Callable +from typing import cast +from typing import ClassVar +from typing import Dict +from typing import Generator +from typing import List +from typing import Mapping +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Set +from typing import TextIO +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union import pluggy -from _pytest import compat +import _pytest._version from _pytest import nodes from _pytest import timing from _pytest._code import ExceptionInfo from _pytest._code.code import ExceptionRepr from _pytest._io import TerminalWriter from _pytest._io.wcwidth import wcswidth -import _pytest._version -from _pytest.compat import running_on_ci +from _pytest.assertion.util import running_on_ci +from _pytest.compat import final from _pytest.config import _PluggyPlugin from _pytest.config import Config from _pytest.config import ExitCode @@ -54,8 +54,9 @@ from _pytest.reports import BaseReport from _pytest.reports import CollectReport from _pytest.reports import TestReport - if TYPE_CHECKING: + from typing_extensions import Literal + from _pytest.main import Session @@ -70,9 +71,6 @@ KNOWN_TYPES = ( "xpassed", "warnings", "error", - "subtests passed", - "subtests failed", - "subtests skipped", ) _REPORTCHARS_DEFAULT = "fE" @@ -91,7 +89,7 @@ class MoreQuietAction(argparse.Action): dest: str, default: object = None, required: bool = False, - help: str | None = None, + help: Optional[str] = None, ) -> None: super().__init__( option_strings=option_strings, @@ -106,8 +104,8 @@ class MoreQuietAction(argparse.Action): self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: str | Sequence[object] | None, - option_string: str | None = None, + values: Union[str, Sequence[object], None], + option_string: Optional[str] = None, ) -> None: new_count = getattr(namespace, self.dest, 0) - 1 setattr(namespace, self.dest, new_count) @@ -132,12 +130,12 @@ class TestShortLogReport(NamedTuple): category: str letter: str - word: str | tuple[str, Mapping[str, bool]] + word: Union[str, Tuple[str, Mapping[str, bool]]] def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "Reporting", after="general") - group._addoption( # private to use reserved lower-case short option + group._addoption( "-v", "--verbose", action="count", @@ -145,35 +143,21 @@ def pytest_addoption(parser: Parser) -> None: dest="verbose", help="Increase verbosity", ) - group.addoption( + group._addoption( "--no-header", action="store_true", default=False, dest="no_header", help="Disable header", ) - group.addoption( + group._addoption( "--no-summary", action="store_true", default=False, dest="no_summary", help="Disable summary", ) - group.addoption( - "--no-fold-skipped", - action="store_false", - dest="fold_skipped", - default=True, - help="Do not fold skipped tests in short summary.", - ) - group.addoption( - "--force-short-summary", - action="store_true", - dest="force_short_summary", - default=False, - help="Force condensed summary output regardless of verbosity level.", - ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-q", "--quiet", action=MoreQuietAction, @@ -181,14 +165,14 @@ def pytest_addoption(parser: Parser) -> None: dest="verbose", help="Decrease verbosity", ) - group.addoption( + group._addoption( "--verbosity", dest="verbose", type=int, default=0, help="Set verbosity. Default: 0.", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-r", action="store", dest="reportchars", @@ -200,7 +184,7 @@ def pytest_addoption(parser: Parser) -> None: "(w)arnings are enabled by default (see --disable-warnings), " "'N' can be used to reset the list. (default: 'fE').", ) - group.addoption( + group._addoption( "--disable-warnings", "--disable-pytest-warnings", default=False, @@ -208,7 +192,7 @@ def pytest_addoption(parser: Parser) -> None: action="store_true", help="Disable warnings summary", ) - group._addoption( # private to use reserved lower-case short option + group._addoption( "-l", "--showlocals", action="store_true", @@ -216,13 +200,13 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Show locals in tracebacks (disabled by default)", ) - group.addoption( + group._addoption( "--no-showlocals", action="store_false", dest="showlocals", help="Hide locals in tracebacks (negate --showlocals passed through addopts)", ) - group.addoption( + group._addoption( "--tb", metavar="style", action="store", @@ -231,14 +215,7 @@ def pytest_addoption(parser: Parser) -> None: choices=["auto", "long", "short", "no", "line", "native"], help="Traceback print mode (auto/long/short/line/native/no)", ) - group.addoption( - "--xfail-tb", - action="store_true", - dest="xfail_tb", - default=False, - help="Show tracebacks for xfail (as long as --tb != no)", - ) - group.addoption( + group._addoption( "--show-capture", action="store", dest="showcapture", @@ -247,14 +224,14 @@ def pytest_addoption(parser: Parser) -> None: help="Controls how captured stdout/stderr/log is shown on failed tests. " "Default: all.", ) - group.addoption( + group._addoption( "--fulltrace", "--full-trace", action="store_true", default=False, help="Don't cut any tracebacks (default is to cut)", ) - group.addoption( + group._addoption( "--color", metavar="color", action="store", @@ -263,7 +240,7 @@ def pytest_addoption(parser: Parser) -> None: choices=["yes", "no", "auto"], help="Color terminal output (yes/no/auto)", ) - group.addoption( + group._addoption( "--code-highlight", default="yes", choices=["yes", "no"], @@ -278,14 +255,6 @@ def pytest_addoption(parser: Parser) -> None: "progress even when capture=no)", default="progress", ) - Config._add_verbosity_ini( - parser, - Config.VERBOSITY_TEST_CASES, - help=( - "Specify a verbosity level for test case execution, overriding the main level. " - "Higher levels will provide more detailed information about each test case executed." - ), - ) def pytest_configure(config: Config) -> None: @@ -299,17 +268,6 @@ def pytest_configure(config: Config) -> None: config.trace.root.setprocessor("pytest:config", mywriter) - if reporter.isatty(): - # Some terminals interpret OSC 9;4 as desktop notification, - # skip on those we know (#13896). - should_skip_terminal_progress = ( - # iTerm2 (reported on version 3.6.5). - "ITERM_SESSION_ID" in os.environ - ) - if not should_skip_terminal_progress: - plugin = TerminalProgressPlugin(reporter) - config.pluginmanager.register(plugin, "terminalprogress") - def getreportopt(config: Config) -> str: reportchars: str = config.option.reportchars @@ -337,7 +295,7 @@ def getreportopt(config: Config) -> str: @hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: +def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: letter = "F" if report.passed: letter = "." @@ -365,12 +323,12 @@ class WarningReport: """ message: str - nodeid: str | None = None - fslocation: tuple[str, int] | None = None + nodeid: Optional[str] = None + fslocation: Optional[Tuple[str, int]] = None count_towards_summary: ClassVar = True - def get_location(self, config: Config) -> str | None: + def get_location(self, config: Config) -> Optional[str]: """Return the more user-friendly information about the location of a warning, or None.""" if self.nodeid: return self.nodeid @@ -383,39 +341,33 @@ class WarningReport: @final class TerminalReporter: - def __init__(self, config: Config, file: TextIO | None = None) -> None: + def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session: Session | None = None - self._showfspath: bool | None = None + self._session: Optional[Session] = None + self._showfspath: Optional[bool] = None - self.stats: dict[str, list[Any]] = {} - self._main_color: str | None = None - self._known_types: list[str] | None = None + self.stats: Dict[str, List[Any]] = {} + self._main_color: Optional[str] = None + self._known_types: Optional[List[str]] = None self.startpath = config.invocation_params.dir if file is None: file = sys.stdout self._tw = _pytest.config.create_terminal_writer(config, file) self._screen_width = self._tw.fullwidth - self.currentfspath: None | Path | str | int = None + self.currentfspath: Union[None, Path, str, int] = None self.reportchars = getreportopt(config) - self.foldskipped = config.option.fold_skipped self.hasmarkup = self._tw.hasmarkup - # isatty should be a method but was wrongly implemented as a boolean. - # We use CallableBool here to support both. - self.isatty = compat.CallableBool(file.isatty()) - self._progress_nodeids_reported: set[str] = set() - self._timing_nodeids_reported: set[str] = set() + self.isatty = file.isatty() + self._progress_nodeids_reported: Set[str] = set() self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write = timing.Instant() - self._already_displayed_warnings: int | None = None - self._keyboardinterrupt_memo: ExceptionRepr | None = None + self._collect_report_last_write: Optional[float] = None + self._already_displayed_warnings: Optional[int] = None + self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None - def _determine_show_progress_info( - self, - ) -> Literal["progress", "count", "times", False]: + def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": """Return whether we should display progress information based on the current config.""" # do not show progress if we are not capturing output (#3038) unless explicitly # overridden by progress-even-when-capture-no @@ -429,12 +381,10 @@ class TerminalReporter: if self.config.getoption("setupshow", False): return False cfg: str = self.config.getini("console_output_style") - if cfg in {"progress", "progress-even-when-capture-no"}: + if cfg == "progress" or cfg == "progress-even-when-capture-no": return "progress" elif cfg == "count": return "count" - elif cfg == "times": - return "times" else: return False @@ -458,30 +408,22 @@ class TerminalReporter: @property def showfspath(self) -> bool: if self._showfspath is None: - return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0 + return self.verbosity >= 0 return self._showfspath @showfspath.setter - def showfspath(self, value: bool | None) -> None: + def showfspath(self, value: Optional[bool]) -> None: self._showfspath = value @property def showlongtestinfo(self) -> bool: - return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0 - - @property - def reported_progress(self) -> int: - """The amount of items reported in the progress so far. - - :meta private: - """ - return len(self._progress_nodeids_reported) + return self.verbosity > 0 def hasopt(self, char: str) -> bool: char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars - def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None: + def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None: fspath = self.config.rootpath / nodeid.split("::")[0] if self.currentfspath is None or fspath != self.currentfspath: if self.currentfspath is not None and self._show_progress_info: @@ -531,13 +473,10 @@ class TerminalReporter: def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: self._tw.write(content, flush=flush, **markup) - def write_raw(self, content: str, *, flush: bool = False) -> None: - self._tw.write_raw(content, flush=flush) - def flush(self) -> None: self._tw.flush() - def write_line(self, line: str | bytes, **markup: bool) -> None: + def write_line(self, line: Union[str, bytes], **markup: bool) -> None: if not isinstance(line, str): line = str(line, errors="replace") self.ensure_newline() @@ -564,8 +503,8 @@ class TerminalReporter: def write_sep( self, sep: str, - title: str | None = None, - fullwidth: int | None = None, + title: Optional[str] = None, + fullwidth: Optional[int] = None, **markup: bool, ) -> None: self.ensure_newline() @@ -615,13 +554,12 @@ class TerminalReporter: self._add_stats("deselected", items) def pytest_runtest_logstart( - self, nodeid: str, location: tuple[str, int | None, str] + self, nodeid: str, location: Tuple[str, Optional[int], str] ) -> None: - fspath, lineno, domain = location # Ensure that the path is printed before the # 1st test of a module starts running. if self.showlongtestinfo: - line = self._locationline(nodeid, fspath, lineno, domain) + line = self._locationline(nodeid, *location) self.write_ensure_prefix(line, "") self.flush() elif self.showfspath: @@ -644,6 +582,7 @@ class TerminalReporter: if not letter and not word: # Probably passed setup/teardown. return + running_xdist = hasattr(rep, "node") if markup is None: was_xfail = hasattr(report, "wasxfail") if rep.passed and not was_xfail: @@ -656,25 +595,16 @@ class TerminalReporter: markup = {"yellow": True} else: markup = {} - self._progress_nodeids_reported.add(rep.nodeid) - if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: + if self.verbosity <= 0: self._tw.write(letter, **markup) - # When running in xdist, the logreport and logfinish of multiple - # items are interspersed, e.g. `logreport`, `logreport`, - # `logfinish`, `logfinish`. To avoid the "past edge" calculation - # from getting confused and overflowing (#7166), do the past edge - # printing here and not in logfinish, except for the 100% which - # should only be printed after all teardowns are finished. - if self._show_progress_info and not self._is_last_item: - self._write_progress_information_if_past_edge() else: + self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) - running_xdist = hasattr(rep, "node") if not running_xdist: self.write_ensure_prefix(line, word, **markup) if rep.skipped or hasattr(report, "wasxfail"): reason = _get_raw_skip_reason(rep) - if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2: + if self.config.option.verbose < 2: available_width = ( (self._tw.fullwidth - self._tw.width_of_current_line) - len(" [100%]") @@ -692,7 +622,7 @@ class TerminalReporter: self._write_progress_information_filling_space() else: self.ensure_newline() - self._tw.write(f"[{rep.node.gateway.id}]") + self._tw.write("[%s]" % rep.node.gateway.id) if self._show_progress_info: self._tw.write( self._get_progress_information_message() + " ", cyan=True @@ -707,82 +637,45 @@ class TerminalReporter: @property def _is_last_item(self) -> bool: assert self._session is not None - return self.reported_progress == self._session.testscollected + return len(self._progress_nodeids_reported) == self._session.testscollected - @hookimpl(wrapper=True) - def pytest_runtestloop(self) -> Generator[None, object, object]: - result = yield + def pytest_runtest_logfinish(self, nodeid: str) -> None: + assert self._session + if self.verbosity <= 0 and self._show_progress_info: + if self._show_progress_info == "count": + num_tests = self._session.testscollected + progress_length = len(f" [{num_tests}/{num_tests}]") + else: + progress_length = len(" [100%]") - # Write the final/100% progress -- deferred until the loop is complete. - if ( - self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 - and self._show_progress_info - and self.reported_progress - ): - self._write_progress_information_filling_space() + self._progress_nodeids_reported.add(nodeid) - return result + if self._is_last_item: + self._write_progress_information_filling_space() + else: + main_color, _ = self._get_main_color() + w = self._width_of_current_line + past_edge = w + progress_length + 1 >= self._screen_width + if past_edge: + msg = self._get_progress_information_message() + self._tw.write(msg + "\n", **{main_color: True}) def _get_progress_information_message(self) -> str: assert self._session collected = self._session.testscollected if self._show_progress_info == "count": if collected: - progress = self.reported_progress + progress = self._progress_nodeids_reported counter_format = f"{{:{len(str(collected))}d}}" format_string = f" [{counter_format}/{{}}]" - return format_string.format(progress, collected) + return format_string.format(len(progress), collected) return f" [ {collected} / {collected} ]" - if self._show_progress_info == "times": - if not collected: - return "" - all_reports = ( - self._get_reports_to_display("passed") - + self._get_reports_to_display("xpassed") - + self._get_reports_to_display("failed") - + self._get_reports_to_display("xfailed") - + self._get_reports_to_display("skipped") - + self._get_reports_to_display("error") - + self._get_reports_to_display("") - ) - current_location = all_reports[-1].location[0] - not_reported = [ - r for r in all_reports if r.nodeid not in self._timing_nodeids_reported - ] - tests_in_module = sum( - i.location[0] == current_location for i in self._session.items - ) - tests_completed = sum( - r.when == "setup" - for r in not_reported - if r.location[0] == current_location - ) - last_in_module = tests_completed == tests_in_module - if self.showlongtestinfo or last_in_module: - self._timing_nodeids_reported.update(r.nodeid for r in not_reported) - return format_node_duration( - sum(r.duration for r in not_reported if isinstance(r, TestReport)) - ) - return "" - if collected: - return f" [{self.reported_progress * 100 // collected:3d}%]" - return " [100%]" - - def _write_progress_information_if_past_edge(self) -> None: - w = self._width_of_current_line - if self._show_progress_info == "count": - assert self._session - num_tests = self._session.testscollected - progress_length = len(f" [{num_tests}/{num_tests}]") - elif self._show_progress_info == "times": - progress_length = len(" 99h 59m") else: - progress_length = len(" [100%]") - past_edge = w + progress_length + 1 >= self._screen_width - if past_edge: - main_color, _ = self._get_main_color() - msg = self._get_progress_information_message() - self._tw.write(msg + "\n", **{main_color: True}) + if collected: + return " [{:3d}%]".format( + len(self._progress_nodeids_reported) * 100 // collected + ) + return " [100%]" def _write_progress_information_filling_space(self) -> None: color, _ = self._get_main_color() @@ -797,9 +690,10 @@ class TerminalReporter: return self._tw.width_of_current_line def pytest_collection(self) -> None: - if self.isatty(): + if self.isatty: if self.config.option.verbose >= 0: self.write("collecting ... ", flush=True, bold=True) + self._collect_report_last_write = timing.time() elif self.config.option.verbose >= 1: self.write("collecting ... ", flush=True, bold=True) @@ -810,7 +704,7 @@ class TerminalReporter: self._add_stats("skipped", [report]) items = [x for x in report.result if isinstance(x, Item)] self._numcollected += len(items) - if self.isatty(): + if self.isatty: self.report_collect() def report_collect(self, final: bool = False) -> None: @@ -818,13 +712,14 @@ class TerminalReporter: return if not final: - # Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`. + # Only write "collecting" report every 0.5s. + t = timing.time() if ( - self._collect_report_last_write.elapsed().seconds - < REPORT_COLLECTING_RESOLUTION + self._collect_report_last_write is not None + and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION ): return - self._collect_report_last_write = timing.Instant() + self._collect_report_last_write = t errors = len(self.stats.get("error", [])) skipped = len(self.stats.get("skipped", [])) @@ -835,14 +730,14 @@ class TerminalReporter: str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") ) if errors: - line += f" / {errors} error{'s' if errors != 1 else ''}" + line += " / %d error%s" % (errors, "s" if errors != 1 else "") if deselected: - line += f" / {deselected} deselected" + line += " / %d deselected" % deselected if skipped: - line += f" / {skipped} skipped" + line += " / %d skipped" % skipped if self._numcollected > selected: - line += f" / {selected} selected" - if self.isatty(): + line += " / %d selected" % selected + if self.isatty: self.rewrite(line, bold=True, erase=True) if final: self.write("\n") @@ -850,9 +745,9 @@ class TerminalReporter: self.write_line(line) @hookimpl(trylast=True) - def pytest_sessionstart(self, session: Session) -> None: + def pytest_sessionstart(self, session: "Session") -> None: self._session = session - self._session_start = timing.Instant() + self._sessionstarttime = timing.time() if not self.showheader: return self.write_sep("=", "test session starts", bold=True) @@ -863,7 +758,9 @@ class TerminalReporter: if pypy_version_info: verinfo = ".".join(map(str, pypy_version_info[:3])) msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]" - msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}" + msg += ", pytest-{}, pluggy-{}".format( + _pytest._version.version, pluggy.__version__ + ) if ( self.verbosity > 0 or self.config.option.debug @@ -877,7 +774,7 @@ class TerminalReporter: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: Sequence[str | Sequence[str]] + self, lines: Sequence[Union[str, Sequence[str]]] ) -> None: for line_or_lines in reversed(lines): if isinstance(line_or_lines, str): @@ -886,29 +783,22 @@ class TerminalReporter: for line in line_or_lines: self.write_line(line) - def pytest_report_header(self, config: Config) -> list[str]: + def pytest_report_header(self, config: Config) -> List[str]: result = [f"rootdir: {config.rootpath}"] if config.inipath: - warning = "" - if config._ignored_config_files: - warning = f" (WARNING: ignoring pytest config in {', '.join(config._ignored_config_files)}!)" - result.append( - "configfile: " + bestrelpath(config.rootpath, config.inipath) + warning - ) + result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: list[str] = config.getini("testpaths") + testpaths: List[str] = config.getini("testpaths") result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - result.append( - "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo))) - ) + result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo))) return result - def pytest_collection_finish(self, session: Session) -> None: + def pytest_collection_finish(self, session: "Session") -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( @@ -931,17 +821,16 @@ class TerminalReporter: rep.toterminal(self._tw) def _printcollecteditems(self, items: Sequence[Item]) -> None: - test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) - if test_cases_verbosity < 0: - if test_cases_verbosity < -1: + if self.config.option.verbose < 0: + if self.config.option.verbose < -1: counts = Counter(item.nodeid.split("::", 1)[0] for item in items) for name, count in sorted(counts.items()): - self._tw.line(f"{name}: {count}") + self._tw.line("%s: %d" % (name, count)) else: for item in items: self._tw.line(item.nodeid) return - stack: list[Node] = [] + stack: List[Node] = [] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -953,18 +842,19 @@ class TerminalReporter: stack.append(col) indent = (len(stack) - 1) * " " self._tw.line(f"{indent}{col}") - if test_cases_verbosity >= 1: + if self.config.option.verbose >= 1: obj = getattr(col, "obj", None) doc = inspect.getdoc(obj) if obj else None if doc: for line in doc.splitlines(): self._tw.line("{}{}".format(indent + " ", line)) - @hookimpl(wrapper=True) + @hookimpl(hookwrapper=True) def pytest_sessionfinish( - self, session: Session, exitstatus: int | ExitCode - ) -> Generator[None]: - result = yield + self, session: "Session", exitstatus: Union[int, ExitCode] + ): + outcome = yield + outcome.get_result() self._tw.line("") summary_exit_codes = ( ExitCode.OK, @@ -985,22 +875,17 @@ class TerminalReporter: elif session.shouldstop: self.write_sep("!", str(session.shouldstop), red=True) self.summary_stats() - return result - @hookimpl(wrapper=True) - def pytest_terminal_summary(self) -> Generator[None]: + @hookimpl(hookwrapper=True) + def pytest_terminal_summary(self) -> Generator[None, None, None]: self.summary_errors() self.summary_failures() - self.summary_xfailures() self.summary_warnings() self.summary_passes() - self.summary_xpasses() - try: - return (yield) - finally: - self.short_test_summary() - # Display any extra warnings from teardown here (if any). - self.summary_warnings() + yield + self.short_test_summary() + # Display any extra warnings from teardown here (if any). + self.summary_warnings() def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) @@ -1026,7 +911,7 @@ class TerminalReporter: ) def _locationline( - self, nodeid: str, fspath: str, lineno: int | None, domain: str + self, nodeid: str, fspath: str, lineno: Optional[int], domain: str ) -> str: def mkrel(nodeid: str) -> str: line = self.config.cwd_relative_nodeid(nodeid) @@ -1037,7 +922,7 @@ class TerminalReporter: line += "[".join(values) return line - # fspath comes from testid which has a "/"-normalized path. + # collect_fspath comes from testid which has a "/"-normalized path. if fspath: res = mkrel(nodeid) if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( @@ -1071,7 +956,7 @@ class TerminalReporter: def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings: list[WarningReport] | None = self.stats.get("warnings") + all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings") if not all_warnings: return @@ -1084,11 +969,11 @@ class TerminalReporter: if not warning_reports: return - reports_grouped_by_message: dict[str, list[WarningReport]] = {} + reports_grouped_by_message: Dict[str, List[WarningReport]] = {} for wr in warning_reports: reports_grouped_by_message.setdefault(wr.message, []).append(wr) - def collapsed_location_report(reports: list[WarningReport]) -> str: + def collapsed_location_report(reports: List[WarningReport]) -> str: locations = [] for w in reports: location = w.get_location(self.config) @@ -1124,20 +1009,12 @@ class TerminalReporter: ) def summary_passes(self) -> None: - self.summary_passes_combined("passed", "PASSES", "P") - - def summary_xpasses(self) -> None: - self.summary_passes_combined("xpassed", "XPASSES", "X") - - def summary_passes_combined( - self, which_reports: str, sep_title: str, needed_opt: str - ) -> None: if self.config.option.tbstyle != "no": - if self.hasopt(needed_opt): - reports: list[TestReport] = self.getreports(which_reports) + if self.hasopt("P"): + reports: List[TestReport] = self.getreports("passed") if not reports: return - self.write_sep("=", sep_title) + self.write_sep("=", "PASSES") for rep in reports: if rep.sections: msg = self._getfailureheadline(rep) @@ -1145,7 +1022,7 @@ class TerminalReporter: self._outrep_summary(rep) self._handle_teardown_sections(rep.nodeid) - def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: + def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: reports = self.getreports("") return [ report @@ -1171,43 +1048,25 @@ class TerminalReporter: self._tw.line(content) def summary_failures(self) -> None: - style = self.config.option.tbstyle - self.summary_failures_combined("failed", "FAILURES", style=style) - - def summary_xfailures(self) -> None: - show_tb = self.config.option.xfail_tb - style = self.config.option.tbstyle if show_tb else "no" - self.summary_failures_combined("xfailed", "XFAILURES", style=style) - - def summary_failures_combined( - self, - which_reports: str, - sep_title: str, - *, - style: str, - needed_opt: str | None = None, - ) -> None: - if style != "no": - if not needed_opt or self.hasopt(needed_opt): - reports: list[BaseReport] = self.getreports(which_reports) - if not reports: - return - self.write_sep("=", sep_title) - if style == "line": - for rep in reports: - line = self._getcrashline(rep) - self._outrep_summary(rep) - self.write_line(line) - else: - for rep in reports: - msg = self._getfailureheadline(rep) - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) + if self.config.option.tbstyle != "no": + reports: List[BaseReport] = self.getreports("failed") + if not reports: + return + self.write_sep("=", "FAILURES") + if self.config.option.tbstyle == "line": + for rep in reports: + line = self._getcrashline(rep) + self.write_line(line) + else: + for rep in reports: + msg = self._getfailureheadline(rep) + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) def summary_errors(self) -> None: if self.config.option.tbstyle != "no": - reports: list[BaseReport] = self.getreports("error") + reports: List[BaseReport] = self.getreports("error") if not reports: return self.write_sep("=", "ERRORS") @@ -1237,7 +1096,7 @@ class TerminalReporter: if self.verbosity < -1: return - session_duration = self._session_start.elapsed() + session_duration = timing.time() - self._sessionstarttime (parts, main_color) = self.build_summary_stats_line() line_parts = [] @@ -1252,7 +1111,7 @@ class TerminalReporter: msg = ", ".join(line_parts) main_markup = {main_color: True} - duration = f" in {format_session_duration(session_duration.seconds)}" + duration = f" in {format_session_duration(session_duration)}" duration_with_markup = self._tw.markup(duration, **main_markup) if display_sep: fullwidth += len(duration_with_markup) - len(duration) @@ -1274,7 +1133,7 @@ class TerminalReporter: if not self.reportchars: return - def show_simple(lines: list[str], *, stat: str) -> None: + def show_simple(lines: List[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return @@ -1286,13 +1145,13 @@ class TerminalReporter: ) lines.append(line) - def show_xfailed(lines: list[str]) -> None: + def show_xfailed(lines: List[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} + verbose_word = rep._get_verbose_word(self.config) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) line = f"{markup_word} {nodeid}" reason = rep.wasxfail @@ -1301,64 +1160,38 @@ class TerminalReporter: lines.append(line) - def show_xpassed(lines: list[str]) -> None: + def show_xpassed(lines: List[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} + verbose_word = rep._get_verbose_word(self.config) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" reason = rep.wasxfail - if reason: - line += " - " + str(reason) - lines.append(line) + lines.append(f"{markup_word} {nodeid} {reason}") - def show_skipped_folded(lines: list[str]) -> None: - skipped: list[CollectReport] = self.stats.get("skipped", []) + def show_skipped(lines: List[str]) -> None: + skipped: List[CollectReport] = self.stats.get("skipped", []) fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return - verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} + verbose_word = skipped[0]._get_verbose_word(self.config) + markup_word = self._tw.markup( + verbose_word, **{_color_for_type["warnings"]: True} ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) prefix = "Skipped: " for num, fspath, lineno, reason in fskips: if reason.startswith(prefix): reason = reason[len(prefix) :] if lineno is not None: - lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}") + lines.append( + "%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason) + ) else: - lines.append(f"{markup_word} [{num}] {fspath}: {reason}") + lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) - def show_skipped_unfolded(lines: list[str]) -> None: - skipped: list[CollectReport] = self.stats.get("skipped", []) - - for rep in skipped: - assert rep.longrepr is not None - assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr) - assert len(rep.longrepr) == 3, (rep, rep.longrepr) - - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - self.config, {_color_for_type["warnings"]: True} - ) - markup_word = self._tw.markup(verbose_word, **verbose_markup) - nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" - reason = rep.longrepr[2] - if reason: - line += " - " + str(reason) - lines.append(line) - - def show_skipped(lines: list[str]) -> None: - if self.foldskipped: - show_skipped_folded(lines) - else: - show_skipped_unfolded(lines) - - REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { + REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { "x": show_xfailed, "X": show_xpassed, "f": partial(show_simple, stat="failed"), @@ -1367,7 +1200,7 @@ class TerminalReporter: "E": partial(show_simple, stat="error"), } - lines: list[str] = [] + lines: List[str] = [] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -1378,7 +1211,7 @@ class TerminalReporter: for line in lines: self.write_line(line) - def _get_main_color(self) -> tuple[str, list[str]]: + def _get_main_color(self) -> Tuple[str, List[str]]: if self._main_color is None or self._known_types is None or self._is_last_item: self._set_main_color() assert self._main_color @@ -1398,22 +1231,22 @@ class TerminalReporter: return main_color def _set_main_color(self) -> None: - unknown_types: list[str] = [] - for found_type in self.stats: + unknown_types: List[str] = [] + for found_type in self.stats.keys(): if found_type: # setup/teardown reports have an empty key, ignore them if found_type not in KNOWN_TYPES and found_type not in unknown_types: unknown_types.append(found_type) self._known_types = list(KNOWN_TYPES) + unknown_types self._main_color = self._determine_main_color(bool(unknown_types)) - def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: + def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: """ Build the parts used in the last summary stats line. The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===". This function builds a list of the "parts" that make up for the text in that line, in - the example above it would be:: + the example above it would be: [ ("12 passed", {"green": True}), @@ -1431,14 +1264,14 @@ class TerminalReporter: else: return self._build_normal_summary_stats_line() - def _get_reports_to_display(self, key: str) -> list[Any]: + def _get_reports_to_display(self, key: str) -> List[Any]: """Get test/collection reports for the given status key, such as `passed` or `error`.""" reports = self.stats.get(key, []) return [x for x in reports if getattr(x, "count_towards_summary", True)] def _build_normal_summary_stats_line( self, - ) -> tuple[list[tuple[str, dict[str, bool]]], str]: + ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: main_color, known_types = self._get_main_color() parts = [] @@ -1448,7 +1281,7 @@ class TerminalReporter: count = len(reports) color = _color_for_type.get(key, _color_for_type_default) markup = {color: True, "bold": color == main_color} - parts.append(("%d %s" % pluralize(count, key), markup)) # noqa: UP031 + parts.append(("%d %s" % pluralize(count, key), markup)) if not parts: parts = [("no tests ran", {_color_for_type_default: True})] @@ -1457,7 +1290,7 @@ class TerminalReporter: def _build_collect_only_summary_stats_line( self, - ) -> tuple[list[tuple[str, dict[str, bool]]], str]: + ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: deselected = len(self._get_reports_to_display("deselected")) errors = len(self._get_reports_to_display("error")) @@ -1467,7 +1300,7 @@ class TerminalReporter: elif deselected == 0: main_color = "green" - collected_output = "%d %s collected" % pluralize(self._numcollected, "test") # noqa: UP031 + collected_output = "%d %s collected" % pluralize(self._numcollected, "test") parts = [(collected_output, {main_color: True})] else: all_tests_were_deselected = self._numcollected == deselected @@ -1483,7 +1316,7 @@ class TerminalReporter: if errors: main_color = _color_for_type["error"] - parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] # noqa: UP031 + parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})] return parts, main_color @@ -1498,7 +1331,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport return path -def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: +def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: """Format msg into format, ellipsizing it if doesn't fit in available_width. Returns None if even the ellipsis can't fit. @@ -1524,35 +1357,27 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" - verbose_word, verbose_markup = rep._get_verbose_word_with_markup( - config, word_markup - ) - word = tw.markup(verbose_word, **verbose_markup) + verbose_word = rep._get_verbose_word(config) + word = tw.markup(verbose_word, **word_markup) node = _get_node_id_with_markup(tw, config, rep) line = f"{word} {node}" line_width = wcswidth(line) - msg: str | None try: - if isinstance(rep.longrepr, str): - msg = rep.longrepr - else: - # Type ignored intentionally -- possible AttributeError expected. - msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] + # Type ignored intentionally -- possible AttributeError expected. + msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] except AttributeError: pass else: - if ( - running_on_ci() or config.option.verbose >= 2 - ) and not config.option.force_short_summary: - msg = f" - {msg}" - else: + if not running_on_ci(): available_width = tw.fullwidth - line_width msg = _format_trimmed(" - {}", msg, available_width) + else: + msg = f" - {msg}" if msg is not None: line += msg @@ -1562,8 +1387,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, skipped: Sequence[CollectReport], -) -> list[tuple[int, str, int | None, str]]: - d: dict[tuple[str, int | None, str], list[CollectReport]] = {} +) -> List[Tuple[int, str, Optional[int], str]]: + d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {} for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) @@ -1580,11 +1405,11 @@ def _folded_skips( and "skip" in keywords and "pytestmark" not in keywords ): - key: tuple[str, int | None, str] = (fspath, None, reason) + key: Tuple[str, Optional[int], str] = (fspath, None, reason) else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: list[tuple[int, str, int | None, str]] = [] + values: List[Tuple[int, str, Optional[int], str]] = [] for key, events in d.items(): values.append((len(events), *key)) return values @@ -1595,13 +1420,11 @@ _color_for_type = { "error": "red", "warnings": "yellow", "passed": "green", - "subtests passed": "green", - "subtests failed": "red", } _color_for_type_default = "yellow" -def pluralize(count: int, noun: str) -> tuple[int, str]: +def pluralize(count: int, noun: str) -> Tuple[int, str]: # No need to pluralize words such as `failed` or `passed`. if noun not in ["error", "warnings", "test"]: return count, noun @@ -1614,11 +1437,11 @@ def pluralize(count: int, noun: str) -> tuple[int, str]: return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo) -> list[str]: - values: list[str] = [] +def _plugin_nameversions(plugininfo) -> List[str]: + values: List[str] = [] for plugin, dist in plugininfo: # Gets us name and version! - name = f"{dist.project_name}-{dist.version}" + name = "{dist.project_name}-{dist.version}".format(dist=dist) # Questionable convenience, but it keeps things short. if name.startswith("pytest-"): name = name[7:] @@ -1637,36 +1460,13 @@ def format_session_duration(seconds: float) -> str: return f"{seconds:.2f}s ({dt})" -def format_node_duration(seconds: float) -> str: - """Format the given seconds in a human readable manner to show in the test progress.""" - # The formatting is designed to be compact and readable, with at most 7 characters - # for durations below 100 hours. - if seconds < 0.00001: - return f" {seconds * 1000000:.3f}us" - if seconds < 0.0001: - return f" {seconds * 1000000:.2f}us" - if seconds < 0.001: - return f" {seconds * 1000000:.1f}us" - if seconds < 0.01: - return f" {seconds * 1000:.3f}ms" - if seconds < 0.1: - return f" {seconds * 1000:.2f}ms" - if seconds < 1: - return f" {seconds * 1000:.1f}ms" - if seconds < 60: - return f" {seconds:.3f}s" - if seconds < 3600: - return f" {seconds // 60:.0f}m {seconds % 60:.0f}s" - return f" {seconds // 3600:.0f}h {(seconds % 3600) // 60:.0f}m" - - def _get_raw_skip_reason(report: TestReport) -> str: """Get the reason string of a skip/xfail/xpass test report. The string is just the part given by the user. """ if hasattr(report, "wasxfail"): - reason = report.wasxfail + reason = cast(str, report.wasxfail) if reason.startswith("reason: "): reason = reason[len("reason: ") :] return reason @@ -1679,92 +1479,3 @@ def _get_raw_skip_reason(report: TestReport) -> str: elif reason == "Skipped": reason = "" return reason - - -class TerminalProgressPlugin: - """Terminal progress reporting plugin using OSC 9;4 ANSI sequences. - - Emits OSC 9;4 sequences to indicate test progress to terminal - tabs/windows/etc. - - Not all terminal emulators support this feature. - - Ref: https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC - """ - - def __init__(self, tr: TerminalReporter) -> None: - self._tr = tr - self._session: Session | None = None - self._has_failures = False - - def _emit_progress( - self, - state: Literal["remove", "normal", "error", "indeterminate", "paused"], - progress: int | None = None, - ) -> None: - """Emit OSC 9;4 sequence for indicating progress to the terminal. - - :param state: - Progress state to set. - :param progress: - Progress value 0-100. Required for "normal", optional for "error" - and "paused", otherwise ignored. - """ - assert progress is None or 0 <= progress <= 100 - - # OSC 9;4 sequence: ESC ] 9 ; 4 ; state ; progress ST - # ST can be ESC \ or BEL. ESC \ seems better supported. - match state: - case "remove": - sequence = "\x1b]9;4;0;\x1b\\" - case "normal": - assert progress is not None - sequence = f"\x1b]9;4;1;{progress}\x1b\\" - case "error": - if progress is not None: - sequence = f"\x1b]9;4;2;{progress}\x1b\\" - else: - sequence = "\x1b]9;4;2;\x1b\\" - case "indeterminate": - sequence = "\x1b]9;4;3;\x1b\\" - case "paused": - if progress is not None: - sequence = f"\x1b]9;4;4;{progress}\x1b\\" - else: - sequence = "\x1b]9;4;4;\x1b\\" - - self._tr.write_raw(sequence, flush=True) - - @hookimpl - def pytest_sessionstart(self, session: Session) -> None: - self._session = session - # Show indeterminate progress during collection. - self._emit_progress("indeterminate") - - @hookimpl - def pytest_collection_finish(self) -> None: - assert self._session is not None - if self._session.testscollected > 0: - # Switch from indeterminate to 0% progress. - self._emit_progress("normal", 0) - - @hookimpl - def pytest_runtest_logreport(self, report: TestReport) -> None: - if report.failed: - self._has_failures = True - - # Let's consider the "call" phase for progress. - if report.when != "call": - return - - # Calculate and emit progress. - assert self._session is not None - collected = self._session.testscollected - if collected > 0: - reported = self._tr.reported_progress - progress = min(reported * 100 // collected, 100) - self._emit_progress("error" if self._has_failures else "normal", progress) - - @hookimpl - def pytest_sessionfinish(self) -> None: - self._emit_progress("remove") diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/threadexception.py b/Backend/venv/lib/python3.12/site-packages/_pytest/threadexception.py index eb57783b..43341e73 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/threadexception.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/threadexception.py @@ -1,152 +1,88 @@ -from __future__ import annotations - -import collections -from collections.abc import Callable -import functools -import sys import threading import traceback -from typing import NamedTuple -from typing import TYPE_CHECKING import warnings +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Generator +from typing import Optional +from typing import Type -from _pytest.config import Config -from _pytest.nodes import Item -from _pytest.stash import StashKey -from _pytest.tracemalloc import tracemalloc_message import pytest -if TYPE_CHECKING: - pass +# Copied from cpython/Lib/test/support/threading_helper.py, with modifications. +class catch_threading_exception: + """Context manager catching threading.Thread exception using + threading.excepthook. -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup + Storing exc_value using a custom hook can create a reference cycle. The + reference cycle is broken explicitly when the context manager exits. + + Storing thread using a custom hook can resurrect it if it is set to an + object which is being finalized. Exiting the context manager clears the + stored object. + + Usage: + with threading_helper.catch_threading_exception() as cm: + # code spawning a thread which raises an exception + ... + # check the thread exception: use cm.args + ... + # cm.args attribute no longer exists at this point + # (to break a reference cycle) + """ + + def __init__(self) -> None: + self.args: Optional["threading.ExceptHookArgs"] = None + self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None + + def _hook(self, args: "threading.ExceptHookArgs") -> None: + self.args = args + + def __enter__(self) -> "catch_threading_exception": + self._old_hook = threading.excepthook + threading.excepthook = self._hook + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + assert self._old_hook is not None + threading.excepthook = self._old_hook + self._old_hook = None + del self.args -class ThreadExceptionMeta(NamedTuple): - msg: str - cause_msg: str - exc_value: BaseException | None - - -thread_exceptions: StashKey[collections.deque[ThreadExceptionMeta | BaseException]] = ( - StashKey() -) - - -def collect_thread_exception(config: Config) -> None: - pop_thread_exception = config.stash[thread_exceptions].pop - errors: list[pytest.PytestUnhandledThreadExceptionWarning | RuntimeError] = [] - meta = None - hook_error = None - try: - while True: - try: - meta = pop_thread_exception() - except IndexError: - break - - if isinstance(meta, BaseException): - hook_error = RuntimeError("Failed to process thread exception") - hook_error.__cause__ = meta - errors.append(hook_error) - continue - - msg = meta.msg - try: - warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) - except pytest.PytestUnhandledThreadExceptionWarning as e: - # This except happens when the warning is treated as an error (e.g. `-Werror`). - if meta.exc_value is not None: - # Exceptions have a better way to show the traceback, but - # warnings do not, so hide the traceback from the msg and - # set the cause so the traceback shows up in the right place. - e.args = (meta.cause_msg,) - e.__cause__ = meta.exc_value - errors.append(e) - - if len(errors) == 1: - raise errors[0] - if errors: - raise ExceptionGroup("multiple thread exception warnings", errors) - finally: - del errors, meta, hook_error - - -def cleanup( - *, config: Config, prev_hook: Callable[[threading.ExceptHookArgs], object] -) -> None: - try: - try: - # We don't join threads here, so exceptions raised from any - # threads still running by the time _threading_atexits joins them - # do not get captured (see #13027). - collect_thread_exception(config) - finally: - threading.excepthook = prev_hook - finally: - del config.stash[thread_exceptions] - - -def thread_exception_hook( - args: threading.ExceptHookArgs, - /, - *, - append: Callable[[ThreadExceptionMeta | BaseException], object], -) -> None: - try: - # we need to compute these strings here as they might change after - # the excepthook finishes and before the metadata object is - # collected by a pytest hook - thread_name = "" if args.thread is None else args.thread.name - summary = f"Exception in thread {thread_name}" - traceback_message = "\n\n" + "".join( - traceback.format_exception( - args.exc_type, - args.exc_value, - args.exc_traceback, +def thread_exception_runtest_hook() -> Generator[None, None, None]: + with catch_threading_exception() as cm: + yield + if cm.args: + thread_name = "" if cm.args.thread is None else cm.args.thread.name + msg = f"Exception in thread {thread_name}\n\n" + msg += "".join( + traceback.format_exception( + cm.args.exc_type, + cm.args.exc_value, + cm.args.exc_traceback, + ) ) - ) - tracemalloc_tb = "\n" + tracemalloc_message(args.thread) - msg = summary + traceback_message + tracemalloc_tb - cause_msg = summary + tracemalloc_tb - - append( - ThreadExceptionMeta( - # Compute these strings here as they might change later - msg=msg, - cause_msg=cause_msg, - exc_value=args.exc_value, - ) - ) - except BaseException as e: - append(e) - # Raising this will cause the exception to be logged twice, once in our - # collect_thread_exception and once by sys.excepthook - # which is fine - this should never happen anyway and if it does - # it should probably be reported as a pytest bug. - raise + warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) -def pytest_configure(config: Config) -> None: - prev_hook = threading.excepthook - deque: collections.deque[ThreadExceptionMeta | BaseException] = collections.deque() - config.stash[thread_exceptions] = deque - config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) - threading.excepthook = functools.partial(thread_exception_hook, append=deque.append) +@pytest.hookimpl(hookwrapper=True, trylast=True) +def pytest_runtest_setup() -> Generator[None, None, None]: + yield from thread_exception_runtest_hook() -@pytest.hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - collect_thread_exception(item.config) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_call() -> Generator[None, None, None]: + yield from thread_exception_runtest_hook() -@pytest.hookimpl(trylast=True) -def pytest_runtest_call(item: Item) -> None: - collect_thread_exception(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_teardown(item: Item) -> None: - collect_thread_exception(item.config) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_teardown() -> Generator[None, None, None]: + yield from thread_exception_runtest_hook() diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/timing.py b/Backend/venv/lib/python3.12/site-packages/_pytest/timing.py index 51c3db23..925163a5 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/timing.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/timing.py @@ -5,91 +5,8 @@ pytest runtime information (issue #185). Fixture "mock_timing" also interacts with this module for pytest's own tests. """ - -from __future__ import annotations - -import dataclasses -from datetime import datetime -from datetime import timezone from time import perf_counter from time import sleep from time import time -from typing import TYPE_CHECKING - - -if TYPE_CHECKING: - from pytest import MonkeyPatch - - -@dataclasses.dataclass(frozen=True) -class Instant: - """ - Represents an instant in time, used to both get the timestamp value and to measure - the duration of a time span. - - Inspired by Rust's `std::time::Instant`. - """ - - # Creation time of this instant, using time.time(), to measure actual time. - # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. - time: float = dataclasses.field(default_factory=lambda: time(), init=False) - - # Performance counter tick of the instant, used to measure precise elapsed time. - # Note: using a `lambda` to correctly get the mocked time via `MockTiming`. - perf_count: float = dataclasses.field( - default_factory=lambda: perf_counter(), init=False - ) - - def elapsed(self) -> Duration: - """Measure the duration since `Instant` was created.""" - return Duration(start=self, stop=Instant()) - - def as_utc(self) -> datetime: - """Instant as UTC datetime.""" - return datetime.fromtimestamp(self.time, timezone.utc) - - -@dataclasses.dataclass(frozen=True) -class Duration: - """A span of time as measured by `Instant.elapsed()`.""" - - start: Instant - stop: Instant - - @property - def seconds(self) -> float: - """Elapsed time of the duration in seconds, measured using a performance counter for precise timing.""" - return self.stop.perf_count - self.start.perf_count - - -@dataclasses.dataclass -class MockTiming: - """Mocks _pytest.timing with a known object that can be used to control timing in tests - deterministically. - - pytest itself should always use functions from `_pytest.timing` instead of `time` directly. - - This then allows us more control over time during testing, if testing code also - uses `_pytest.timing` functions. - - Time is static, and only advances through `sleep` calls, thus tests might sleep over large - numbers and obtain accurate time() calls at the end, making tests reliable and instant.""" - - _current_time: float = datetime(2020, 5, 22, 14, 20, 50).timestamp() - - def sleep(self, seconds: float) -> None: - self._current_time += seconds - - def time(self) -> float: - return self._current_time - - def patch(self, monkeypatch: MonkeyPatch) -> None: - # pylint: disable-next=import-self - from _pytest import timing # noqa: PLW0406 - - monkeypatch.setattr(timing, "sleep", self.sleep) - monkeypatch.setattr(timing, "time", self.time) - monkeypatch.setattr(timing, "perf_counter", self.time) - __all__ = ["perf_counter", "sleep", "time"] diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/tmpdir.py b/Backend/venv/lib/python3.12/site-packages/_pytest/tmpdir.py index dcd5784f..3cc2bace 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/tmpdir.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/tmpdir.py @@ -1,63 +1,68 @@ -# mypy: allow-untyped-defs """Support for providing temporary directories to test functions.""" - -from __future__ import annotations - -from collections.abc import Generator import dataclasses import os -from pathlib import Path import re -from shutil import rmtree import tempfile +from pathlib import Path +from shutil import rmtree from typing import Any -from typing import final -from typing import Literal +from typing import Dict +from typing import Generator +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from _pytest.nodes import Item +from _pytest.reports import CollectReport +from _pytest.stash import StashKey + +if TYPE_CHECKING: + from typing_extensions import Literal + + RetentionType = Literal["all", "failed", "none"] + + +from _pytest.config.argparsing import Parser -from .pathlib import cleanup_dead_symlinks from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from .pathlib import rm_rf -from _pytest.compat import get_user_id +from .pathlib import cleanup_dead_symlinks +from _pytest.compat import final, get_user_id from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl -from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch -from _pytest.nodes import Item -from _pytest.reports import TestReport -from _pytest.stash import StashKey - -tmppath_result_key = StashKey[dict[str, bool]]() -RetentionType = Literal["all", "failed", "none"] +tmppath_result_key = StashKey[Dict[str, bool]]() @final @dataclasses.dataclass class TempPathFactory: - """Factory for temporary directories under the common base temp directory, - as discussed at :ref:`temporary directory location and retention`. + """Factory for temporary directories under the common base temp directory. + + The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp: Path | None + _given_basetemp: Optional[Path] # pluggy TagTracerSub, not currently exposed, so Any. _trace: Any - _basetemp: Path | None + _basetemp: Optional[Path] _retention_count: int - _retention_policy: RetentionType + _retention_policy: "RetentionType" def __init__( self, - given_basetemp: Path | None, + given_basetemp: Optional[Path], retention_count: int, - retention_policy: RetentionType, + retention_policy: "RetentionType", trace, - basetemp: Path | None = None, + basetemp: Optional[Path] = None, *, _ispytest: bool = False, ) -> None: @@ -80,7 +85,7 @@ class TempPathFactory: config: Config, *, _ispytest: bool = False, - ) -> TempPathFactory: + ) -> "TempPathFactory": """Create a factory according to pytest configuration. :meta private: @@ -196,7 +201,7 @@ class TempPathFactory: return basetemp -def get_user() -> str | None: +def get_user() -> Optional[str]: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" try: @@ -204,7 +209,7 @@ def get_user() -> str | None: import getpass return getpass.getuser() - except (ImportError, OSError, KeyError): + except (ImportError, KeyError): return None @@ -254,17 +259,26 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: @fixture def tmp_path( request: FixtureRequest, tmp_path_factory: TempPathFactory -) -> Generator[Path]: - """Return a temporary directory (as :class:`pathlib.Path` object) - which is unique to each test function invocation. - The temporary directory is created as a subdirectory - of the base temporary directory, with configurable retention, - as discussed in :ref:`temporary directory location and retention`. +) -> Generator[Path, None, None]: + """Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + By default, a new base temporary directory is created each test session, + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See :ref:`base + temporary directory`. + + The returned object is a :class:`pathlib.Path` object. """ + path = _mk_tmp(request, tmp_path_factory) yield path # Remove the tmpdir if the policy is "failed" and the test passed. + tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore policy = tmp_path_factory._retention_policy result_dict = request.node.stash[tmppath_result_key] @@ -276,7 +290,7 @@ def tmp_path( del request.node.stash[tmppath_result_key] -def pytest_sessionfinish(session, exitstatus: int | ExitCode): +def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): """After each session, remove base directory if all the tests passed, the policy is "failed", and the basetemp is not specified by a user. """ @@ -301,12 +315,10 @@ def pytest_sessionfinish(session, exitstatus: int | ExitCode): cleanup_dead_symlinks(basetemp) -@hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_makereport( - item: Item, call -) -> Generator[None, TestReport, TestReport]: - rep = yield - assert rep.when is not None - empty: dict[str, bool] = {} - item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed - return rep +@hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item: Item, call): + outcome = yield + result: CollectReport = outcome.get_result() + + empty: Dict[str, bool] = {} + item.stash.setdefault(tmppath_result_key, empty)[result.when] = result.passed diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/tracemalloc.py b/Backend/venv/lib/python3.12/site-packages/_pytest/tracemalloc.py deleted file mode 100644 index 5d0b1985..00000000 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/tracemalloc.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - - -def tracemalloc_message(source: object) -> str: - if source is None: - return "" - - try: - import tracemalloc - except ImportError: - return "" - - tb = tracemalloc.get_object_traceback(source) - if tb is not None: - formatted_tb = "\n".join(tb.format()) - # Use a leading new line to better separate the (large) output - # from the traceback to the previous warning text. - return f"\nObject allocated at:\n{formatted_tb}" - # No need for a leading new line. - url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" - return ( - "Enable tracemalloc to get traceback where the object was allocated.\n" - f"See {url} for more info." - ) diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/unittest.py b/Backend/venv/lib/python3.12/site-packages/_pytest/unittest.py index 7498f1b0..d42a12a3 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/unittest.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/unittest.py @@ -1,29 +1,24 @@ -# mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" - -from __future__ import annotations - -from collections.abc import Callable -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from enum import auto -from enum import Enum -import inspect import sys import traceback import types from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type from typing import TYPE_CHECKING -from unittest import TestCase +from typing import Union import _pytest._code -from _pytest._code import ExceptionInfo -from _pytest.compat import assert_never +import pytest +from _pytest.compat import getimfunc from _pytest.compat import is_async_function from _pytest.config import hookimpl from _pytest.fixtures import FixtureRequest -from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector from _pytest.nodes import Item from _pytest.outcomes import exit @@ -34,45 +29,32 @@ from _pytest.python import Class from _pytest.python import Function from _pytest.python import Module from _pytest.runner import CallInfo -from _pytest.runner import check_interactive_exception -from _pytest.subtests import SubtestContext -from _pytest.subtests import SubtestReport - - -if sys.version_info[:2] < (3, 11): - from exceptiongroup import ExceptionGroup +from _pytest.scope import Scope if TYPE_CHECKING: - from types import TracebackType import unittest - import twisted.trial.unittest - -_SysExcInfoType = ( - tuple[type[BaseException], BaseException, types.TracebackType] - | tuple[None, None, None] -) + _SysExcInfoType = Union[ + Tuple[Type[BaseException], BaseException, types.TracebackType], + Tuple[None, None, None], + ] def pytest_pycollect_makeitem( - collector: Module | Class, name: str, obj: object -) -> UnitTestCase | None: + collector: Union[Module, Class], name: str, obj: object +) -> Optional["UnitTestCase"]: + # Has unittest been imported and is obj a subclass of its TestCase? try: - # Has unittest been imported? ut = sys.modules["unittest"] - # Is obj a subclass of unittest.TestCase? # Type ignored because `ut` is an opaque module. if not issubclass(obj, ut.TestCase): # type: ignore return None except Exception: return None - # Is obj a concrete class? - # Abstract classes can't be instantiated so no point collecting them. - if inspect.isabstract(obj): - return None # Yes, so let's collect it. - return UnitTestCase.from_parent(collector, name=name, obj=obj) + item: UnitTestCase = UnitTestCase.from_parent(collector, name=name, obj=obj) + return item class UnitTestCase(Class): @@ -80,15 +62,7 @@ class UnitTestCase(Class): # to declare that our children do not support funcargs. nofuncargs = True - def newinstance(self): - # TestCase __init__ takes the method (test) name. The TestCase - # constructor treats the name "runTest" as a special no-op, so it can be - # used when a dummy instance is needed. While unittest.TestCase has a - # default, some subclasses omit the default (#9610), so always supply - # it. - return self.obj("runTest") - - def collect(self) -> Iterable[Item | Collector]: + def collect(self) -> Iterable[Union[Item, Collector]]: from unittest import TestLoader cls = self.obj @@ -97,156 +71,157 @@ class UnitTestCase(Class): skipped = _is_skipped(cls) if not skipped: - self._register_unittest_setup_method_fixture(cls) - self._register_unittest_setup_class_fixture(cls) - self._register_setup_class_fixture() - - self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) + self._inject_setup_teardown_fixtures(cls) + self._inject_setup_class_fixture() + self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) if not getattr(x, "__test__", True): continue - yield TestCaseFunction.from_parent(self, name=name) + funcobj = getimfunc(x) + yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj) foundsomething = True if not foundsomething: runtest = getattr(self.obj, "runTest", None) if runtest is not None: ut = sys.modules.get("twisted.trial.unittest", None) - if ut is None or runtest != ut.TestCase.runTest: + # Type ignored because `ut` is an opaque module. + if ut is None or runtest != ut.TestCase.runTest: # type: ignore yield TestCaseFunction.from_parent(self, name="runTest") - def _register_unittest_setup_class_fixture(self, cls: type) -> None: - """Register an auto-use fixture to invoke setUpClass and - tearDownClass (#517).""" - setup = getattr(cls, "setUpClass", None) - teardown = getattr(cls, "tearDownClass", None) - if setup is None and teardown is None: - return None - cleanup = getattr(cls, "doClassCleanups", lambda: None) + def _inject_setup_teardown_fixtures(self, cls: type) -> None: + """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding + teardown functions (#517).""" + class_fixture = _make_xunit_fixture( + cls, + "setUpClass", + "tearDownClass", + "doClassCleanups", + scope=Scope.Class, + pass_self=False, + ) + if class_fixture: + cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] - def process_teardown_exceptions() -> None: - # tearDown_exceptions is a list set in the class containing exc_infos for errors during - # teardown for the class. - exc_infos = getattr(cls, "tearDown_exceptions", None) - if not exc_infos: - return - exceptions = [exc for (_, exc, _) in exc_infos] - # If a single exception, raise it directly as this provides a more readable - # error (hopefully this will improve in #12255). - if len(exceptions) == 1: - raise exceptions[0] - else: - raise ExceptionGroup("Unittest class cleanup errors", exceptions) + method_fixture = _make_xunit_fixture( + cls, + "setup_method", + "teardown_method", + None, + scope=Scope.Function, + pass_self=True, + ) + if method_fixture: + cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] - def unittest_setup_class_fixture( - request: FixtureRequest, - ) -> Generator[None]: - cls = request.cls - if _is_skipped(cls): - reason = cls.__unittest_skip_why__ - raise skip.Exception(reason, _use_item_location=True) - if setup is not None: - try: - setup() - # unittest does not call the cleanup function for every BaseException, so we - # follow this here. - except Exception: - cleanup() - process_teardown_exceptions() - raise - yield + +def _make_xunit_fixture( + obj: type, + setup_name: str, + teardown_name: str, + cleanup_name: Optional[str], + scope: Scope, + pass_self: bool, +): + setup = getattr(obj, setup_name, None) + teardown = getattr(obj, teardown_name, None) + if setup is None and teardown is None: + return None + + if cleanup_name: + cleanup = getattr(obj, cleanup_name, lambda *args: None) + else: + + def cleanup(*args): + pass + + @pytest.fixture( + scope=scope.value, + autouse=True, + # Use a unique name to speed up lookup. + name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}", + ) + def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: + if _is_skipped(self): + reason = self.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: try: - if teardown is not None: - teardown() - finally: - cleanup() - process_teardown_exceptions() + if pass_self: + setup(self, request.function) + else: + setup() + # unittest does not call the cleanup function for every BaseException, so we + # follow this here. + except Exception: + if pass_self: + cleanup(self) + else: + cleanup() - self.session._fixturemanager._register_fixture( - # Use a unique name to speed up lookup. - name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", - func=unittest_setup_class_fixture, - nodeid=self.nodeid, - scope="class", - autouse=True, - ) - - def _register_unittest_setup_method_fixture(self, cls: type) -> None: - """Register an auto-use fixture to invoke setup_method and - teardown_method (#517).""" - setup = getattr(cls, "setup_method", None) - teardown = getattr(cls, "teardown_method", None) - if setup is None and teardown is None: - return None - - def unittest_setup_method_fixture( - request: FixtureRequest, - ) -> Generator[None]: - self = request.instance - if _is_skipped(self): - reason = self.__unittest_skip_why__ - raise skip.Exception(reason, _use_item_location=True) - if setup is not None: - setup(self, request.function) - yield + raise + yield + try: if teardown is not None: - teardown(self, request.function) + if pass_self: + teardown(self, request.function) + else: + teardown() + finally: + if pass_self: + cleanup(self) + else: + cleanup() - self.session._fixturemanager._register_fixture( - # Use a unique name to speed up lookup. - name=f"_unittest_setup_method_fixture_{cls.__qualname__}", - func=unittest_setup_method_fixture, - nodeid=self.nodeid, - scope="function", - autouse=True, - ) + return fixture class TestCaseFunction(Function): nofuncargs = True - failfast = False - _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None + _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None + _testcase: Optional["unittest.TestCase"] = None - def _getinstance(self): - assert isinstance(self.parent, UnitTestCase) - return self.parent.obj(self.name) - - # Backward compat for pytest-django; can be removed after pytest-django - # updates + some slack. - @property - def _testcase(self): - return self.instance + def _getobj(self): + assert self.parent is not None + # Unlike a regular Function in a Class, where `item.obj` returns + # a *bound* method (attached to an instance), TestCaseFunction's + # `obj` returns an *unbound* method (not attached to an instance). + # This inconsistency is probably not desirable, but needs some + # consideration before changing. + return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). - self._explicit_tearDown: Callable[[], None] | None = None - super().setup() + self._explicit_tearDown: Optional[Callable[[], None]] = None + assert self.parent is not None + self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] + self._obj = getattr(self._testcase, self.name) + if hasattr(self, "_request"): + self._request._fillfixtures() def teardown(self) -> None: if self._explicit_tearDown is not None: self._explicit_tearDown() self._explicit_tearDown = None + self._testcase = None self._obj = None - del self._instance - super().teardown() - def startTest(self, testcase: unittest.TestCase) -> None: + def startTest(self, testcase: "unittest.TestCase") -> None: pass - def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: - rawexcinfo = _handle_twisted_exc_info(rawexcinfo) + def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: + # Unwrap potential exception info (see twisted trial support below). + rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: - excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info( - rawexcinfo # type: ignore[arg-type] - ) + excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info(rawexcinfo) # type: ignore[arg-type] # Invoke the attributes to trigger storing the traceback # trial causes some issue there. - _ = excinfo.value - _ = excinfo.traceback + excinfo.value + excinfo.traceback except TypeError: try: try: @@ -262,7 +237,7 @@ class TestCaseFunction(Function): except BaseException: fail( "ERROR: Unknown Incompatible Exception " - f"representation:\n{rawexcinfo!r}", + "representation:\n%r" % (rawexcinfo,), pytrace=False, ) except KeyboardInterrupt: @@ -272,7 +247,7 @@ class TestCaseFunction(Function): self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType + self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -282,51 +257,20 @@ class TestCaseFunction(Function): self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType + self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" ) -> None: self._addexcinfo(rawexcinfo) - def addSkip( - self, testcase: unittest.TestCase, reason: str, *, handle_subtests: bool = True - ) -> None: - from unittest.case import _SubTest # type: ignore[attr-defined] - - def add_skip() -> None: - try: - raise skip.Exception(reason, _use_item_location=True) - except skip.Exception: - self._addexcinfo(sys.exc_info()) - - if not handle_subtests: - add_skip() - return - - if isinstance(testcase, _SubTest): - add_skip() - if self._excinfo is not None: - exc_info = self._excinfo[-1] - self.addSubTest(testcase.test_case, testcase, exc_info) - else: - # For python < 3.11: the non-subtest skips have to be added by `add_skip` only after all subtest - # failures are processed by `_addSubTest`: `self.instance._outcome` has no attribute - # `skipped/errors` anymore. - # We also need to check if `self.instance._outcome` is `None` (this happens if the test - # class/method is decorated with `unittest.skip`, see pytest-dev/pytest-subtests#173). - if sys.version_info < (3, 11) and self.instance._outcome is not None: - subtest_errors = [ - x - for x, y in self.instance._outcome.errors - if isinstance(x, _SubTest) and y is not None - ] - if len(subtest_errors) == 0: - add_skip() - else: - add_skip() + def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: + try: + raise pytest.skip.Exception(reason, _use_item_location=True) + except skip.Exception: + self._addexcinfo(sys.exc_info()) def addExpectedFailure( self, - testcase: unittest.TestCase, - rawexcinfo: _SysExcInfoType, + testcase: "unittest.TestCase", + rawexcinfo: "_SysExcInfoType", reason: str = "", ) -> None: try: @@ -336,8 +280,8 @@ class TestCaseFunction(Function): def addUnexpectedSuccess( self, - testcase: unittest.TestCase, - reason: twisted.trial.unittest.Todo | None = None, + testcase: "unittest.TestCase", + reason: Optional["twisted.trial.unittest.Todo"] = None, ) -> None: msg = "Unexpected success" if reason: @@ -348,26 +292,26 @@ class TestCaseFunction(Function): except fail.Exception: self._addexcinfo(sys.exc_info()) - def addSuccess(self, testcase: unittest.TestCase) -> None: + def addSuccess(self, testcase: "unittest.TestCase") -> None: pass - def stopTest(self, testcase: unittest.TestCase) -> None: + def stopTest(self, testcase: "unittest.TestCase") -> None: pass - def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: + def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: pass def runtest(self) -> None: from _pytest.debugging import maybe_wrap_pytest_function_for_tracing - testcase = self.instance - assert testcase is not None + assert self._testcase is not None maybe_wrap_pytest_function_for_tracing(self) # Let the unittest framework handle async functions. if is_async_function(self.obj): - testcase(result=self) + # Type ignored because self acts as the TestResult, but is not actually one. + self._testcase(result=self) # type: ignore[arg-type] else: # When --pdb is given, we want to postpone calling tearDown() otherwise # when entering the pdb prompt, tearDown() would have probably cleaned up @@ -379,16 +323,16 @@ class TestCaseFunction(Function): assert isinstance(self.parent, UnitTestCase) skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj) if self.config.getoption("usepdb") and not skipped: - self._explicit_tearDown = testcase.tearDown - setattr(testcase, "tearDown", lambda *args: None) + self._explicit_tearDown = self._testcase.tearDown + setattr(self._testcase, "tearDown", lambda *args: None) # We need to update the actual bound method with self.obj, because # wrap_pytest_function_for_tracing replaces self.obj by a wrapper. - setattr(testcase, self.name, self.obj) + setattr(self._testcase, self.name, self.obj) try: - testcase(result=self) + self._testcase(result=self) # type: ignore[arg-type] finally: - delattr(testcase, self.name) + delattr(self._testcase, self.name) def _traceback_filter( self, excinfo: _pytest._code.ExceptionInfo[BaseException] @@ -401,70 +345,6 @@ class TestCaseFunction(Function): ntraceback = traceback return ntraceback - def addSubTest( - self, - test_case: Any, - test: TestCase, - exc_info: ExceptionInfo[BaseException] - | tuple[type[BaseException], BaseException, TracebackType] - | None, - ) -> None: - exception_info: ExceptionInfo[BaseException] | None - match exc_info: - case tuple(): - exception_info = ExceptionInfo(exc_info, _ispytest=True) - case ExceptionInfo() | None: - exception_info = exc_info - case unreachable: - assert_never(unreachable) - - call_info = CallInfo[None]( - None, - exception_info, - start=0, - stop=0, - duration=0, - when="call", - _ispytest=True, - ) - msg = test._message if isinstance(test._message, str) else None # type: ignore[attr-defined] - report = self.ihook.pytest_runtest_makereport(item=self, call=call_info) - sub_report = SubtestReport._new( - report, - SubtestContext(msg=msg, kwargs=dict(test.params)), # type: ignore[attr-defined] - captured_output=None, - captured_logs=None, - ) - self.ihook.pytest_runtest_logreport(report=sub_report) - if check_interactive_exception(call_info, sub_report): - self.ihook.pytest_exception_interact( - node=self, call=call_info, report=sub_report - ) - - # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`. - if sys.version_info < (3, 11): - from unittest.case import _SubTest # type: ignore[attr-defined] - - non_subtest_skip = [ - (x, y) - for x, y in self.instance._outcome.skipped - if not isinstance(x, _SubTest) - ] - subtest_errors = [ - (x, y) - for x, y in self.instance._outcome.errors - if isinstance(x, _SubTest) and y is not None - ] - # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in - # `_addSubTest` and have to be added using `add_skip` after all subtest failures are processed. - if len(non_subtest_skip) > 0 and len(subtest_errors) > 0: - # Make sure we have processed the last subtest failure - last_subset_error = subtest_errors[-1] - if exc_info is last_subset_error[-1]: - # Add non-subtest skips (as they could not be treated in `_addSkip`) - for testcase, reason in non_subtest_skip: - self.addSkip(testcase, reason, handle_subtests=False) - @hookimpl(tryfirst=True) def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: @@ -477,138 +357,65 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: pass # Convert unittest.SkipTest to pytest.skip. - # This covers explicit `raise unittest.SkipTest`. + # This is actually only needed for nose, which reuses unittest.SkipTest for + # its own nose.SkipTest. For unittest TestCases, SkipTest is already + # handled internally, and doesn't reach here. unittest = sys.modules.get("unittest") - if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest): + if ( + unittest + and call.excinfo + and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] + ): excinfo = call.excinfo - call2 = CallInfo[None].from_call(lambda: skip(str(excinfo.value)), call.when) + call2 = CallInfo[None].from_call( + lambda: pytest.skip(str(excinfo.value)), call.when + ) call.excinfo = call2.excinfo +# Twisted trial support. + + +@hookimpl(hookwrapper=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: + if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: + ut: Any = sys.modules["twisted.python.failure"] + Failure__init__ = ut.Failure.__init__ + check_testcase_implements_trial_reporter() + + def excstore( + self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None + ): + if exc_value is None: + self._rawexcinfo = sys.exc_info() + else: + if exc_type is None: + exc_type = type(exc_value) + self._rawexcinfo = (exc_type, exc_value, exc_tb) + try: + Failure__init__( + self, exc_value, exc_type, exc_tb, captureVars=captureVars + ) + except TypeError: + Failure__init__(self, exc_value, exc_type, exc_tb) + + ut.Failure.__init__ = excstore + yield + ut.Failure.__init__ = Failure__init__ + else: + yield + + +def check_testcase_implements_trial_reporter(done: List[int] = []) -> None: + if done: + return + from zope.interface import classImplements + from twisted.trial.itrial import IReporter + + classImplements(TestCaseFunction, IReporter) + done.append(1) + + def _is_skipped(obj) -> bool: """Return True if the given object has been marked with @unittest.skip.""" return bool(getattr(obj, "__unittest_skip__", False)) - - -def pytest_configure() -> None: - """Register the TestCaseFunction class as an IReporter if twisted.trial is available.""" - if _get_twisted_version() is not TwistedVersion.NotInstalled: - from twisted.trial.itrial import IReporter - from zope.interface import classImplements - - classImplements(TestCaseFunction, IReporter) - - -class TwistedVersion(Enum): - """ - The Twisted version installed in the environment. - - We have different workarounds in place for different versions of Twisted. - """ - - # Twisted version 24 or prior. - Version24 = auto() - # Twisted version 25 or later. - Version25 = auto() - # Twisted version is not available. - NotInstalled = auto() - - -def _get_twisted_version() -> TwistedVersion: - # We need to check if "twisted.trial.unittest" is specifically present in sys.modules. - # This is because we intend to integrate with Trial only when it's actively running - # the test suite, but not needed when only other Twisted components are in use. - if "twisted.trial.unittest" not in sys.modules: - return TwistedVersion.NotInstalled - - import importlib.metadata - - import packaging.version - - version_str = importlib.metadata.version("twisted") - version = packaging.version.parse(version_str) - if version.major <= 24: - return TwistedVersion.Version24 - else: - return TwistedVersion.Version25 - - -# Name of the attribute in `twisted.python.Failure` instances that stores -# the `sys.exc_info()` tuple. -# See twisted.trial support in `pytest_runtest_protocol`. -TWISTED_RAW_EXCINFO_ATTR = "_twisted_raw_excinfo" - - -@hookimpl(wrapper=True) -def pytest_runtest_protocol(item: Item) -> Iterator[None]: - if _get_twisted_version() is TwistedVersion.Version24: - import twisted.python.failure as ut - - # Monkeypatch `Failure.__init__` to store the raw exception info. - original__init__ = ut.Failure.__init__ - - def store_raw_exception_info( - self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None - ): # pragma: no cover - if exc_value is None: - raw_exc_info = sys.exc_info() - else: - if exc_type is None: - exc_type = type(exc_value) - if exc_tb is None: - exc_tb = sys.exc_info()[2] - raw_exc_info = (exc_type, exc_value, exc_tb) - setattr(self, TWISTED_RAW_EXCINFO_ATTR, tuple(raw_exc_info)) - try: - original__init__( - self, exc_value, exc_type, exc_tb, captureVars=captureVars - ) - except TypeError: # pragma: no cover - original__init__(self, exc_value, exc_type, exc_tb) - - with MonkeyPatch.context() as patcher: - patcher.setattr(ut.Failure, "__init__", store_raw_exception_info) - return (yield) - else: - return (yield) - - -def _handle_twisted_exc_info( - rawexcinfo: _SysExcInfoType | BaseException, -) -> _SysExcInfoType: - """ - Twisted passes a custom Failure instance to `addError()` instead of using `sys.exc_info()`. - Therefore, if `rawexcinfo` is a `Failure` instance, convert it into the equivalent `sys.exc_info()` tuple - as expected by pytest. - """ - twisted_version = _get_twisted_version() - if twisted_version is TwistedVersion.NotInstalled: - # Unfortunately, because we cannot import `twisted.python.failure` at the top of the file - # and use it in the signature, we need to use `type:ignore` here because we cannot narrow - # the type properly in the `if` statement above. - return rawexcinfo # type:ignore[return-value] - elif twisted_version is TwistedVersion.Version24: - # Twisted calls addError() passing its own classes (like `twisted.python.Failure`), which violates - # the `addError()` signature, so we extract the original `sys.exc_info()` tuple which is stored - # in the object. - if hasattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR): - saved_exc_info = getattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) - # Delete the attribute from the original object to avoid leaks. - delattr(rawexcinfo, TWISTED_RAW_EXCINFO_ATTR) - return saved_exc_info # type:ignore[no-any-return] - return rawexcinfo # type:ignore[return-value] - elif twisted_version is TwistedVersion.Version25: - if isinstance(rawexcinfo, BaseException): - import twisted.python.failure - - if isinstance(rawexcinfo, twisted.python.failure.Failure): - tb = rawexcinfo.__traceback__ - if tb is None: - tb = sys.exc_info()[2] - return type(rawexcinfo.value), rawexcinfo.value, tb - - return rawexcinfo # type:ignore[return-value] - else: - # Ideally we would use assert_never() here, but it is not available in all Python versions - # we support, plus we do not require `type_extensions` currently. - assert False, f"Unexpected Twisted version: {twisted_version}" diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py b/Backend/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py index 0faca36a..fcb5d823 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/unraisableexception.py @@ -1,163 +1,93 @@ -from __future__ import annotations - -import collections -from collections.abc import Callable -import functools -import gc import sys import traceback -from typing import NamedTuple -from typing import TYPE_CHECKING import warnings +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Generator +from typing import Optional +from typing import Type -from _pytest.config import Config -from _pytest.nodes import Item -from _pytest.stash import StashKey -from _pytest.tracemalloc import tracemalloc_message import pytest -if TYPE_CHECKING: - pass +# Copied from cpython/Lib/test/support/__init__.py, with modifications. +class catch_unraisable_exception: + """Context manager catching unraisable exception using sys.unraisablehook. -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup + Storing the exception value (cm.unraisable.exc_value) creates a reference + cycle. The reference cycle is broken explicitly when the context manager + exits. + + Storing the object (cm.unraisable.object) can resurrect it if it is set to + an object which is being finalized. Exiting the context manager clears the + stored object. + + Usage: + with catch_unraisable_exception() as cm: + # code creating an "unraisable exception" + ... + # check the unraisable exception: use cm.unraisable + ... + # cm.unraisable attribute no longer exists at this point + # (to break a reference cycle) + """ + + def __init__(self) -> None: + self.unraisable: Optional["sys.UnraisableHookArgs"] = None + self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None + + def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None: + # Storing unraisable.object can resurrect an object which is being + # finalized. Storing unraisable.exc_value creates a reference cycle. + self.unraisable = unraisable + + def __enter__(self) -> "catch_unraisable_exception": + self._old_hook = sys.unraisablehook + sys.unraisablehook = self._hook + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + assert self._old_hook is not None + sys.unraisablehook = self._old_hook + self._old_hook = None + del self.unraisable -# This is a stash item and not a simple constant to allow pytester to override it. -gc_collect_iterations_key = StashKey[int]() - - -def gc_collect_harder(iterations: int) -> None: - for _ in range(iterations): - gc.collect() - - -class UnraisableMeta(NamedTuple): - msg: str - cause_msg: str - exc_value: BaseException | None - - -unraisable_exceptions: StashKey[collections.deque[UnraisableMeta | BaseException]] = ( - StashKey() -) - - -def collect_unraisable(config: Config) -> None: - pop_unraisable = config.stash[unraisable_exceptions].pop - errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = [] - meta = None - hook_error = None - try: - while True: - try: - meta = pop_unraisable() - except IndexError: - break - - if isinstance(meta, BaseException): - hook_error = RuntimeError("Failed to process unraisable exception") - hook_error.__cause__ = meta - errors.append(hook_error) - continue - - msg = meta.msg - try: - warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) - except pytest.PytestUnraisableExceptionWarning as e: - # This except happens when the warning is treated as an error (e.g. `-Werror`). - if meta.exc_value is not None: - # Exceptions have a better way to show the traceback, but - # warnings do not, so hide the traceback from the msg and - # set the cause so the traceback shows up in the right place. - e.args = (meta.cause_msg,) - e.__cause__ = meta.exc_value - errors.append(e) - - if len(errors) == 1: - raise errors[0] - if errors: - raise ExceptionGroup("multiple unraisable exception warnings", errors) - finally: - del errors, meta, hook_error - - -def cleanup( - *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object] -) -> None: - # A single collection doesn't necessarily collect everything. - # Constant determined experimentally by the Trio project. - gc_collect_iterations = config.stash.get(gc_collect_iterations_key, 5) - try: - try: - gc_collect_harder(gc_collect_iterations) - collect_unraisable(config) - finally: - sys.unraisablehook = prev_hook - finally: - del config.stash[unraisable_exceptions] - - -def unraisable_hook( - unraisable: sys.UnraisableHookArgs, - /, - *, - append: Callable[[UnraisableMeta | BaseException], object], -) -> None: - try: - # we need to compute these strings here as they might change after - # the unraisablehook finishes and before the metadata object is - # collected by a pytest hook - err_msg = ( - "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg - ) - summary = f"{err_msg}: {unraisable.object!r}" - traceback_message = "\n\n" + "".join( - traceback.format_exception( - unraisable.exc_type, - unraisable.exc_value, - unraisable.exc_traceback, +def unraisable_exception_runtest_hook() -> Generator[None, None, None]: + with catch_unraisable_exception() as cm: + yield + if cm.unraisable: + if cm.unraisable.err_msg is not None: + err_msg = cm.unraisable.err_msg + else: + err_msg = "Exception ignored in" + msg = f"{err_msg}: {cm.unraisable.object!r}\n\n" + msg += "".join( + traceback.format_exception( + cm.unraisable.exc_type, + cm.unraisable.exc_value, + cm.unraisable.exc_traceback, + ) ) - ) - tracemalloc_tb = "\n" + tracemalloc_message(unraisable.object) - msg = summary + traceback_message + tracemalloc_tb - cause_msg = summary + tracemalloc_tb - - append( - UnraisableMeta( - msg=msg, - cause_msg=cause_msg, - exc_value=unraisable.exc_value, - ) - ) - except BaseException as e: - append(e) - # Raising this will cause the exception to be logged twice, once in our - # collect_unraisable and once by the unraisablehook calling machinery - # which is fine - this should never happen anyway and if it does - # it should probably be reported as a pytest bug. - raise + warnings.warn(pytest.PytestUnraisableExceptionWarning(msg)) -def pytest_configure(config: Config) -> None: - prev_hook = sys.unraisablehook - deque: collections.deque[UnraisableMeta | BaseException] = collections.deque() - config.stash[unraisable_exceptions] = deque - config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook)) - sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_setup() -> Generator[None, None, None]: + yield from unraisable_exception_runtest_hook() -@pytest.hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - collect_unraisable(item.config) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_call() -> Generator[None, None, None]: + yield from unraisable_exception_runtest_hook() -@pytest.hookimpl(trylast=True) -def pytest_runtest_call(item: Item) -> None: - collect_unraisable(item.config) - - -@pytest.hookimpl(trylast=True) -def pytest_runtest_teardown(item: Item) -> None: - collect_unraisable(item.config) +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_teardown() -> Generator[None, None, None]: + yield from unraisable_exception_runtest_hook() diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/warning_types.py b/Backend/venv/lib/python3.12/site-packages/_pytest/warning_types.py index 93071b4a..bd5f4187 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/warning_types.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/warning_types.py @@ -1,13 +1,13 @@ -from __future__ import annotations - import dataclasses import inspect +import warnings from types import FunctionType from typing import Any -from typing import final from typing import Generic +from typing import Type from typing import TypeVar -import warnings + +from _pytest.compat import final class PytestWarning(UserWarning): @@ -50,14 +50,14 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): __module__ = "pytest" -class PytestRemovedIn9Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 9.""" +class PytestRemovedIn8Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 8.""" __module__ = "pytest" -class PytestRemovedIn10Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 10.""" +class PytestReturnNotNoneWarning(PytestRemovedIn8Warning): + """Warning emitted when a test function is returning value other than None.""" __module__ = "pytest" @@ -73,16 +73,21 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): __module__ = "pytest" @classmethod - def simple(cls, apiname: str) -> PytestExperimentalApiWarning: - return cls(f"{apiname} is an experimental api that may change over time") + def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": + return cls( + "{apiname} is an experimental api that may change over time".format( + apiname=apiname + ) + ) @final -class PytestReturnNotNoneWarning(PytestWarning): - """ - Warning emitted when a test function returns a value other than ``None``. +class PytestUnhandledCoroutineWarning(PytestReturnNotNoneWarning): + """Warning emitted for an unhandled coroutine. - See :ref:`return-not-none` for details. + A coroutine was encountered when collecting test functions, but was not + handled by any async-aware plugin. + Coroutine test functions are not natively supported. """ __module__ = "pytest" @@ -132,7 +137,7 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category: type[_W] + category: Type["_W"] template: str def format(self, **kwargs: Any) -> _W: @@ -140,13 +145,6 @@ class UnformattedWarning(Generic[_W]): return self.category(self.template.format(**kwargs)) -@final -class PytestFDWarning(PytestWarning): - """When the lsof plugin finds leaked fds.""" - - __module__ = "pytest" - - def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: """ Issue the warning :param:`message` for the definition of the given :param:`method` diff --git a/Backend/venv/lib/python3.12/site-packages/_pytest/warnings.py b/Backend/venv/lib/python3.12/site-packages/_pytest/warnings.py index 1dbf0025..4aaa9445 100644 --- a/Backend/venv/lib/python3.12/site-packages/_pytest/warnings.py +++ b/Backend/venv/lib/python3.12/site-packages/_pytest/warnings.py @@ -1,32 +1,37 @@ -# mypy: allow-untyped-defs -from __future__ import annotations - -from collections.abc import Generator -from contextlib import contextmanager -from contextlib import ExitStack import sys -from typing import Literal import warnings +from contextlib import contextmanager +from typing import Generator +from typing import Optional +from typing import TYPE_CHECKING +import pytest from _pytest.config import apply_warning_filters from _pytest.config import Config from _pytest.config import parse_warning_filter from _pytest.main import Session from _pytest.nodes import Item from _pytest.terminal import TerminalReporter -from _pytest.tracemalloc import tracemalloc_message -import pytest + +if TYPE_CHECKING: + from typing_extensions import Literal + + +def pytest_configure(config: Config) -> None: + config.addinivalue_line( + "markers", + "filterwarnings(warning): add a warning filter to the given test. " + "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ", + ) @contextmanager def catch_warnings_for_item( config: Config, ihook, - when: Literal["config", "collect", "runtest"], - item: Item | None, - *, - record: bool = True, -) -> Generator[None]: + when: "Literal['config', 'collect', 'runtest']", + item: Optional[Item], +) -> Generator[None, None, None]: """Context manager that catches warnings generated in the contained execution block. ``item`` can be None if we are not in the context of an item execution. @@ -35,14 +40,15 @@ def catch_warnings_for_item( """ config_filters = config.getini("filterwarnings") cmdline_filters = config.known_args_namespace.pythonwarnings or [] - with warnings.catch_warnings(record=record) as log: + with warnings.catch_warnings(record=True) as log: + # mypy can't infer that record=True means log is not None; help it. + assert log is not None + if not sys.warnoptions: # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908). warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning) - apply_warning_filters(config_filters, cmdline_filters) # apply filters from "filterwarnings" marks @@ -52,100 +58,91 @@ def catch_warnings_for_item( for arg in mark.args: warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) - try: - yield - finally: - if record: - # mypy can't infer that record=True means log is not None; help it. - assert log is not None + yield - for warning_message in log: - ihook.pytest_warning_recorded.call_historic( - kwargs=dict( - warning_message=warning_message, - nodeid=nodeid, - when=when, - location=None, - ) - ) + for warning_message in log: + ihook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=warning_message, + nodeid=nodeid, + when=when, + location=None, + ) + ) def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: """Convert a warnings.WarningMessage to a string.""" - return warnings.formatwarning( - str(warning_message.message), + warn_msg = warning_message.message + msg = warnings.formatwarning( + str(warn_msg), warning_message.category, warning_message.filename, warning_message.lineno, warning_message.line, - ) + tracemalloc_message(warning_message.source) + ) + if warning_message.source is not None: + try: + import tracemalloc + except ImportError: + pass + else: + tb = tracemalloc.get_object_traceback(warning_message.source) + if tb is not None: + formatted_tb = "\n".join(tb.format()) + # Use a leading new line to better separate the (large) output + # from the traceback to the previous warning text. + msg += f"\nObject allocated at:\n{formatted_tb}" + else: + # No need for a leading new line. + url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings" + msg += "Enable tracemalloc to get traceback where the object was allocated.\n" + msg += f"See {url} for more info." + return msg -@pytest.hookimpl(wrapper=True, tryfirst=True) -def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: with catch_warnings_for_item( config=item.config, ihook=item.ihook, when="runtest", item=item ): - return (yield) + yield -@pytest.hookimpl(wrapper=True, tryfirst=True) -def pytest_collection(session: Session) -> Generator[None, object, object]: +@pytest.hookimpl(hookwrapper=True, tryfirst=True) +def pytest_collection(session: Session) -> Generator[None, None, None]: config = session.config with catch_warnings_for_item( config=config, ihook=config.hook, when="collect", item=None ): - return (yield) + yield -@pytest.hookimpl(wrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_terminal_summary( terminalreporter: TerminalReporter, -) -> Generator[None]: +) -> Generator[None, None, None]: config = terminalreporter.config with catch_warnings_for_item( config=config, ihook=config.hook, when="config", item=None ): - return (yield) + yield -@pytest.hookimpl(wrapper=True) -def pytest_sessionfinish(session: Session) -> Generator[None]: +@pytest.hookimpl(hookwrapper=True) +def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: config = session.config with catch_warnings_for_item( config=config, ihook=config.hook, when="config", item=None ): - return (yield) + yield -@pytest.hookimpl(wrapper=True) +@pytest.hookimpl(hookwrapper=True) def pytest_load_initial_conftests( - early_config: Config, -) -> Generator[None]: + early_config: "Config", +) -> Generator[None, None, None]: with catch_warnings_for_item( config=early_config, ihook=early_config.hook, when="config", item=None ): - return (yield) - - -def pytest_configure(config: Config) -> None: - with ExitStack() as stack: - stack.enter_context( - catch_warnings_for_item( - config=config, - ihook=config.hook, - when="config", - item=None, - # this disables recording because the terminalreporter has - # finished by the time it comes to reporting logged warnings - # from the end of config cleanup. So for now, this is only - # useful for setting a warning filter with an 'error' action. - record=False, - ) - ) - config.addinivalue_line( - "markers", - "filterwarnings(warning): add a warning filter to the given test. " - "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ", - ) - config.add_cleanup(stack.pop_all().close) + yield diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/__init__.cpython-312.pyc index 47c3d780..adf6c598 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/base.cpython-312.pyc index 34b358f3..f975bb72 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc index e6741e32..6e88596c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc index 0340305f..c0a0383d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc index c489af88..73c841f8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-312.pyc index 49ac95d0..9d086cef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/binary.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/text.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/text.cpython-312.pyc index 0f3c0974..4acc479d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/text.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/text.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-312.pyc index a071aef0..f5144a8e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiofiles/threadpool/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/__init__.cpython-312.pyc index 731e8e18..2491ad2e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/api.cpython-312.pyc index cc272f60..81b19f92 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/auth.cpython-312.pyc index a5406ee7..5b2e2810 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/email.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/email.cpython-312.pyc index a7bcae01..96b6c945 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/email.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/email.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/errors.cpython-312.pyc index f71076a1..8772c061 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/esmtp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/esmtp.cpython-312.pyc index 571fd853..7ee8fdfc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/esmtp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/esmtp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/protocol.cpython-312.pyc index 1440bfeb..5744aec2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/response.cpython-312.pyc index b91196ca..b047c468 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/smtp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/smtp.cpython-312.pyc index b29df119..104e94fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/smtp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/smtp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/typing.cpython-312.pyc index cd3ea2f4..77dfd99d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/aiosmtplib/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc index 7c40d44c..68a1ff73 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc index e5b0425d..aa542911 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc index e71f2454..531e1a08 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc index 46f99ee1..27c383d2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc index eb388e34..92716538 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc index 4a616c8e..fa94ca25 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc index ac58ecf0..71d865b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/compare.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/compare.cpython-312.pyc index fcc88a52..08881234 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/compare.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/compare.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc index 576ead09..a8dc32bd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc index e7145d33..b25b6b26 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc index d9657436..66550daf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc index 3e997212..e55b00b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc index 238d48e2..507f364f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc index 5acbf8c7..f1255070 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc index f35befea..b13f57e2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc index db7fca23..117313b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc index d58ebc51..bf424766 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc index c3af3bd5..95a9fbb8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc index f71e7488..7d33626a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc index 0063659f..8e6efab1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc index 016821bc..b94fcfcc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc index 29befe7d..3d25a435 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc index b597aedc..ae46c367 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc index 26c16a22..0f2b0376 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc index 4ee5fcc6..e61e3603 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc index 91ca0ba5..993d2258 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc index e4f90bb9..0d262948 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc index 3dad6ce9..f85e575c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc index cf1d4eca..825773ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc index 9267029a..9f1d745f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc index 091d680d..3f5b1426 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc index 3a6cd320..f9562915 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc index be1fa177..968f5ae6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc index d139ac5c..25d10dc5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc index f2bc74e7..9d99b2ff 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc index 08c0c07e..f7873aef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc index a3e5dc9e..bc6a292d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc index 0a6a26a1..956f09ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc index 98ebdc1b..177ce39e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc index 22cd974c..e62d79f9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc index 1d462977..3ca60bf4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc index 34459304..ee8295ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc index eb02f314..2f459bc5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc index 11032773..52bdc564 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc index e85045a7..349d13f4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc index 24dcba9b..b964cd5a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc index 2ed58217..885177c9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc index 4e8f26b3..b46887d7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc index 78b221cf..fa06bdc3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_compat.cpython-312.pyc index 5fc25e08..76361e79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc index ce7bb6b7..ac2b0d67 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc index 40f6fd14..7dc6555f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc index 810a87ff..67614ec5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc index a3e340b4..590f6d40 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc index a6aa1461..2b959fb5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc index 72544100..6f079a18 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc index 63b46bf3..32263cd2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc index 6adfeea3..8888ee94 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc index 05ab1255..4bf33f79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc index 471767bc..7d731976 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc index a31b4541..9019ac6e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc index cb74b210..e1a9c15d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc index 1724b5b4..9aa6d67a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc index d43b3259..9eaeac68 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc index b0756337..7963e0d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc index c6035583..c5d4fec3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc index 0938df15..4fd26431 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc index de853864..79eae19a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc index 7e9f0c8d..457be380 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc index acbc05d7..613630f5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc index c442d585..1455cb9f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc index e34f74a5..45034471 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc index d69a02d9..907ae9e1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc index 795c49b1..3a921dc3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/__init__.cpython-312.pyc index 25b2c08e..9034f0dd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/callbacks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/callbacks.cpython-312.pyc index 89e7a347..1597d68e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/callbacks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/callbacks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/html5lib_shim.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/html5lib_shim.cpython-312.pyc index c8fbf9ac..6968a69f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/html5lib_shim.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/html5lib_shim.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/linkifier.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/linkifier.cpython-312.pyc index 92bb8221..5cab0b8f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/linkifier.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/linkifier.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/parse_shim.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/parse_shim.cpython-312.pyc index 2faa0926..46fcc34d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/parse_shim.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/parse_shim.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/sanitizer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/sanitizer.cpython-312.pyc index b322e54b..96b17ec5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/sanitizer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/__pycache__/sanitizer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/__init__.cpython-312.pyc index 2a651707..5583741e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/parse.cpython-312.pyc index 0fe6248b..2d55291c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/__pycache__/parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/__init__.cpython-312.pyc index c4aefade..da2492e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_inputstream.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_inputstream.cpython-312.pyc index 6623940c..ad3b6d61 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_inputstream.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_inputstream.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_tokenizer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_tokenizer.cpython-312.pyc index dcbc6e59..46b53aad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_tokenizer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_tokenizer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_utils.cpython-312.pyc index d20f3013..8b5e1030 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/constants.cpython-312.pyc index 46a7a523..4d23015c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/html5parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/html5parser.cpython-312.pyc index 8480f652..719eec87 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/html5parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/html5parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/serializer.cpython-312.pyc index 00eccdc1..10197759 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/serializer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/__pycache__/serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/__init__.cpython-312.pyc index b6f1c0bf..48dc89f0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/_base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/_base.cpython-312.pyc index 93b78d66..48a860aa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/_base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/_base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/py.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/py.cpython-312.pyc index 0913b8db..3a147387 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/py.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/_trie/__pycache__/py.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/__init__.cpython-312.pyc index 0da68e4c..e5ce8426 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/base.cpython-312.pyc index 0724b922..fce8d9d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/sanitizer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/sanitizer.cpython-312.pyc index 7d5083f3..5775a4d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/sanitizer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/filters/__pycache__/sanitizer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/__init__.cpython-312.pyc index e952866e..f47fb64d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/base.cpython-312.pyc index 2daf0f4f..5b47b8e7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treebuilders/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-312.pyc index 6571f10c..7455b59a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/bleach/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc index eece9f2f..cda1d6e4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc index c50579ca..bbf91bbb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc index 450be934..9ede99fa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc index f8058d54..5fdf2f3e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc index 11a8413b..4e608285 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc index 329a0eb7..5989647d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc index 3a66e51e..d9dfc95a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc index 74fbb05d..49c50ca1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc index 9d1aafc7..28f51102 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc index 6ada84ac..1e0a23f2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc index d32cbd45..971ea2a9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc index d60fa63c..6fddccc6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc index 5303a25a..210a9103 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc index f0890920..4799af89 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc index 42d27b97..efc6828c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc index ce303a28..fc6fb447 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc index 73fbb11c..6e3e35ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc index 89b635e7..4f1475d2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc index 5627c80d..bd20dd79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc index 1c77cb4c..0732898d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc index 50cebef4..dc84d737 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc index 76bf5f63..202906df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc index 20e2ee20..88dec6be 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__about__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc index 4e6df774..3c0a752a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc index c9c901a9..8d6bb620 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc index 6ce20e0e..b0c6446e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/fernet.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc index 63892bfb..3478b908 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc index eeeef84c..e4f6b15d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc index 9f72801a..538085c0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/__pycache__/_oid.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc index 83bd5c90..bd506bfc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc index 8eb410c1..2a27d23f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc index b161a284..6335d79f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc index c9625a28..f1d51af4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc index ae3171a5..b6bef7d2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc index fa9efc2d..d0e80097 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc index df6fc35e..58942c8f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc index 37c376df..c4329e89 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc index 8d696a25..399a9a52 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc index f7acfa3f..f819de16 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc index 7ac68e70..ad3b01ea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc index 153678cd..3c4a37cb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc index 90962e3e..57a81b89 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc index df3e3291..19d229f6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc index 66895bec..f88d54df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc index aeb00d1f..c3524b6c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc index 9dd7f6f9..980959b9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc index 1d4218f3..d2876620 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc index 3e1651aa..f3a4d334 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc index 44f0728e..c5819897 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc index 1d851a77..60aaccd2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc index dc11d5e2..3aad6b60 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc index 1964efe7..385aa24a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc index ba9ce528..ce37ba7d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc index a103a52a..24d6c03c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc index b501c3e2..16ec8d5b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc index dab05009..d3a5d01d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc index 0c796aa9..2759a37c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc index 0d96b94f..19749eee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc index 5b850d8a..75865161 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc index 33bdc8b1..5bb83822 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc index 992f920c..6cc7ec5e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc index 53165ff9..9b6dac21 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc index e436fc03..822f9618 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc index e4becf1a..14636f7a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc index 12385c72..11f86e83 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc index bc3daee1..724b0a80 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc index 016a0c6a..666035fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc index c8f443ef..17b7a945 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc index c5c34993..0b831659 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc index 7c0a70f2..f61d9133 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc index 3c9f74e5..328abd09 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc index 36245790..ec5bb6e4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc index 37327060..1fb93e9c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc index 4550b459..44fca66b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/extensions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc index 0fd09543..3d69dcfe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/general_name.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc index 1d5cf10c..c7f2a859 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/name.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc index 02678a99..3669b95f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/oid.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc index 5099e2da..8dbe1d46 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/cryptography/x509/__pycache__/verification.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/ElementTree.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/ElementTree.cpython-312.pyc index 23e1ba60..6e38747d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/ElementTree.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/ElementTree.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/__init__.cpython-312.pyc index f219052c..8917c8d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/common.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/common.cpython-312.pyc index 6e7d110b..3648b938 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/common.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/defusedxml/__pycache__/common.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/__init__.cpython-312.pyc index 26a6ae0f..ed0331d7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/classic.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/classic.cpython-312.pyc index b7b805fa..1f567848 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/classic.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/classic.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/params.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/params.cpython-312.pyc index 126f5085..a7446fb0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/params.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/params.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/sphinx.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/sphinx.cpython-312.pyc index 7b1edb63..fdde562f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/sphinx.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/deprecated/__pycache__/sphinx.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc index 9b08bf6d..9d488177 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc index 2cff7149..4dce7547 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc index 2bfa8953..8b1b41a6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc index 9b35ef07..92810b53 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/__init__.cpython-312.pyc index af16bf04..e2e32ce9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/exceptions_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/exceptions_types.cpython-312.pyc index 0123bf13..496a69f1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/exceptions_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/exceptions_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/rfc_constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/rfc_constants.cpython-312.pyc index afdef37f..28b68fd4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/rfc_constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/rfc_constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/syntax.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/syntax.cpython-312.pyc index ca955e87..40b72ad2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/syntax.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/syntax.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/validate_email.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/validate_email.cpython-312.pyc index 942703cc..55a4b962 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/validate_email.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/validate_email.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/version.cpython-312.pyc index 35fbde4e..0600da24 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/email_validator/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc index 3092536b..2c2983ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc index 39d1e3c8..10715f4c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc index 06c6c6fd..d5d12e64 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc index cd34b086..ab291c1c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc index 28ad4f3f..6069edfa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc index 9eb15469..4fa9548a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc index b39938e1..2fcb0110 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc index 806f0b59..ae9ea813 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc index e06b1611..752c759d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc index 5372428a..6bb4d57e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc index 6ea4ad0e..11808a14 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc index 7b34323e..37f84c81 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc index 4c194a5a..81de2d04 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc index 6c9d2916..d0ef2b3f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc index 3dec0d96..7ec95c65 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/temp_pydantic_v1_params.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/temp_pydantic_v1_params.cpython-312.pyc index 3ee922ff..4d432f66 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/temp_pydantic_v1_params.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/temp_pydantic_v1_params.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc index ed7d2462..e50a9bd2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc index 8ecb0f41..77a51287 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc index 8fa57874..99414697 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc index 975d1c59..09b545e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/main.cpython-312.pyc index 25486383..3a22309e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/may_v1.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/may_v1.cpython-312.pyc index dd224cdc..0e13b817 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/may_v1.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/may_v1.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/model_field.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/model_field.cpython-312.pyc index 0f2fc815..6186c4d7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/model_field.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/model_field.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc index 17f7dab6..b76580fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v1.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v1.cpython-312.pyc index 457fb722..f08bf266 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v1.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v1.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc index eeb60b29..72cc9ce1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc index 7a348f65..355b7373 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc index 32296745..30e899c4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc index 84ae066d..9ecf833f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc index a9b29fce..c5cd8b35 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc index 24086257..8f425120 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc index 57b18ced..bea2aebe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc index 526bd858..fde6cbfa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc index 3d4fd6b2..671694f7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc index e5f103d4..5281e6f0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc index b60c3823..9c01beee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc index 51eada15..8726c2c6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc index 9565cd3f..6300ade3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc index 6b9226cf..fb4d44b1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc index 794b94bf..71825106 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc index d07cc9a8..2f93fe60 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc index 8594d7c5..1b7115f4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc index def1976c..446b4410 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc index 99a6ecb1..96ba825e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc index 62fab2c1..706ddd62 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/__init__.cpython-312.pyc index 0c473e8b..a7d76042 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/_version.cpython-312.pyc index 959fc73e..e12fa2e8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httptools/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/__init__.cpython-312.pyc index 4e93220d..0338e72d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/errors.cpython-312.pyc index b5a73483..e8054be8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/protocol.cpython-312.pyc index 0070ee07..99b0a564 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httptools/parser/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc index 9ba732c6..255f0330 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc index 829a78f2..b940d794 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc index 629c6f55..d85b18d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc index 78e10a79..367abf21 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc index 2f065d68..b1af977d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc index 1683b230..f4c6e9f2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc index 21f90ecf..aaa4d9ac 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc index 2a4c84e0..9d5d24d3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc index 5237ce11..fd0f622a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc index 3d140869..aed352f8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc index f653a961..135262ea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc index bc03d531..a5e09b5b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc index d5129e51..8404e780 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc index 792ba9af..c4df4b63 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc index c7a5d712..5aabf7d7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc index 91f05d89..a89e4d5d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc index 6ee67ae3..4b1cdb10 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc index 08fb82a7..2b0b41c9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc index c26f7cbc..00e7a1c7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc index 9c578e8a..24ae6c76 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc index 17735c56..93af0dd8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc index d23ba42b..04421045 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc index 38312f26..9f548b00 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/__init__.cpython-312.pyc index f523ba29..d0d8e654 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/core.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/core.cpython-312.pyc index f4e16082..97b41d81 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/core.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/core.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/idnadata.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/idnadata.cpython-312.pyc index c8117e60..eac06ad5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/idnadata.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/idnadata.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/intranges.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/intranges.cpython-312.pyc index 478f0049..974e61a8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/intranges.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/intranges.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/package_data.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/package_data.cpython-312.pyc index 6feaa5dc..c4ed118f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/package_data.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/package_data.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/uts46data.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/uts46data.cpython-312.pyc index 5bf6b566..443cb799 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/uts46data.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/idna/__pycache__/uts46data.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/__init__.cpython-312.pyc index ae59046b..67cec43d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/constants.cpython-312.pyc index cc271883..22d05a13 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/exceptions.cpython-312.pyc index f933eb20..ebc5cad2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwk.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwk.cpython-312.pyc index ef71fa60..e9223f72 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwk.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwk.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jws.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jws.cpython-312.pyc index f8ac6859..5b290ae0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jws.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jws.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwt.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwt.cpython-312.pyc index 3cc65585..9cee352d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwt.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/jwt.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/utils.cpython-312.pyc index dbb450f5..353e7d5d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/__init__.cpython-312.pyc index 43850aee..2e44cafd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/base.cpython-312.pyc index d8d421c7..c1645e40 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/cryptography_backend.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/cryptography_backend.cpython-312.pyc index 5dd6a405..5050a660 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/cryptography_backend.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/cryptography_backend.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/native.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/native.cpython-312.pyc index 2ed385fb..38d76c12 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/native.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/jose/backends/__pycache__/native.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/__init__.cpython-312.pyc index d158c236..a41b897f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/_version.cpython-312.pyc index 3c7e458b..160fc7c6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/errors.cpython-312.pyc index b8670cbd..bca70456 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/limits.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/limits.cpython-312.pyc index 2f6fa1a3..0df806b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/limits.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/limits.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/strategies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/strategies.cpython-312.pyc index 5066e3b1..4ff91da6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/strategies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/strategies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/typing.cpython-312.pyc index 4aa0ee7f..7ac1c359 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/util.cpython-312.pyc index 551e0cc1..e4a54232 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/__init__.cpython-312.pyc index 7439a880..8679bc0f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/strategies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/strategies.cpython-312.pyc index 291ae1d8..6c27e628 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/strategies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/__pycache__/strategies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/__init__.cpython-312.pyc index 12ff1e6c..42e5acca 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/base.cpython-312.pyc index 1265a57b..58a7d54b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/memory.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/memory.cpython-312.pyc index 1718b1a8..fde47057 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/memory.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/memory.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/mongodb.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/mongodb.cpython-312.pyc index c3686161..0e334394 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/mongodb.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/__pycache__/mongodb.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/__init__.cpython-312.pyc index 457e9756..a9b56c16 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/bridge.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/bridge.cpython-312.pyc index 0f5881cc..4ead1892 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/bridge.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/bridge.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/emcache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/emcache.cpython-312.pyc index 94a01907..12a166e8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/emcache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/emcache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/memcachio.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/memcachio.cpython-312.pyc index c5402471..d5aa9edf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/memcachio.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/memcached/__pycache__/memcachio.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/__init__.cpython-312.pyc index 2886cb1a..7fad06ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/bridge.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/bridge.cpython-312.pyc index d5ef1989..934e1c37 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/bridge.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/bridge.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/coredis.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/coredis.cpython-312.pyc index 9a27f2ce..d302d321 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/coredis.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/coredis.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/redispy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/redispy.cpython-312.pyc index c3f7c8fa..b611fdbb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/redispy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/redispy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/valkey.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/valkey.cpython-312.pyc index e42a1849..031c3afb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/valkey.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/aio/storage/redis/__pycache__/valkey.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/__init__.cpython-312.pyc index bfafbbcd..62280065 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/base.cpython-312.pyc index 9263b63e..e4779eef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memcached.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memcached.cpython-312.pyc index 055306ae..51df251f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memcached.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memcached.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memory.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memory.cpython-312.pyc index c0ce8d03..9ecd4de1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memory.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/memory.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/mongodb.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/mongodb.cpython-312.pyc index 092b5ff7..af7fa799 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/mongodb.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/mongodb.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis.cpython-312.pyc index f88d5291..e9ec0cba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_cluster.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_cluster.cpython-312.pyc index 91830365..65fc2ca2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_cluster.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_cluster.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_sentinel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_sentinel.cpython-312.pyc index 4d85e846..e4693389 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_sentinel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/redis_sentinel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/registry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/registry.cpython-312.pyc index a0410de4..e8067c90 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/registry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/limits/storage/__pycache__/registry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/__init__.cpython-312.pyc index 43e14cb8..b0efd060 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/_ast_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/_ast_util.cpython-312.pyc index 4e720637..ea336e30 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/_ast_util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/_ast_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/ast.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/ast.cpython-312.pyc index 34dfe8f8..47b8ddb1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/ast.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/ast.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/cache.cpython-312.pyc index 01b753a0..25d0158e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/codegen.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/codegen.cpython-312.pyc index 3b937af5..fe0890ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/codegen.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/codegen.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/compat.cpython-312.pyc index b74742c2..3ecc97b3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/exceptions.cpython-312.pyc index 173bd8aa..579d5584 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/filters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/filters.cpython-312.pyc index 3cbab225..b0761ef8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/filters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/filters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/lexer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/lexer.cpython-312.pyc index 27562941..a6bf0c0a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/lexer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/lexer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/parsetree.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/parsetree.cpython-312.pyc index d474b5d4..4af17a14 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/parsetree.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/parsetree.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pygen.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pygen.cpython-312.pyc index 9f3e9ff3..902da724 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pygen.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pygen.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pyparser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pyparser.cpython-312.pyc index 7de206c1..11cc3ab2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pyparser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/pyparser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/runtime.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/runtime.cpython-312.pyc index 260e76ff..345e28e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/runtime.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/runtime.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/template.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/template.cpython-312.pyc index ded10a55..89a4b752 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/template.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/template.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/util.cpython-312.pyc index 55d898e0..0b9e4cfe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/__init__.cpython-312.pyc index fcb82f06..14b60935 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-312.pyc index 91daa811..055880a5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc index f16873a9..307cd095 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/__init__.cpython-312.pyc index 4a1a5c10..41165a0e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/_structures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/_structures.cpython-312.pyc index 4aa32fe4..d3af0ebc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/_structures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/_structures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/version.cpython-312.pyc index eff75247..71f5c312 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/packaging/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc index 2bed8584..e88b0fc3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc index 388fc07a..25346ea5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc index b4671938..f60b76db 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc index 899516db..f4dc13ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc index 97882416..dd751d47 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/access_token_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc index 35096f48..5ead4ed3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/environment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc index bb7ec43f..cd408d82 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/paypal_http_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc index c0a43e65..d17a7d9c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/refresh_token_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc index 5624dfd5..069e401c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/core/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc index e332d449..c9b136fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc index 0e66a296..564850d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_authorize_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc index 5b7e5335..82cae2da 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_capture_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc index ca2b3c45..14cec159 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_create_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc index b86a6c98..66989d3b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc index c8bd4c16..9d768c35 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_patch_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc index 01d2654c..fe9544e1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/orders/__pycache__/orders_validate_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc index ce6970a5..9ff23b21 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc index b72fbede..75255d4b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_capture_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc index 4ba481d4..2f0ce2cc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc index a048a05d..1c672683 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_reauthorize_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc index 72697a3d..bc7e1d20 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/authorizations_void_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc index 97eefb20..ced510f2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc index e4aca873..427dd723 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/captures_refund_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc index 00b3d08c..88d04537 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalcheckoutsdk/payments/__pycache__/refunds_get_request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc index fbf7e2f9..e5e1a832 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc index 1cbf073f..c1dd753f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/encoder.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc index d4d6e52c..c72cb4ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/environment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc index 1987e356..25ac442b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/file.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc index 9a2fc64f..5ee1b489 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc index acf7955f..844b87b3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_error.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc index 28a37638..d9c013d7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/__pycache__/http_response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc index ee95ea3a..90bf365d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc index 22e9be5c..0dfca777 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_encoded_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc index e3d0c95e..7452065a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/form_part.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc index a4865d6a..900aba9d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/json_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc index e618d745..77a1056b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/multipart_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc index 9a5edaaf..c234943d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/paypalhttp/serializers/__pycache__/text_serializer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc index 133cde35..6f4eb657 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc index dfccec43..5cb40a4e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc new file mode 100644 index 00000000..01e2e4f8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc index 5c438fa7..2380107d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc index 8475356a..a3a851a1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc index bda1830c..f056e70e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc index 50e17387..61cf35d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc index 5275e972..7a5fa5ae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc new file mode 100644 index 00000000..ec0dd64c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc index d1f1fd06..a6f2b0b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc index 8017583c..bc839fe4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc index b3f6ff91..168194c0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc index f6962d6e..e9999f36 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc index eb971aed..ae172466 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc index 79ce44c0..6219c248 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc index 9bf3d2ea..d30aad45 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc index bff8db8e..53339b54 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc index b04d882f..aff0aece 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc index 2d3e1a58..f39dd2de 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc index 052063e6..d26d25e4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc index 8b55a966..20e30abf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc index 88760d39..4e1e9a2f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc index df76262b..646f21f7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc index 735b1621..0ee65e48 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc index 846bdbc8..cde67eaf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc index 22242d8d..853a15b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc new file mode 100644 index 00000000..1bcb49c3 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc new file mode 100644 index 00000000..c5fce98a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc new file mode 100644 index 00000000..bfa7e603 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc new file mode 100644 index 00000000..7ecf8fa0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc new file mode 100644 index 00000000..ea4df7b8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc new file mode 100644 index 00000000..523e8ba3 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc index 9dfda734..31e7b72e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc new file mode 100644 index 00000000..a03400db Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc new file mode 100644 index 00000000..d320adf0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc new file mode 100644 index 00000000..708aa524 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc new file mode 100644 index 00000000..e9663795 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc index ebc45ab5..a7705f5f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc index 13580e00..9a44cbe7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/lock.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/lock.cpython-312.pyc new file mode 100644 index 00000000..1878563b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/lock.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc new file mode 100644 index 00000000..3f04364b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc index 42cc941d..fec827d3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc new file mode 100644 index 00000000..3c4f344c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc new file mode 100644 index 00000000..31337b80 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc index c0700ef2..c6514f9f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc index b0b6c357..8c1a1cbf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc index 5356946d..ee12b991 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc index 73854602..a1f31817 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc index 6fac619b..9c677c50 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc index ea3d2fd8..818ab728 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc index d3907c1a..a80f5339 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc index 777e719e..cb33a017 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc index 3d6a7fe4..6f93002f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc index 098d9463..fc3df910 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc new file mode 100644 index 00000000..34a0c627 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc index 724d169a..dcaf5133 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc index 87d44bae..8e61a1b2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc index 42cc0ba3..72703bf8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc index dc837b2b..ef5ea1c4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc index b91e6339..9050b477 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc new file mode 100644 index 00000000..5a4903fa Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc index 651a5452..188ec0ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc index 00f5dba0..f77bc5a8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc index 45d55ee3..fbc83819 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc index a9734970..b14c108c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc index ee871b5c..bc82d8df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc index 5c9da878..e414a569 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc index 6602847a..864ff0a3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc index dc498d15..d5f7e645 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc index c1ff97ab..6487af07 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc index cd343a34..540e27d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc index d4e522b5..3c83f2ae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/pylock.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/pylock.cpython-312.pyc new file mode 100644 index 00000000..e95bbe78 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/pylock.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc index 75f8c82c..87fecb30 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc index 86441fd5..963d3850 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc index e0d2fef2..e18bb5c7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc index 53b65d4e..6faf8ca0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc index 965e7628..0898461d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc index 67b2270a..ecf7afb3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc index 7524d5f3..8fe907ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc index 590e0a73..dcbf47ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc index 8b5bf344..eb0b8bcc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc index 5d0af4ad..86827431 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc index d1a5da7d..0acb7350 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc index 70c8285d..464a4728 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc new file mode 100644 index 00000000..8fb270bc Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc index 8f26e353..6746a51d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc index 04aeb823..1039b91c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc index 5c2b3330..49eb9b97 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc index 31c23e13..b47bf058 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc index 6000dbe7..faca0369 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc index ceccc6de..41a7ab52 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc index b4bca657..bd1b0a36 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc index 0fac9c00..f1e5c913 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_dependency_group.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_dependency_group.cpython-312.pyc index b598c1dd..af7ccf1c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_dependency_group.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_dependency_group.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc index afd3e60c..cc836965 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc index 3c8b189f..4bfb9a0b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc index 05cf30e3..e57a2999 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc index 22f8c489..a36a18f6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc index 2ca3a0ce..5147c507 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc index 2bf70f8d..60012c05 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..0a0a2bf4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc new file mode 100644 index 00000000..08e60ce4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc index d10aaa89..6439cd3d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc index cfdb3c01..62715a79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc index 7c8620f3..a2a1c747 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc index a0f193fa..e0c57c9b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc index b867d03c..a258a7d1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc index 6de48e6b..d9f6a694 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc index 54dcb9e1..53697259 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc index 5d1535a1..6abc5f59 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc index 6f321e2a..e17ec7ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc index 0af4121c..6a394184 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc new file mode 100644 index 00000000..95608aaf Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc index 6f7ea42b..1af973df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc index c2c26518..514065c9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc index 531b73ff..5e5db2af 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc index bef63b7f..6ec5cb0c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc new file mode 100644 index 00000000..cdd9a427 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc index 5da0ab84..baf28fa9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc index 3f861407..318dcbbe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc index 4a900ad4..208aa66b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc index 605efc65..beaa96c3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc index 11adda48..dae66021 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc index 1b260fa3..4b0af7be 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc index 1f03c930..b40bbbc4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc index 412d6234..ac82ae94 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc index 5d2bbc3b..610deddb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc index 551ed61b..7d90d2ea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc index a4d5cd54..92de06e3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc index 9acbdedb..70c7fdb0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc index 7c9f51b0..e0e03620 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc index 74690613..63c2b640 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc index 04849962..2a5569ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc index 4f0fccdc..5404ce84 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc index 71331461..88dd4b3b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc index 08aa47a1..e198dcdf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc index 8a473abd..637fab63 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc index a438bf5a..d572ca05 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc index 3c9abe27..30a0a3fb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc index 78b6b7f6..db147c06 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc index 95444ba0..a12cb275 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc index ef4851f8..9695d428 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc index 6656d655..7a54500d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc index 85f54949..8238c4ba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc new file mode 100644 index 00000000..6bcfcb32 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc index 4ae19b16..4da2985d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc index fcf2d987..7fc4e01b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc index 891b4d2c..2524ce87 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc index 1ce95d6b..325c760e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc new file mode 100644 index 00000000..34e24de4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc index fb42afd4..e6230494 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc index c489a202..549d8bf7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc index cfcbfaf6..91ac8d7e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc index a5bc3a39..233f7ebe 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc index 3b3054ea..9c092b01 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc index 0f503b82..65fe526d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..3d142420 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc index 3824c58e..d490fd79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__init__.cpython-312.pyc index 8b489485..29ff3168 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..c107b600 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_implementation.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_implementation.cpython-312.pyc index f4cacefa..e38800dc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_implementation.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_implementation.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_lint_dependency_groups.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_lint_dependency_groups.cpython-312.pyc new file mode 100644 index 00000000..330a3eae Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_lint_dependency_groups.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_pip_wrapper.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_pip_wrapper.cpython-312.pyc new file mode 100644 index 00000000..18fc1daa Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_pip_wrapper.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_toml_compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_toml_compat.cpython-312.pyc new file mode 100644 index 00000000..1c7cd466 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/dependency_groups/__pycache__/_toml_compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc index 59f774e1..2aece85b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc index fa0cd966..ece60823 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc index a0184701..1dd77e09 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc index f678bf5a..90234f8a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc index aca477be..424bd216 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc index bf6696f0..51258d25 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..12806863 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc index f6d1ee42..4746c935 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc index 0b652dfe..b7c717b3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc new file mode 100644 index 00000000..17cf1a4b Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..fb4a9ded Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc index dd2d11fc..bf23b709 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc index b1f9d69b..0aa7c70c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc index caf91e57..4ce80bda 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc index c6ee1a5c..4c4e9a4b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc new file mode 100644 index 00000000..1e54f158 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc index 582b4b4f..769d24ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc index dc26f44f..a4f64fd9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc index 2ad222bb..327e0a75 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc index 3d0d47c3..c7be87ef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc index 682ded91..1a4d5087 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc index 67ed4c4d..24aeaae5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc index fa33a35c..f0878266 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc index 29207847..cf02bc10 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc index 127d8330..097a3bf8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc index a03654b3..7642177c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc index 270daa65..864b4fd9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc index bbd2a554..2ab2ab89 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc new file mode 100644 index 00000000..a0a00f62 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc index c0e99597..88b03206 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc index 05ca010a..32100cde 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc index 538d8d2d..458d4256 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc index 42bde423..a0ebb52c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc index 641aa7b3..1e4d9656 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..cb9f477d Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-312.pyc new file mode 100644 index 00000000..9305c1b2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/packaging/licenses/__pycache__/_spdx.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..aa9399b2 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc index 7a71430c..e79a9ad4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..4075648c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc new file mode 100644 index 00000000..ebb63bee Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc index 94caa0b5..414383c8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc new file mode 100644 index 00000000..9b7d8d83 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc index 7100b4d5..dcba40ca 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc index d723c249..4cc40e9c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc new file mode 100644 index 00000000..cc578479 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc index 5e03fadf..a6b28514 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..c28b7cf1 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc new file mode 100644 index 00000000..7507fe57 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc index 1becadd1..f475c269 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc new file mode 100644 index 00000000..bc962ff6 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc index 7e7af94e..0786bd41 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc index 88393f13..6b58bbea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc index 5f9591e2..99f6d617 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc index 1526b584..67968b48 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc new file mode 100644 index 00000000..873804be Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc new file mode 100644 index 00000000..de2a1566 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc index f1843027..25d29198 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc index 63a7e2e9..d59365a0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc new file mode 100644 index 00000000..a9d32b8c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc index 2281eac4..b6329e06 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc index 20da6aed..52523e4b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..0a3b103d Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc new file mode 100644 index 00000000..2d0efd80 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc index b7e82b2b..e2addd8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc index db55df76..8a1752b7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc new file mode 100644 index 00000000..b9584bb8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc index 18d9aa31..c175a1a3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc index 1f7af8b9..dbed8389 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc index b0a546ed..6c758269 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc index bb549645..f49caac6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc index 9e47d6bd..1bef1159 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc new file mode 100644 index 00000000..1d3162ee Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc index 777fd771..029afe74 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc index 9f41f01e..9c7b0ba3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc index 9391ac70..3ec1a4b3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc index 410995ef..b9a8e6cc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc index 2a6d32f4..e0dcd525 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc index 7142ce47..20ed5648 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc index 2437574d..d664a98d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc index fe34d49b..7e32e9a8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc index a8ffae9f..c5e7c300 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc index 820cf55b..5db43769 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc new file mode 100644 index 00000000..8e058e78 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc index a4e2ca76..679a71a0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc index 2794bb53..4645d90e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc index 01925963..344ece44 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc index a30df361..80a8f030 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc index e499c7a5..c5d7f8f5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc index 0fda36a3..04b97c9e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc index 0b7a2d4e..e013fee0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc index 199b3f7b..27063fdd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc index 002e7e93..031ef23a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc index b27122d0..b2098f80 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc index 66bbc7fd..1083bfca 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-312.pyc index 3b5c3912..2e3416bf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-312.pyc index d0cab2c2..48cfc562 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/abstract.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-312.pyc index cd326a93..01a6a9e7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/criterion.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-312.pyc index da945bbf..9f53962c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-312.pyc index 45f8d22b..d33516b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers/__pycache__/resolution.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc index b0053818..dcfcbbdc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..d740f312 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc index 57992b68..f66c0fdd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc index 5e6ad96d..b8479464 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc index ab2c7516..2ef16d54 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc index a7f5b84a..54e396b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc index dc2c0504..11885d0f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc index af4d647a..3e140fac 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc new file mode 100644 index 00000000..ac273a15 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc index 4438a793..8a54e21b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc index 27636dcf..bdf63932 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc index 17120939..e4f319f4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc index 3daa5a33..bbc23141 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc index ddc63dad..a66b4239 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc index 9e24b299..bb112e90 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc index 75f53077..b64e992b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc new file mode 100644 index 00000000..83ffb4e5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc new file mode 100644 index 00000000..4f0856cf Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc new file mode 100644 index 00000000..352fdeca Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc new file mode 100644 index 00000000..bb058628 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc new file mode 100644 index 00000000..1fe8c2ef Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc index 136ba0a0..c3b53aaf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc index a9846a3c..5078a998 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc index 5b2fee0f..0dd9a777 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc index c3160f83..9581f070 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc new file mode 100644 index 00000000..943ec3b6 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc index cf4ae3cc..e637d979 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc index 28f4ffd5..e7e30d9d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc index 20dcee3f..cf4be64d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc index 3739b61b..e9a65372 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc index c9dae992..c4b99d32 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc index e04b1515..1fbf2833 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc index 70d400f8..09bc135b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc index 8c87d328..795508b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc index 106262d1..459164fa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc index 19e2dea0..528ecaef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc new file mode 100644 index 00000000..990257ca Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc index b9335386..e56aba90 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc index e1b577bf..ea039a41 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc index 177916d3..b66b1646 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc index 62f9b631..2188ea76 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc index 351208cb..2a795b8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc new file mode 100644 index 00000000..c4725399 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc index 27ea0a87..9d574d13 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc new file mode 100644 index 00000000..1a7ad656 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc index 42303257..7945043b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc index 7f6f74af..4a838be1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc index d4bf2e4d..f427d504 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc index e398eabd..bb3b70cb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc index fe2db59c..3f59fc18 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc index d682754f..a740dcb0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc index 129768a8..e5937b48 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc index b63c45c0..2bae0188 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc index 65f2a092..7158156f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc index 8c52d9eb..6cf9f0c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc index e6224e06..548df7a0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc index d457175a..c6066c98 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc new file mode 100644 index 00000000..c80ef6f0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc index 23eb67f4..b158e074 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc index e8ccceef..970b149c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc index 5921e6ad..e89e7e8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc new file mode 100644 index 00000000..8bb74ac8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc index a0a38b62..c5ee04b1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc index de10ef2c..17fe3217 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc index 13b67f5c..011763ff 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc index 5a41c3e1..6c73a07a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc new file mode 100644 index 00000000..fe2db321 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc index 7e1abbd1..b38c8eec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc index 839d5f9d..7018fb11 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc index eb0a5470..2d81d8d0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc index b140502e..b6d6e43b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc index 5f42af1d..cf3860ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc index 45e6f43a..5490bc6d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc index cca5408c..df5d832f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc index 999f82d4..8e052342 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc index b8b538e0..b81dc7ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc new file mode 100644 index 00000000..f69d61f3 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..f3dc7d67 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc new file mode 100644 index 00000000..379c78e5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc new file mode 100644 index 00000000..3f595a4c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc new file mode 100644 index 00000000..5a4b6070 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..b820638a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc new file mode 100644 index 00000000..cfabe489 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/tomli_w/__pycache__/_writer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc index 275eff45..c4556b33 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc index 5808faa1..ed19c7d9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc new file mode 100644 index 00000000..07914b6d Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc index 1568f45d..dbcd1908 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc index 8a812ed7..97cb4f12 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc new file mode 100644 index 00000000..3e86aab6 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc index 61b539b8..61781511 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc index 0052f69f..013c3214 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc index fa5a774e..a12e169e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc index e8d5d924..6ba86957 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc index 0ec988d2..0bd4a18c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc index ac298f96..40a2fe60 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc index 8b973218..79b54d58 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc index 3f1b9732..ffdca1f3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc index af6118a3..f8d106bf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc index 8776a91f..24cc6fa5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc index 7142faf8..6bb1cdd8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc index ebaa1c35..f44c3ce4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc index d0afce5d..6daa2d8e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc new file mode 100644 index 00000000..c9fdc346 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc new file mode 100644 index 00000000..623702c5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc new file mode 100644 index 00000000..f78451a0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc new file mode 100644 index 00000000..49bb61e8 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc index 13eb7681..d62f438a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..6acad045 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc new file mode 100644 index 00000000..4177514a Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc new file mode 100644 index 00000000..56f702c5 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc index 23fe7cb8..b66685de 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc index 7b57f306..d088516d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..c3df771d Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc new file mode 100644 index 00000000..9faf5fb4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc new file mode 100644 index 00000000..adba38be Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc index 286e8a8a..0a877a7d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc index d3eaa37f..d637064b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc index fdf64476..f30ad3d1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc index a15ef655..250b98c4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc index 0f0bf695..1e5f6667 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc index 445fef98..8a686e49 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc index c1b42a11..35639135 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc index 66834f2e..a69374a6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc index a4092b88..07b971f3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc index 9c1a2fd7..a3f01710 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc index 302d209d..856f10ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc index b0bd2987..6aa7a413 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc index cde806a3..499d56a4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/py.py b/Backend/venv/lib/python3.12/site-packages/py.py index 5c661e66..7813c9b9 100644 --- a/Backend/venv/lib/python3.12/site-packages/py.py +++ b/Backend/venv/lib/python3.12/site-packages/py.py @@ -1,15 +1,10 @@ # shim for pylib going away # if pylib is installed this file will get skipped # (`py/__init__.py` has higher precedence) -from __future__ import annotations - import sys import _pytest._py.error as error import _pytest._py.path as path - sys.modules["py.error"] = error sys.modules["py.path"] = path - -__all__ = ["error", "path"] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/METADATA deleted file mode 100644 index fe0df088..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/METADATA +++ /dev/null @@ -1,1029 +0,0 @@ -Metadata-Version: 2.4 -Name: pydantic -Version: 2.12.5 -Summary: Data validation using Python type hints -Project-URL: Homepage, https://github.com/pydantic/pydantic -Project-URL: Documentation, https://docs.pydantic.dev -Project-URL: Funding, https://github.com/sponsors/samuelcolvin -Project-URL: Source, https://github.com/pydantic/pydantic -Project-URL: Changelog, https://docs.pydantic.dev/latest/changelog/ -Author-email: Samuel Colvin , Eric Jolibois , Hasan Ramezani , Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Terrence Dorsey , David Montague , Serge Matveenko , Marcelo Trylesinski , Sydney Runkle , David Hewitt , Alex Hall , Victorien Plot , Douwe Maan -License-Expression: MIT -License-File: LICENSE -Classifier: Development Status :: 5 - Production/Stable -Classifier: Framework :: Hypothesis -Classifier: Framework :: Pydantic -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Information Technology -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Requires-Python: >=3.9 -Requires-Dist: annotated-types>=0.6.0 -Requires-Dist: pydantic-core==2.41.5 -Requires-Dist: typing-extensions>=4.14.1 -Requires-Dist: typing-inspection>=0.4.2 -Provides-Extra: email -Requires-Dist: email-validator>=2.0.0; extra == 'email' -Provides-Extra: timezone -Requires-Dist: tzdata; (python_version >= '3.9' and platform_system == 'Windows') and extra == 'timezone' -Description-Content-Type: text/markdown - -# Pydantic Validation - -[![CI](https://img.shields.io/github/actions/workflow/status/pydantic/pydantic/ci.yml?branch=main&logo=github&label=CI)](https://github.com/pydantic/pydantic/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) -[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic) -[![pypi](https://img.shields.io/pypi/v/pydantic.svg)](https://pypi.python.org/pypi/pydantic) -[![CondaForge](https://img.shields.io/conda/v/conda-forge/pydantic.svg)](https://anaconda.org/conda-forge/pydantic) -[![downloads](https://static.pepy.tech/badge/pydantic/month)](https://pepy.tech/project/pydantic) -[![versions](https://img.shields.io/pypi/pyversions/pydantic.svg)](https://github.com/pydantic/pydantic) -[![license](https://img.shields.io/github/license/pydantic/pydantic.svg)](https://github.com/pydantic/pydantic/blob/main/LICENSE) -[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://docs.pydantic.dev/latest/contributing/#badges) -[![llms.txt](https://img.shields.io/badge/llms.txt-green)](https://docs.pydantic.dev/latest/llms.txt) - -Data validation using Python type hints. - -Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. -Define how data should be in pure, canonical Python 3.9+; validate it with Pydantic. - -## Pydantic Logfire :fire: - -We've recently launched Pydantic Logfire to help you monitor your applications. -[Learn more](https://pydantic.dev/articles/logfire-announcement) - -## Pydantic V1.10 vs. V2 - -Pydantic V2 is a ground-up rewrite that offers many new features, performance improvements, and some breaking changes compared to Pydantic V1. - -If you're using Pydantic V1 you may want to look at the -[pydantic V1.10 Documentation](https://docs.pydantic.dev/) or, -[`1.10.X-fixes` git branch](https://github.com/pydantic/pydantic/tree/1.10.X-fixes). Pydantic V2 also ships with the latest version of Pydantic V1 built in so that you can incrementally upgrade your code base and projects: `from pydantic import v1 as pydantic_v1`. - -## Help - -See [documentation](https://docs.pydantic.dev/) for more details. - -## Installation - -Install using `pip install -U pydantic` or `conda install pydantic -c conda-forge`. -For more installation options to make Pydantic even faster, -see the [Install](https://docs.pydantic.dev/install/) section in the documentation. - -## A Simple Example - -```python -from datetime import datetime -from typing import Optional -from pydantic import BaseModel - -class User(BaseModel): - id: int - name: str = 'John Doe' - signup_ts: Optional[datetime] = None - friends: list[int] = [] - -external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']} -user = User(**external_data) -print(user) -#> User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] -print(user.id) -#> 123 -``` - -## Contributing - -For guidance on setting up a development environment and how to make a -contribution to Pydantic, see -[Contributing to Pydantic](https://docs.pydantic.dev/contributing/). - -## Reporting a Security Vulnerability - -See our [security policy](https://github.com/pydantic/pydantic/security/policy). - -## Changelog - - - - - -## v2.12.5 (2025-11-26) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.5) - -This is the fifth 2.12 patch release, addressing an issue with the `MISSING` sentinel and providing several documentation improvements. - -The next 2.13 minor release will be published in a couple weeks, and will include a new *polymorphic serialization* feature addressing -the remaining unexpected changes to the *serialize as any* behavior. - -* Fix pickle error when using `model_construct()` on a model with `MISSING` as a default value by [@ornariece](https://github.com/ornariece) in [#12522](https://github.com/pydantic/pydantic/pull/12522). -* Several updates to the documentation by [@Viicos](https://github.com/Viicos). - -## v2.12.4 (2025-11-05) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.4) - -This is the fourth 2.12 patch release, fixing more regressions, and reverting a change in the `build()` method -of the [`AnyUrl` and Dsn types](https://docs.pydantic.dev/latest/api/networks/). - -This patch release also fixes an issue with the serialization of IP address types, when `serialize_as_any` is used. The next patch release -will try to address the remaining issues with *serialize as any* behavior by introducing a new *polymorphic serialization* feature, that -should be used in most cases in place of *serialize as any*. - -* Fix issue with forward references in parent `TypedDict` classes by [@Viicos](https://github.com/Viicos) in [#12427](https://github.com/pydantic/pydantic/pull/12427). - - This issue is only relevant on Python 3.14 and greater. -* Exclude fields with `exclude_if` from JSON Schema required fields by [@Viicos](https://github.com/Viicos) in [#12430](https://github.com/pydantic/pydantic/pull/12430) -* Revert URL percent-encoding of credentials in the `build()` method - of the [`AnyUrl` and Dsn types](https://docs.pydantic.dev/latest/api/networks/) by [@davidhewitt](https://github.com/davidhewitt) in - [pydantic-core#1833](https://github.com/pydantic/pydantic-core/pull/1833). - - This was initially considered as a bugfix, but caused regressions and as such was fully reverted. The next release will include - an opt-in option to percent-encode components of the URL. -* Add type inference for IP address types by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1868](https://github.com/pydantic/pydantic-core/pull/1868). - - The 2.12 changes to the `serialize_as_any` behavior made it so that IP address types could not properly serialize to JSON. -* Avoid getting default values from defaultdict by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1853](https://github.com/pydantic/pydantic-core/pull/1853). - - This fixes a subtle regression in the validation behavior of the [`collections.defaultdict`](https://docs.python.org/3/library/collections.html#collections.defaultdict) - type. -* Fix issue with field serializers on nested typed dictionaries by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1879](https://github.com/pydantic/pydantic-core/pull/1879). -* Add more `pydantic-core` builds for the three-threaded version of Python 3.14 by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1864](https://github.com/pydantic/pydantic-core/pull/1864). - -## v2.12.3 (2025-10-17) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.3) - -### What's Changed - -This is the third 2.12 patch release, fixing issues related to the `FieldInfo` class, and reverting a change to the supported -[*after* model validator](https://docs.pydantic.dev/latest/concepts/validators/#model-validators) function signatures. - -* Raise a warning when an invalid after model validator function signature is raised by [@Viicos](https://github.com/Viicos) in [#12414](https://github.com/pydantic/pydantic/pull/12414). - Starting in 2.12.0, using class methods for *after* model validators raised an error, but the error wasn't raised concistently. We decided - to emit a deprecation warning instead. -* Add [`FieldInfo.asdict()`](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.FieldInfo.asdict) method, improve documentation around `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#12411](https://github.com/pydantic/pydantic/pull/12411). - This also add back support for mutations on `FieldInfo` classes, that are reused as `Annotated` metadata. **However**, note that this is still - *not* a supported pattern. Instead, please refer to the [added example](https://docs.pydantic.dev/latest/examples/dynamic_models/) in the documentation. - -The [blog post](https://pydantic.dev/articles/pydantic-v2-12-release#changes) section on changes was also updated to document the changes related to `serialize_as_any`. - -## v2.12.2 (2025-10-14) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.2) - -### What's Changed - -#### Fixes - -* Release a new `pydantic-core` version, as a corrupted CPython 3.10 `manylinux2014_aarch64` wheel got uploaded ([pydantic-core#1843](https://github.com/pydantic/pydantic-core/pull/1843)). -* Fix issue with recursive generic models with a parent model class by [@Viicos](https://github.com/Viicos) in [#12398](https://github.com/pydantic/pydantic/pull/12398) - -## v2.12.1 (2025-10-13) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.1) - -### What's Changed - -This is the first 2.12 patch release, addressing most (but not all yet) regressions from the initial 2.12.0 release. - -#### Fixes - -* Do not evaluate annotations when inspecting validators and serializers by [@Viicos](https://github.com/Viicos) in [#12355](https://github.com/pydantic/pydantic/pull/12355) -* Make sure `None` is converted as `NoneType` in Python 3.14 by [@Viicos](https://github.com/Viicos) in [#12370](https://github.com/pydantic/pydantic/pull/12370) -* Backport V1 runtime warning when using Python 3.14 by [@Viicos](https://github.com/Viicos) in [#12367](https://github.com/pydantic/pydantic/pull/12367) -* Fix error message for invalid validator signatures by [@Viicos](https://github.com/Viicos) in [#12366](https://github.com/pydantic/pydantic/pull/12366) -* Populate field name in `ValidationInfo` for validation of default value by [@Viicos](https://github.com/Viicos) in [pydantic-core#1826](https://github.com/pydantic/pydantic-core/pull/1826) -* Encode credentials in `MultiHostUrl` builder by [@willswire](https://github.com/willswire) in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) -* Respect field serializers when using `serialize_as_any` serialization flag by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) -* Fix various `RootModel` serialization issues by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1836](https://github.com/pydantic/pydantic-core/pull/1836) - -### New Contributors - -* [@willswire](https://github.com/willswire) made their first contribution in [pydantic-core#1829](https://github.com/pydantic/pydantic-core/pull/1829) - -## v2.12.0 (2025-10-07) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0) - -### What's Changed - -This is the final 2.12 release. It features the work of 20 external contributors and provides useful new features, along with initial Python 3.14 support. -Several minor changes (considered non-breaking changes according to our [versioning policy](https://docs.pydantic.dev/2.12/version-policy/#pydantic-v2)) -are also included in this release. Make sure to look into them before upgrading. - -**Note that Pydantic V1 is not compatible with Python 3.14 and greater**. - -Changes (see the alpha and beta releases for additional changes since 2.11): - -#### Packaging - -* Update V1 copy to v1.10.24 by [@Viicos](https://github.com/Viicos) in [#12338](https://github.com/pydantic/pydantic/pull/12338) - -#### New Features - -* Add `extra` parameter to the validate functions by [@anvilpete](https://github.com/anvilpete) in [#12233](https://github.com/pydantic/pydantic/pull/12233) -* Add `exclude_computed_fields` serialization option by [@Viicos](https://github.com/Viicos) in [#12334](https://github.com/pydantic/pydantic/pull/12334) -* Add `preverse_empty_path` URL options by [@Viicos](https://github.com/Viicos) in [#12336](https://github.com/pydantic/pydantic/pull/12336) -* Add `union_format` parameter to JSON Schema generation by [@Viicos](https://github.com/Viicos) in [#12147](https://github.com/pydantic/pydantic/pull/12147) -* Add `__qualname__` parameter for `create_model` by [@Atry](https://github.com/Atry) in [#12001](https://github.com/pydantic/pydantic/pull/12001) - -#### Fixes - -* Do not try to infer name from lambda definitions in pipelines API by [@Viicos](https://github.com/Viicos) in [#12289](https://github.com/pydantic/pydantic/pull/12289) -* Use proper namespace for functions in `TypeAdapter` by [@Viicos](https://github.com/Viicos) in [#12324](https://github.com/pydantic/pydantic/pull/12324) -* Use `Any` for context type annotation in `TypeAdapter` by [@inducer](https://github.com/inducer) in [#12279](https://github.com/pydantic/pydantic/pull/12279) -* Expose `FieldInfo` in `pydantic.fields.__all__` by [@Viicos](https://github.com/Viicos) in [#12339](https://github.com/pydantic/pydantic/pull/12339) -* Respect `validation_alias` in `@validate_call` by [@Viicos](https://github.com/Viicos) in [#12340](https://github.com/pydantic/pydantic/pull/12340) -* Use `Any` as context annotation in plugin API by [@Viicos](https://github.com/Viicos) in [#12341](https://github.com/pydantic/pydantic/pull/12341) -* Use proper `stacklevel` in warnings when possible by [@Viicos](https://github.com/Viicos) in [#12342](https://github.com/pydantic/pydantic/pull/12342) - -### New Contributors - -* [@anvilpete](https://github.com/anvilpete) made their first contribution in [#12233](https://github.com/pydantic/pydantic/pull/12233) -* [@JonathanWindell](https://github.com/JonathanWindell) made their first contribution in [#12327](https://github.com/pydantic/pydantic/pull/12327) -* [@inducer](https://github.com/inducer) made their first contribution in [#12279](https://github.com/pydantic/pydantic/pull/12279) -* [@Atry](https://github.com/Atry) made their first contribution in [#12001](https://github.com/pydantic/pydantic/pull/12001) - -## v2.12.0b1 (2025-10-03) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0b1) - -This is the first beta release of the upcoming 2.12 release. - -### What's Changed - -#### Packaging - -* Bump `pydantic-core` to v2.40.1 by [@Viicos](https://github.com/Viicos) in [#12314](https://github.com/pydantic/pydantic/pull/12314) - -#### New Features - -* Add support for `exclude_if` at the field level by [@andresliszt](https://github.com/andresliszt) in [#12141](https://github.com/pydantic/pydantic/pull/12141) -* Add `ValidateAs` annotation helper by [@Viicos](https://github.com/Viicos) in [#11942](https://github.com/pydantic/pydantic/pull/11942) -* Add configuration options for validation and JSON serialization of temporal types by [@ollz272](https://github.com/ollz272) in [#12068](https://github.com/pydantic/pydantic/pull/12068) -* Add support for PEP 728 by [@Viicos](https://github.com/Viicos) in [#12179](https://github.com/pydantic/pydantic/pull/12179) -* Add field name in serialization error by [@NicolasPllr1](https://github.com/NicolasPllr1) in [pydantic-core#1799](https://github.com/pydantic/pydantic-core/pull/1799) -* Add option to preserve empty URL paths by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1789](https://github.com/pydantic/pydantic-core/pull/1789) - -#### Changes - -* Raise error if an incompatible `pydantic-core` version is installed by [@Viicos](https://github.com/Viicos) in [#12196](https://github.com/pydantic/pydantic/pull/12196) -* Remove runtime warning for experimental features by [@Viicos](https://github.com/Viicos) in [#12265](https://github.com/pydantic/pydantic/pull/12265) -* Warn if registering virtual subclasses on Pydantic models by [@Viicos](https://github.com/Viicos) in [#11669](https://github.com/pydantic/pydantic/pull/11669) - -#### Fixes - -* Fix `__getattr__()` behavior on Pydantic models when a property raised an `AttributeError` and extra values are present by [@raspuchin](https://github.com/raspuchin) in [#12106](https://github.com/pydantic/pydantic/pull/12106) -* Add test to prevent regression with Pydantic models used as annotated metadata by [@Viicos](https://github.com/Viicos) in [#12133](https://github.com/pydantic/pydantic/pull/12133) -* Allow to use property setters on Pydantic dataclasses with `validate_assignment` set by [@Viicos](https://github.com/Viicos) in [#12173](https://github.com/pydantic/pydantic/pull/12173) -* Fix mypy v2 plugin for upcoming mypy release by [@cdce8p](https://github.com/cdce8p) in [#12209](https://github.com/pydantic/pydantic/pull/12209) -* Respect custom title in functions JSON Schema by [@Viicos](https://github.com/Viicos) in [#11892](https://github.com/pydantic/pydantic/pull/11892) -* Fix `ImportString` JSON serialization for objects with a `name` attribute by [@chr1sj0nes](https://github.com/chr1sj0nes) in [#12219](https://github.com/pydantic/pydantic/pull/12219) -* Do not error on fields overridden by methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#12290](https://github.com/pydantic/pydantic/pull/12290) - -### New Contributors - -* [@raspuchin](https://github.com/raspuchin) made their first contribution in [#12106](https://github.com/pydantic/pydantic/pull/12106) -* [@chr1sj0nes](https://github.com/chr1sj0nes) made their first contribution in [#12219](https://github.com/pydantic/pydantic/pull/12219) - -## v2.12.0a1 (2025-07-26) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.12.0a1) - -This is the first alpha release of the upcoming 2.12 release, which adds initial support for Python 3.14. - -### What's Changed - -#### New Features - -* Add `__pydantic_on_complete__()` hook that is called once model is fully ready to be used by [@DouweM](https://github.com/DouweM) in [#11762](https://github.com/pydantic/pydantic/pull/11762) -* Add initial support for Python 3.14 by [@Viicos](https://github.com/Viicos) in [#11991](https://github.com/pydantic/pydantic/pull/11991) -* Add regex patterns to JSON schema for `Decimal` type by [@Dima-Bulavenko](https://github.com/Dima-Bulavenko) in [#11987](https://github.com/pydantic/pydantic/pull/11987) -* Add support for `doc` attribute on dataclass fields by [@Viicos](https://github.com/Viicos) in [#12077](https://github.com/pydantic/pydantic/pull/12077) -* Add experimental `MISSING` sentinel by [@Viicos](https://github.com/Viicos) in [#11883](https://github.com/pydantic/pydantic/pull/11883) - -#### Changes - -* Allow config and bases to be specified together in `create_model()` by [@Viicos](https://github.com/Viicos) in [#11714](https://github.com/pydantic/pydantic/pull/11714) -* Move some field logic out of the `GenerateSchema` class by [@Viicos](https://github.com/Viicos) in [#11733](https://github.com/pydantic/pydantic/pull/11733) -* Always make use of `inspect.getsourcelines()` for docstring extraction on Python 3.13 and greater by [@Viicos](https://github.com/Viicos) in [#11829](https://github.com/pydantic/pydantic/pull/11829) -* Only support the latest Mypy version by [@Viicos](https://github.com/Viicos) in [#11832](https://github.com/pydantic/pydantic/pull/11832) -* Do not implicitly convert after model validators to class methods by [@Viicos](https://github.com/Viicos) in [#11957](https://github.com/pydantic/pydantic/pull/11957) -* Refactor `FieldInfo` creation implementation by [@Viicos](https://github.com/Viicos) in [#11898](https://github.com/pydantic/pydantic/pull/11898) -* Make `Secret` covariant by [@bluenote10](https://github.com/bluenote10) in [#12008](https://github.com/pydantic/pydantic/pull/12008) -* Emit warning when field-specific metadata is used in invalid contexts by [@Viicos](https://github.com/Viicos) in [#12028](https://github.com/pydantic/pydantic/pull/12028) - -#### Fixes - -* Properly fetch plain serializer function when serializing default value in JSON Schema by [@Viicos](https://github.com/Viicos) in [#11721](https://github.com/pydantic/pydantic/pull/11721) -* Remove generics cache workaround by [@Viicos](https://github.com/Viicos) in [#11755](https://github.com/pydantic/pydantic/pull/11755) -* Remove coercion of decimal constraints by [@Viicos](https://github.com/Viicos) in [#11772](https://github.com/pydantic/pydantic/pull/11772) -* Fix crash when expanding root type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11735](https://github.com/pydantic/pydantic/pull/11735) -* Only mark model as complete once all fields are complete by [@DouweM](https://github.com/DouweM) in [#11759](https://github.com/pydantic/pydantic/pull/11759) -* Do not provide `field_name` in validator core schemas by [@DouweM](https://github.com/DouweM) in [#11761](https://github.com/pydantic/pydantic/pull/11761) -* Fix issue with recursive generic models by [@Viicos](https://github.com/Viicos) in [#11775](https://github.com/pydantic/pydantic/pull/11775) -* Fix qualified name comparison of private attributes during namespace inspection by [@karta9821](https://github.com/karta9821) in [#11803](https://github.com/pydantic/pydantic/pull/11803) -* Make sure Pydantic dataclasses with slots and `validate_assignment` can be unpickled by [@Viicos](https://github.com/Viicos) in [#11769](https://github.com/pydantic/pydantic/pull/11769) -* Traverse `function-before` schemas during schema gathering by [@Viicos](https://github.com/Viicos) in [#11801](https://github.com/pydantic/pydantic/pull/11801) -* Fix check for stdlib dataclasses by [@Viicos](https://github.com/Viicos) in [#11822](https://github.com/pydantic/pydantic/pull/11822) -* Check if `FieldInfo` is complete after applying type variable map by [@Viicos](https://github.com/Viicos) in [#11855](https://github.com/pydantic/pydantic/pull/11855) -* Do not delete mock validator/serializer in `model_rebuild()` by [@Viicos](https://github.com/Viicos) in [#11890](https://github.com/pydantic/pydantic/pull/11890) -* Rebuild dataclass fields before schema generation by [@Viicos](https://github.com/Viicos) in [#11949](https://github.com/pydantic/pydantic/pull/11949) -* Always store the original field assignment on `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11946](https://github.com/pydantic/pydantic/pull/11946) -* Do not use deprecated methods as default field values by [@Viicos](https://github.com/Viicos) in [#11914](https://github.com/pydantic/pydantic/pull/11914) -* Allow callable discriminator to be applied on PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in [#11941](https://github.com/pydantic/pydantic/pull/11941) -* Suppress core schema generation warning when using `SkipValidation` by [@ygsh0816](https://github.com/ygsh0816) in [#12002](https://github.com/pydantic/pydantic/pull/12002) -* Do not emit typechecking error for invalid `Field()` default with `validate_default` set to `True` by [@Viicos](https://github.com/Viicos) in [#11988](https://github.com/pydantic/pydantic/pull/11988) -* Refactor logic to support Pydantic's `Field()` function in dataclasses by [@Viicos](https://github.com/Viicos) in [#12051](https://github.com/pydantic/pydantic/pull/12051) - -#### Packaging - -* Update project metadata to use PEP 639 by [@Viicos](https://github.com/Viicos) in [#11694](https://github.com/pydantic/pydantic/pull/11694) -* Bump `mkdocs-llmstxt` to v0.2.0 by [@Viicos](https://github.com/Viicos) in [#11725](https://github.com/pydantic/pydantic/pull/11725) -* Bump `pydantic-core` to v2.35.1 by [@Viicos](https://github.com/Viicos) in [#11963](https://github.com/pydantic/pydantic/pull/11963) -* Bump dawidd6/action-download-artifact from 10 to 11 by [@dependabot](https://github.com/dependabot)[bot] in [#12033](https://github.com/pydantic/pydantic/pull/12033) -* Bump astral-sh/setup-uv from 5 to 6 by [@dependabot](https://github.com/dependabot)[bot] in [#11826](https://github.com/pydantic/pydantic/pull/11826) -* Update mypy to 1.17.0 by [@Viicos](https://github.com/Viicos) in [#12076](https://github.com/pydantic/pydantic/pull/12076) - -### New Contributors - -* [@parth-paradkar](https://github.com/parth-paradkar) made their first contribution in [#11695](https://github.com/pydantic/pydantic/pull/11695) -* [@dqkqd](https://github.com/dqkqd) made their first contribution in [#11739](https://github.com/pydantic/pydantic/pull/11739) -* [@fhightower](https://github.com/fhightower) made their first contribution in [#11722](https://github.com/pydantic/pydantic/pull/11722) -* [@gbaian10](https://github.com/gbaian10) made their first contribution in [#11766](https://github.com/pydantic/pydantic/pull/11766) -* [@DouweM](https://github.com/DouweM) made their first contribution in [#11759](https://github.com/pydantic/pydantic/pull/11759) -* [@bowenliang123](https://github.com/bowenliang123) made their first contribution in [#11719](https://github.com/pydantic/pydantic/pull/11719) -* [@rawwar](https://github.com/rawwar) made their first contribution in [#11799](https://github.com/pydantic/pydantic/pull/11799) -* [@karta9821](https://github.com/karta9821) made their first contribution in [#11803](https://github.com/pydantic/pydantic/pull/11803) -* [@jinnovation](https://github.com/jinnovation) made their first contribution in [#11834](https://github.com/pydantic/pydantic/pull/11834) -* [@zmievsa](https://github.com/zmievsa) made their first contribution in [#11861](https://github.com/pydantic/pydantic/pull/11861) -* [@Otto-AA](https://github.com/Otto-AA) made their first contribution in [#11860](https://github.com/pydantic/pydantic/pull/11860) -* [@ygsh0816](https://github.com/ygsh0816) made their first contribution in [#12002](https://github.com/pydantic/pydantic/pull/12002) -* [@lukland](https://github.com/lukland) made their first contribution in [#12015](https://github.com/pydantic/pydantic/pull/12015) -* [@Dima-Bulavenko](https://github.com/Dima-Bulavenko) made their first contribution in [#11987](https://github.com/pydantic/pydantic/pull/11987) -* [@GSemikozov](https://github.com/GSemikozov) made their first contribution in [#12050](https://github.com/pydantic/pydantic/pull/12050) -* [@hannah-heywa](https://github.com/hannah-heywa) made their first contribution in [#12082](https://github.com/pydantic/pydantic/pull/12082) - -## v2.11.7 (2025-06-14) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.7) - -### What's Changed - -#### Fixes - -* Copy `FieldInfo` instance if necessary during `FieldInfo` build by [@Viicos](https://github.com/Viicos) in [#11898](https://github.com/pydantic/pydantic/pull/11898) - -## v2.11.6 (2025-06-13) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.6) - -### What's Changed - -#### Fixes - -* Rebuild dataclass fields before schema generation by [@Viicos](https://github.com/Viicos) in [#11949](https://github.com/pydantic/pydantic/pull/11949) -* Always store the original field assignment on `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11946](https://github.com/pydantic/pydantic/pull/11946) - -## v2.11.5 (2025-05-22) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.5) - -### What's Changed - -#### Fixes - -* Check if `FieldInfo` is complete after applying type variable map by [@Viicos](https://github.com/Viicos) in [#11855](https://github.com/pydantic/pydantic/pull/11855) -* Do not delete mock validator/serializer in `model_rebuild()` by [@Viicos](https://github.com/Viicos) in [#11890](https://github.com/pydantic/pydantic/pull/11890) -* Do not duplicate metadata on model rebuild by [@Viicos](https://github.com/Viicos) in [#11902](https://github.com/pydantic/pydantic/pull/11902) - -## v2.11.4 (2025-04-29) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.4) - -### What's Changed - -#### Packaging - -* Bump `mkdocs-llmstxt` to v0.2.0 by [@Viicos](https://github.com/Viicos) in [#11725](https://github.com/pydantic/pydantic/pull/11725) - -#### Changes - -* Allow config and bases to be specified together in `create_model()` by [@Viicos](https://github.com/Viicos) in [#11714](https://github.com/pydantic/pydantic/pull/11714). - This change was backported as it was previously possible (although not meant to be supported) - to provide `model_config` as a field, which would make it possible to provide both configuration - and bases. - -#### Fixes - -* Remove generics cache workaround by [@Viicos](https://github.com/Viicos) in [#11755](https://github.com/pydantic/pydantic/pull/11755) -* Remove coercion of decimal constraints by [@Viicos](https://github.com/Viicos) in [#11772](https://github.com/pydantic/pydantic/pull/11772) -* Fix crash when expanding root type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11735](https://github.com/pydantic/pydantic/pull/11735) -* Fix issue with recursive generic models by [@Viicos](https://github.com/Viicos) in [#11775](https://github.com/pydantic/pydantic/pull/11775) -* Traverse `function-before` schemas during schema gathering by [@Viicos](https://github.com/Viicos) in [#11801](https://github.com/pydantic/pydantic/pull/11801) - -## v2.11.3 (2025-04-08) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.3) - -### What's Changed - -#### Packaging - -* Update V1 copy to v1.10.21 by [@Viicos](https://github.com/Viicos) in [#11706](https://github.com/pydantic/pydantic/pull/11706) - -#### Fixes - -* Preserve field description when rebuilding model fields by [@Viicos](https://github.com/Viicos) in [#11698](https://github.com/pydantic/pydantic/pull/11698) - -## v2.11.2 (2025-04-03) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.2) - -### What's Changed - -#### Fixes - -* Bump `pydantic-core` to v2.33.1 by [@Viicos](https://github.com/Viicos) in [#11678](https://github.com/pydantic/pydantic/pull/11678) -* Make sure `__pydantic_private__` exists before setting private attributes by [@Viicos](https://github.com/Viicos) in [#11666](https://github.com/pydantic/pydantic/pull/11666) -* Do not override `FieldInfo._complete` when using field from parent class by [@Viicos](https://github.com/Viicos) in [#11668](https://github.com/pydantic/pydantic/pull/11668) -* Provide the available definitions when applying discriminated unions by [@Viicos](https://github.com/Viicos) in [#11670](https://github.com/pydantic/pydantic/pull/11670) -* Do not expand root type in the mypy plugin for variables by [@Viicos](https://github.com/Viicos) in [#11676](https://github.com/pydantic/pydantic/pull/11676) -* Mention the attribute name in model fields deprecation message by [@Viicos](https://github.com/Viicos) in [#11674](https://github.com/pydantic/pydantic/pull/11674) -* Properly validate parameterized mappings by [@Viicos](https://github.com/Viicos) in [#11658](https://github.com/pydantic/pydantic/pull/11658) - -## v2.11.1 (2025-03-28) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.1) - -### What's Changed - -#### Fixes - -* Do not override `'definitions-ref'` schemas containing serialization schemas or metadata by [@Viicos](https://github.com/Viicos) in [#11644](https://github.com/pydantic/pydantic/pull/11644) - -## v2.11.0 (2025-03-27) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0) - -### What's Changed - -Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). -See the [blog post](https://pydantic.dev/articles/pydantic-v2-11-release) for more details. - -#### Packaging - -* Bump `pydantic-core` to v2.33.0 by [@Viicos](https://github.com/Viicos) in [#11631](https://github.com/pydantic/pydantic/pull/11631) - -#### New Features - -* Add `encoded_string()` method to the URL types by [@YassinNouh21](https://github.com/YassinNouh21) in [#11580](https://github.com/pydantic/pydantic/pull/11580) -* Add support for `defer_build` with `@validate_call` decorator by [@Viicos](https://github.com/Viicos) in [#11584](https://github.com/pydantic/pydantic/pull/11584) -* Allow `@with_config` decorator to be used with keyword arguments by [@Viicos](https://github.com/Viicos) in [#11608](https://github.com/pydantic/pydantic/pull/11608) -* Simplify customization of default value inclusion in JSON Schema generation by [@Viicos](https://github.com/Viicos) in [#11634](https://github.com/pydantic/pydantic/pull/11634) -* Add `generate_arguments_schema()` function by [@Viicos](https://github.com/Viicos) in [#11572](https://github.com/pydantic/pydantic/pull/11572) - -#### Fixes - -* Allow generic typed dictionaries to be used for unpacked variadic keyword parameters by [@Viicos](https://github.com/Viicos) in [#11571](https://github.com/pydantic/pydantic/pull/11571) -* Fix runtime error when computing model string representation involving cached properties and self-referenced models by [@Viicos](https://github.com/Viicos) in [#11579](https://github.com/pydantic/pydantic/pull/11579) -* Preserve other steps when using the ellipsis in the pipeline API by [@Viicos](https://github.com/Viicos) in [#11626](https://github.com/pydantic/pydantic/pull/11626) -* Fix deferred discriminator application logic by [@Viicos](https://github.com/Viicos) in [#11591](https://github.com/pydantic/pydantic/pull/11591) - -### New Contributors - -* [@cmenon12](https://github.com/cmenon12) made their first contribution in [#11562](https://github.com/pydantic/pydantic/pull/11562) -* [@Jeukoh](https://github.com/Jeukoh) made their first contribution in [#11611](https://github.com/pydantic/pydantic/pull/11611) - -## v2.11.0b2 (2025-03-17) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0b2) - -### What's Changed - -#### Packaging - -* Bump `pydantic-core` to v2.32.0 by [@Viicos](https://github.com/Viicos) in [#11567](https://github.com/pydantic/pydantic/pull/11567) - -#### New Features - -* Add experimental support for free threading by [@Viicos](https://github.com/Viicos) in [#11516](https://github.com/pydantic/pydantic/pull/11516) - -#### Fixes - -* Fix `NotRequired` qualifier not taken into account in stringified annotation by [@Viicos](https://github.com/Viicos) in [#11559](https://github.com/pydantic/pydantic/pull/11559) - -### New Contributors - -* [@joren485](https://github.com/joren485) made their first contribution in [#11547](https://github.com/pydantic/pydantic/pull/11547) - -## v2.11.0b1 (2025-03-06) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0b1) - -### What's Changed - -#### Packaging - -* Add a `check_pydantic_core_version()` function by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11324 -* Remove `greenlet` development dependency by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11351 -* Use the `typing-inspection` library by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11479 -* Bump `pydantic-core` to `v2.31.1` by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11526 - -#### New Features - -* Support unsubstituted type variables with both a default and a bound or constraints by [@FyZzyss](https://github.com/FyZzyss) in https://github.com/pydantic/pydantic/pull/10789 -* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11034 -* Raise a better error when a generic alias is used inside `type[]` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11088 -* Properly support PEP 695 generics syntax by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11189 -* Properly support type variable defaults by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11332 -* Add support for validating v6, v7, v8 UUIDs by [@astei](https://github.com/astei) in https://github.com/pydantic/pydantic/pull/11436 -* Improve alias configuration APIs by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11468 - -#### Changes - -* Rework `create_model` field definitions format by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11032 -* Raise a deprecation warning when a field is annotated as final with a default value by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11168 -* Deprecate accessing `model_fields` and `model_computed_fields` on instances by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11169 -* **Breaking Change:** Move core schema generation logic for path types inside the `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/10846 -* Remove Python 3.8 Support by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11258 -* Optimize calls to `get_type_ref` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10863 -* Disable `pydantic-core` core schema validation by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11271 - -#### Performance - -* Only evaluate `FieldInfo` annotations if required during schema building by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10769 -* Improve `__setattr__` performance of Pydantic models by caching setter functions by [@MarkusSintonen](https://github.com/MarkusSintonen) in https://github.com/pydantic/pydantic/pull/10868 -* Improve annotation application performance by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11186 -* Improve performance of `_typing_extra` module by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11255 -* Refactor and optimize schema cleaning logic by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11244 -* Create a single dictionary when creating a `CoreConfig` instance by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11384 -* Bump `pydantic-core` and thus use `SchemaValidator` and `SchemaSerializer` caching by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11402 -* Reuse cached core schemas for parametrized generic Pydantic models by [@MarkusSintonen](https://github.com/MarkusSintonen) in https://github.com/pydantic/pydantic/pull/11434 - -#### Fixes - -* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/10872 -* Use the correct frame when instantiating a parametrized `TypeAdapter` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10893 -* Infer final fields with a default value as class variables in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11121 -* Recursively unpack `Literal` values if using PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11114 -* Override `__subclasscheck__` on `ModelMetaclass` to avoid memory leak and performance issues by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11116 -* Remove unused `_extract_get_pydantic_json_schema()` parameter by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11155 -* Improve discriminated union error message for invalid union variants by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11161 -* Unpack PEP 695 type aliases if using the `Annotated` form by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11109 -* Add missing stacklevel in `deprecated_instance_property` warning by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11200 -* Copy `WithJsonSchema` schema to avoid sharing mutated data by [@thejcannon](https://github.com/thejcannon) in https://github.com/pydantic/pydantic/pull/11014 -* Do not cache parametrized models when in the process of parametrizing another model by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/10704 -* Add discriminated union related metadata entries to the `CoreMetadata` definition by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11216 -* Consolidate schema definitions logic in the `_Definitions` class by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11208 -* Support initializing root model fields with values of the `root` type in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11212 -* Fix various issues with dataclasses and `use_attribute_docstrings` by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11246 -* Only compute normalized decimal places if necessary in `decimal_places_validator` by [@misrasaurabh1](https://github.com/misrasaurabh1) in https://github.com/pydantic/pydantic/pull/11281 -* Add support for `validation_alias` in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11295 -* Fix JSON Schema reference collection with `"examples"` keys by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11305 -* Do not transform model serializer functions as class methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11298 -* Simplify `GenerateJsonSchema.literal_schema()` implementation by [@misrasaurabh1](https://github.com/misrasaurabh1) in https://github.com/pydantic/pydantic/pull/11321 -* Add additional allowed schemes for `ClickHouseDsn` by [@Maze21127](https://github.com/Maze21127) in https://github.com/pydantic/pydantic/pull/11319 -* Coerce decimal constraints to `Decimal` instances by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11350 -* Use the correct JSON Schema mode when handling function schemas by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11367 -* Improve exception message when encountering recursion errors during type evaluation by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11356 -* Always include `additionalProperties: True` for arbitrary dictionary schemas by [@austinyu](https://github.com/austinyu) in https://github.com/pydantic/pydantic/pull/11392 -* Expose `fallback` parameter in serialization methods by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11398 -* Fix path serialization behavior by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11416 -* Do not reuse validators and serializers during model rebuild by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11429 -* Collect model fields when rebuilding a model by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11388 -* Allow cached properties to be altered on frozen models by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11432 -* Fix tuple serialization for `Sequence` types by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11435 -* Fix: do not check for `__get_validators__` on classes where `__get_pydantic_core_schema__` is also defined by [@tlambert03](https://github.com/tlambert03) in https://github.com/pydantic/pydantic/pull/11444 -* Allow callable instances to be used as serializers by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11451 -* Improve error thrown when overriding field with a property by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11459 -* Fix JSON Schema generation with referenceable core schemas holding JSON metadata by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11475 -* Support strict specification on union member types by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11481 -* Implicitly set `validate_by_name` to `True` when `validate_by_alias` is `False` by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic/pull/11503 -* Change type of `Any` when synthesizing `BaseSettings.__init__` signature in the mypy plugin by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11497 -* Support type variable defaults referencing other type variables by [@Viicos](https://github.com/Viicos) in https://github.com/pydantic/pydantic/pull/11520 -* Fix `ValueError` on year zero by [@davidhewitt](https://github.com/davidhewitt) in https://github.com/pydantic/pydantic-core/pull/1583 -* `dataclass` `InitVar` shouldn't be required on serialization by [@sydney-runkle](https://github.com/sydney-runkle) in https://github.com/pydantic/pydantic-core/pull/1602 - -## New Contributors - -* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in https://github.com/pydantic/pydantic/pull/10789 -* [@tamird](https://github.com/tamird) made their first contribution in https://github.com/pydantic/pydantic/pull/10948 -* [@felixxm](https://github.com/felixxm) made their first contribution in https://github.com/pydantic/pydantic/pull/11077 -* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in https://github.com/pydantic/pydantic/pull/11082 -* [@Kharianne](https://github.com/Kharianne) made their first contribution in https://github.com/pydantic/pydantic/pull/11111 -* [@mdaffad](https://github.com/mdaffad) made their first contribution in https://github.com/pydantic/pydantic/pull/11177 -* [@thejcannon](https://github.com/thejcannon) made their first contribution in https://github.com/pydantic/pydantic/pull/11014 -* [@thomasfrimannkoren](https://github.com/thomasfrimannkoren) made their first contribution in https://github.com/pydantic/pydantic/pull/11251 -* [@usernameMAI](https://github.com/usernameMAI) made their first contribution in https://github.com/pydantic/pydantic/pull/11275 -* [@ananiavito](https://github.com/ananiavito) made their first contribution in https://github.com/pydantic/pydantic/pull/11302 -* [@pawamoy](https://github.com/pawamoy) made their first contribution in https://github.com/pydantic/pydantic/pull/11311 -* [@Maze21127](https://github.com/Maze21127) made their first contribution in https://github.com/pydantic/pydantic/pull/11319 -* [@kauabh](https://github.com/kauabh) made their first contribution in https://github.com/pydantic/pydantic/pull/11369 -* [@jaceklaskowski](https://github.com/jaceklaskowski) made their first contribution in https://github.com/pydantic/pydantic/pull/11353 -* [@tmpbeing](https://github.com/tmpbeing) made their first contribution in https://github.com/pydantic/pydantic/pull/11375 -* [@petyosi](https://github.com/petyosi) made their first contribution in https://github.com/pydantic/pydantic/pull/11405 -* [@austinyu](https://github.com/austinyu) made their first contribution in https://github.com/pydantic/pydantic/pull/11392 -* [@mikeedjones](https://github.com/mikeedjones) made their first contribution in https://github.com/pydantic/pydantic/pull/11402 -* [@astei](https://github.com/astei) made their first contribution in https://github.com/pydantic/pydantic/pull/11436 -* [@dsayling](https://github.com/dsayling) made their first contribution in https://github.com/pydantic/pydantic/pull/11522 -* [@sobolevn](https://github.com/sobolevn) made their first contribution in https://github.com/pydantic/pydantic-core/pull/1645 - -## v2.11.0a2 (2025-02-10) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0a2) - -### What's Changed - -Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). -This is another early alpha release, meant to collect early feedback from users having issues with core schema builds. - -#### Packaging - -* Bump `ruff` from 0.9.2 to 0.9.5 by [@Viicos](https://github.com/Viicos) in [#11407](https://github.com/pydantic/pydantic/pull/11407) -* Bump `pydantic-core` to v2.29.0 by [@mikeedjones](https://github.com/mikeedjones) in [#11402](https://github.com/pydantic/pydantic/pull/11402) -* Use locally-built rust with symbols & pgo by [@davidhewitt](https://github.com/davidhewitt) in [#11403](https://github.com/pydantic/pydantic/pull/11403) - -#### Performance - -* Create a single dictionary when creating a `CoreConfig` instance by [@sydney-runkle](https://github.com/sydney-runkle) in [#11384](https://github.com/pydantic/pydantic/pull/11384) - -#### Fixes - -* Use the correct JSON Schema mode when handling function schemas by [@Viicos](https://github.com/Viicos) in [#11367](https://github.com/pydantic/pydantic/pull/11367) -* Fix JSON Schema reference logic with `examples` keys by [@Viicos](https://github.com/Viicos) in [#11366](https://github.com/pydantic/pydantic/pull/11366) -* Improve exception message when encountering recursion errors during type evaluation by [@Viicos](https://github.com/Viicos) in [#11356](https://github.com/pydantic/pydantic/pull/11356) -* Always include `additionalProperties: True` for arbitrary dictionary schemas by [@austinyu](https://github.com/austinyu) in [#11392](https://github.com/pydantic/pydantic/pull/11392) -* Expose `fallback` parameter in serialization methods by [@Viicos](https://github.com/Viicos) in [#11398](https://github.com/pydantic/pydantic/pull/11398) -* Fix path serialization behavior by [@sydney-runkle](https://github.com/sydney-runkle) in [#11416](https://github.com/pydantic/pydantic/pull/11416) - -### New Contributors - -* [@kauabh](https://github.com/kauabh) made their first contribution in [#11369](https://github.com/pydantic/pydantic/pull/11369) -* [@jaceklaskowski](https://github.com/jaceklaskowski) made their first contribution in [#11353](https://github.com/pydantic/pydantic/pull/11353) -* [@tmpbeing](https://github.com/tmpbeing) made their first contribution in [#11375](https://github.com/pydantic/pydantic/pull/11375) -* [@petyosi](https://github.com/petyosi) made their first contribution in [#11405](https://github.com/pydantic/pydantic/pull/11405) -* [@austinyu](https://github.com/austinyu) made their first contribution in [#11392](https://github.com/pydantic/pydantic/pull/11392) -* [@mikeedjones](https://github.com/mikeedjones) made their first contribution in [#11402](https://github.com/pydantic/pydantic/pull/11402) - -## v2.11.0a1 (2025-01-30) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0a1) - -### What's Changed - -Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). -This is an early alpha release, meant to collect early feedback from users having issues with core schema builds. - -#### Packaging - -* Bump dawidd6/action-download-artifact from 6 to 7 by [@dependabot](https://github.com/dependabot) in [#11018](https://github.com/pydantic/pydantic/pull/11018) -* Re-enable memray related tests on Python 3.12+ by [@Viicos](https://github.com/Viicos) in [#11191](https://github.com/pydantic/pydantic/pull/11191) -* Bump astral-sh/setup-uv to 5 by [@dependabot](https://github.com/dependabot) in [#11205](https://github.com/pydantic/pydantic/pull/11205) -* Bump `ruff` to v0.9.0 by [@sydney-runkle](https://github.com/sydney-runkle) in [#11254](https://github.com/pydantic/pydantic/pull/11254) -* Regular `uv.lock` deps update by [@sydney-runkle](https://github.com/sydney-runkle) in [#11333](https://github.com/pydantic/pydantic/pull/11333) -* Add a `check_pydantic_core_version()` function by [@Viicos](https://github.com/Viicos) in [#11324](https://github.com/pydantic/pydantic/pull/11324) -* Remove `greenlet` development dependency by [@Viicos](https://github.com/Viicos) in [#11351](https://github.com/pydantic/pydantic/pull/11351) -* Bump `pydantic-core` to v2.28.0 by [@Viicos](https://github.com/Viicos) in [#11364](https://github.com/pydantic/pydantic/pull/11364) - -#### New Features - -* Support unsubstituted type variables with both a default and a bound or constraints by [@FyZzyss](https://github.com/FyZzyss) in [#10789](https://github.com/pydantic/pydantic/pull/10789) -* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11034](https://github.com/pydantic/pydantic/pull/11034) -* Raise a better error when a generic alias is used inside `type[]` by [@Viicos](https://github.com/Viicos) in [#11088](https://github.com/pydantic/pydantic/pull/11088) -* Properly support PEP 695 generics syntax by [@Viicos](https://github.com/Viicos) in [#11189](https://github.com/pydantic/pydantic/pull/11189) -* Properly support type variable defaults by [@Viicos](https://github.com/Viicos) in [#11332](https://github.com/pydantic/pydantic/pull/11332) - -#### Changes - -* Rework `create_model` field definitions format by [@Viicos](https://github.com/Viicos) in [#11032](https://github.com/pydantic/pydantic/pull/11032) -* Raise a deprecation warning when a field is annotated as final with a default value by [@Viicos](https://github.com/Viicos) in [#11168](https://github.com/pydantic/pydantic/pull/11168) -* Deprecate accessing `model_fields` and `model_computed_fields` on instances by [@Viicos](https://github.com/Viicos) in [#11169](https://github.com/pydantic/pydantic/pull/11169) -* Move core schema generation logic for path types inside the `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in [#10846](https://github.com/pydantic/pydantic/pull/10846) -* Move `deque` schema gen to `GenerateSchema` class by [@sydney-runkle](https://github.com/sydney-runkle) in [#11239](https://github.com/pydantic/pydantic/pull/11239) -* Move `Mapping` schema gen to `GenerateSchema` to complete removal of `prepare_annotations_for_known_type` workaround by [@sydney-runkle](https://github.com/sydney-runkle) in [#11247](https://github.com/pydantic/pydantic/pull/11247) -* Remove Python 3.8 Support by [@sydney-runkle](https://github.com/sydney-runkle) in [#11258](https://github.com/pydantic/pydantic/pull/11258) -* Disable `pydantic-core` core schema validation by [@sydney-runkle](https://github.com/sydney-runkle) in [#11271](https://github.com/pydantic/pydantic/pull/11271) - -#### Performance - -* Only evaluate `FieldInfo` annotations if required during schema building by [@Viicos](https://github.com/Viicos) in [#10769](https://github.com/pydantic/pydantic/pull/10769) -* Optimize calls to `get_type_ref` by [@Viicos](https://github.com/Viicos) in [#10863](https://github.com/pydantic/pydantic/pull/10863) -* Improve `__setattr__` performance of Pydantic models by caching setter functions by [@MarkusSintonen](https://github.com/MarkusSintonen) in [#10868](https://github.com/pydantic/pydantic/pull/10868) -* Improve annotation application performance by [@Viicos](https://github.com/Viicos) in [#11186](https://github.com/pydantic/pydantic/pull/11186) -* Improve performance of `_typing_extra` module by [@Viicos](https://github.com/Viicos) in [#11255](https://github.com/pydantic/pydantic/pull/11255) -* Refactor and optimize schema cleaning logic by [@Viicos](https://github.com/Viicos) and [@MarkusSintonen](https://github.com/MarkusSintonen) in [#11244](https://github.com/pydantic/pydantic/pull/11244) - -#### Fixes - -* Add validation tests for `_internal/_validators.py` by [@tkasuz](https://github.com/tkasuz) in [#10763](https://github.com/pydantic/pydantic/pull/10763) -* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in [#10872](https://github.com/pydantic/pydantic/pull/10872) -* Revert "ci: use locally built pydantic-core with debug symbols by [@sydney-runkle](https://github.com/sydney-runkle) in [#10942](https://github.com/pydantic/pydantic/pull/10942) -* Re-enable all FastAPI tests by [@tamird](https://github.com/tamird) in [#10948](https://github.com/pydantic/pydantic/pull/10948) -* Fix typo in HISTORY.md. by [@felixxm](https://github.com/felixxm) in [#11077](https://github.com/pydantic/pydantic/pull/11077) -* Infer final fields with a default value as class variables in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11121](https://github.com/pydantic/pydantic/pull/11121) -* Recursively unpack `Literal` values if using PEP 695 type aliases by [@Viicos](https://github.com/Viicos) in [#11114](https://github.com/pydantic/pydantic/pull/11114) -* Override `__subclasscheck__` on `ModelMetaclass` to avoid memory leak and performance issues by [@Viicos](https://github.com/Viicos) in [#11116](https://github.com/pydantic/pydantic/pull/11116) -* Remove unused `_extract_get_pydantic_json_schema()` parameter by [@Viicos](https://github.com/Viicos) in [#11155](https://github.com/pydantic/pydantic/pull/11155) -* Add FastAPI and SQLModel to third-party tests by [@sydney-runkle](https://github.com/sydney-runkle) in [#11044](https://github.com/pydantic/pydantic/pull/11044) -* Fix conditional expressions syntax for third-party tests by [@Viicos](https://github.com/Viicos) in [#11162](https://github.com/pydantic/pydantic/pull/11162) -* Move FastAPI tests to third-party workflow by [@Viicos](https://github.com/Viicos) in [#11164](https://github.com/pydantic/pydantic/pull/11164) -* Improve discriminated union error message for invalid union variants by [@Viicos](https://github.com/Viicos) in [#11161](https://github.com/pydantic/pydantic/pull/11161) -* Unpack PEP 695 type aliases if using the `Annotated` form by [@Viicos](https://github.com/Viicos) in [#11109](https://github.com/pydantic/pydantic/pull/11109) -* Include `openapi-python-client` check in issue creation for third-party failures, use `main` branch by [@sydney-runkle](https://github.com/sydney-runkle) in [#11182](https://github.com/pydantic/pydantic/pull/11182) -* Add pandera third-party tests by [@Viicos](https://github.com/Viicos) in [#11193](https://github.com/pydantic/pydantic/pull/11193) -* Add ODMantic third-party tests by [@sydney-runkle](https://github.com/sydney-runkle) in [#11197](https://github.com/pydantic/pydantic/pull/11197) -* Add missing stacklevel in `deprecated_instance_property` warning by [@Viicos](https://github.com/Viicos) in [#11200](https://github.com/pydantic/pydantic/pull/11200) -* Copy `WithJsonSchema` schema to avoid sharing mutated data by [@thejcannon](https://github.com/thejcannon) in [#11014](https://github.com/pydantic/pydantic/pull/11014) -* Do not cache parametrized models when in the process of parametrizing another model by [@Viicos](https://github.com/Viicos) in [#10704](https://github.com/pydantic/pydantic/pull/10704) -* Re-enable Beanie third-party tests by [@Viicos](https://github.com/Viicos) in [#11214](https://github.com/pydantic/pydantic/pull/11214) -* Add discriminated union related metadata entries to the `CoreMetadata` definition by [@Viicos](https://github.com/Viicos) in [#11216](https://github.com/pydantic/pydantic/pull/11216) -* Consolidate schema definitions logic in the `_Definitions` class by [@Viicos](https://github.com/Viicos) in [#11208](https://github.com/pydantic/pydantic/pull/11208) -* Support initializing root model fields with values of the `root` type in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11212](https://github.com/pydantic/pydantic/pull/11212) -* Fix various issues with dataclasses and `use_attribute_docstrings` by [@Viicos](https://github.com/Viicos) in [#11246](https://github.com/pydantic/pydantic/pull/11246) -* Only compute normalized decimal places if necessary in `decimal_places_validator` by [@misrasaurabh1](https://github.com/misrasaurabh1) in [#11281](https://github.com/pydantic/pydantic/pull/11281) -* Fix two misplaced sentences in validation errors documentation by [@ananiavito](https://github.com/ananiavito) in [#11302](https://github.com/pydantic/pydantic/pull/11302) -* Fix mkdocstrings inventory example in documentation by [@pawamoy](https://github.com/pawamoy) in [#11311](https://github.com/pydantic/pydantic/pull/11311) -* Add support for `validation_alias` in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11295](https://github.com/pydantic/pydantic/pull/11295) -* Do not transform model serializer functions as class methods in the mypy plugin by [@Viicos](https://github.com/Viicos) in [#11298](https://github.com/pydantic/pydantic/pull/11298) -* Simplify `GenerateJsonSchema.literal_schema()` implementation by [@misrasaurabh1](https://github.com/misrasaurabh1) in [#11321](https://github.com/pydantic/pydantic/pull/11321) -* Add additional allowed schemes for `ClickHouseDsn` by [@Maze21127](https://github.com/Maze21127) in [#11319](https://github.com/pydantic/pydantic/pull/11319) -* Coerce decimal constraints to `Decimal` instances by [@Viicos](https://github.com/Viicos) in [#11350](https://github.com/pydantic/pydantic/pull/11350) -* Fix `ValueError` on year zero by [@davidhewitt](https://github.com/davidhewitt) in [pydantic-core#1583](https://github.com/pydantic/pydantic-core/pull/1583) - -### New Contributors - -* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in [#10789](https://github.com/pydantic/pydantic/pull/10789) -* [@tamird](https://github.com/tamird) made their first contribution in [#10948](https://github.com/pydantic/pydantic/pull/10948) -* [@felixxm](https://github.com/felixxm) made their first contribution in [#11077](https://github.com/pydantic/pydantic/pull/11077) -* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in [#11082](https://github.com/pydantic/pydantic/pull/11082) -* [@Kharianne](https://github.com/Kharianne) made their first contribution in [#11111](https://github.com/pydantic/pydantic/pull/11111) -* [@mdaffad](https://github.com/mdaffad) made their first contribution in [#11177](https://github.com/pydantic/pydantic/pull/11177) -* [@thejcannon](https://github.com/thejcannon) made their first contribution in [#11014](https://github.com/pydantic/pydantic/pull/11014) -* [@thomasfrimannkoren](https://github.com/thomasfrimannkoren) made their first contribution in [#11251](https://github.com/pydantic/pydantic/pull/11251) -* [@usernameMAI](https://github.com/usernameMAI) made their first contribution in [#11275](https://github.com/pydantic/pydantic/pull/11275) -* [@ananiavito](https://github.com/ananiavito) made their first contribution in [#11302](https://github.com/pydantic/pydantic/pull/11302) -* [@pawamoy](https://github.com/pawamoy) made their first contribution in [#11311](https://github.com/pydantic/pydantic/pull/11311) -* [@Maze21127](https://github.com/Maze21127) made their first contribution in [#11319](https://github.com/pydantic/pydantic/pull/11319) - -## v2.10.6 (2025-01-23) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.6) - -### What's Changed - -#### Fixes - -* Fix JSON Schema reference collection with `'examples'` keys by [@Viicos](https://github.com/Viicos) in [#11325](https://github.com/pydantic/pydantic/pull/11325) -* Fix url python serialization by [@sydney-runkle](https://github.com/sydney-runkle) in [#11331](https://github.com/pydantic/pydantic/pull/11331) - -## v2.10.5 (2025-01-08) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.5) - -### What's Changed - -#### Fixes - -* Remove custom MRO implementation of Pydantic models by [@Viicos](https://github.com/Viicos) in [#11184](https://github.com/pydantic/pydantic/pull/11184) -* Fix URL serialization for unions by [@sydney-runkle](https://github.com/sydney-runkle) in [#11233](https://github.com/pydantic/pydantic/pull/11233) - -## v2.10.4 (2024-12-18) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.4) - -### What's Changed - -#### Packaging - -* Bump `pydantic-core` to v2.27.2 by [@davidhewitt](https://github.com/davidhewitt) in [#11138](https://github.com/pydantic/pydantic/pull/11138) - -#### Fixes - -* Fix for comparison of `AnyUrl` objects by [@alexprabhat99](https://github.com/alexprabhat99) in [#11082](https://github.com/pydantic/pydantic/pull/11082) -* Properly fetch PEP 695 type params for functions, do not fetch annotations from signature by [@Viicos](https://github.com/Viicos) in [#11093](https://github.com/pydantic/pydantic/pull/11093) -* Include JSON Schema input core schema in function schemas by [@Viicos](https://github.com/Viicos) in [#11085](https://github.com/pydantic/pydantic/pull/11085) -* Add `len` to `_BaseUrl` to avoid TypeError by [@Kharianne](https://github.com/Kharianne) in [#11111](https://github.com/pydantic/pydantic/pull/11111) -* Make sure the type reference is removed from the seen references by [@Viicos](https://github.com/Viicos) in [#11143](https://github.com/pydantic/pydantic/pull/11143) - -### New Contributors - -* [@FyZzyss](https://github.com/FyZzyss) made their first contribution in [#10789](https://github.com/pydantic/pydantic/pull/10789) -* [@tamird](https://github.com/tamird) made their first contribution in [#10948](https://github.com/pydantic/pydantic/pull/10948) -* [@felixxm](https://github.com/felixxm) made their first contribution in [#11077](https://github.com/pydantic/pydantic/pull/11077) -* [@alexprabhat99](https://github.com/alexprabhat99) made their first contribution in [#11082](https://github.com/pydantic/pydantic/pull/11082) -* [@Kharianne](https://github.com/Kharianne) made their first contribution in [#11111](https://github.com/pydantic/pydantic/pull/11111) - -## v2.10.3 (2024-12-03) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.3) - -### What's Changed - -#### Fixes - -* Set fields when `defer_build` is set on Pydantic dataclasses by [@Viicos](https://github.com/Viicos) in [#10984](https://github.com/pydantic/pydantic/pull/10984) -* Do not resolve the JSON Schema reference for `dict` core schema keys by [@Viicos](https://github.com/Viicos) in [#10989](https://github.com/pydantic/pydantic/pull/10989) -* Use the globals of the function when evaluating the return type for `PlainSerializer` and `WrapSerializer` functions by [@Viicos](https://github.com/Viicos) in [#11008](https://github.com/pydantic/pydantic/pull/11008) -* Fix host required enforcement for urls to be compatible with v2.9 behavior by [@sydney-runkle](https://github.com/sydney-runkle) in [#11027](https://github.com/pydantic/pydantic/pull/11027) -* Add a `default_factory_takes_validated_data` property to `FieldInfo` by [@Viicos](https://github.com/Viicos) in [#11034](https://github.com/pydantic/pydantic/pull/11034) -* Fix url json schema in `serialization` mode by [@sydney-runkle](https://github.com/sydney-runkle) in [#11035](https://github.com/pydantic/pydantic/pull/11035) - -## v2.10.2 (2024-11-25) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.2) - -### What's Changed - -#### Fixes - -* Only evaluate FieldInfo annotations if required during schema building by [@Viicos](https://github.com/Viicos) in [#10769](https://github.com/pydantic/pydantic/pull/10769) -* Do not evaluate annotations for private fields by [@Viicos](https://github.com/Viicos) in [#10962](https://github.com/pydantic/pydantic/pull/10962) -* Support serialization as any for `Secret` types and `Url` types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10947](https://github.com/pydantic/pydantic/pull/10947) -* Fix type hint of `Field.default` to be compatible with Python 3.8 and 3.9 by [@Viicos](https://github.com/Viicos) in [#10972](https://github.com/pydantic/pydantic/pull/10972) -* Add hashing support for URL types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10975](https://github.com/pydantic/pydantic/pull/10975) -* Hide `BaseModel.__replace__` definition from type checkers by [@Viicos](https://github.com/Viicos) in [#10979](https://github.com/pydantic/pydantic/pull/10979) - -## v2.10.1 (2024-11-21) - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.1) - -### What's Changed - -#### Packaging - -* Bump `pydantic-core` version to `v2.27.1` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10938](https://github.com/pydantic/pydantic/pull/10938) - -#### Fixes - -* Use the correct frame when instantiating a parametrized `TypeAdapter` by [@Viicos](https://github.com/Viicos) in [#10893](https://github.com/pydantic/pydantic/pull/10893) -* Relax check for validated data in `default_factory` utils by [@sydney-runkle](https://github.com/sydney-runkle) in [#10909](https://github.com/pydantic/pydantic/pull/10909) -* Fix type checking issue with `model_fields` and `model_computed_fields` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10911](https://github.com/pydantic/pydantic/pull/10911) -* Use the parent configuration during schema generation for stdlib `dataclass`es by [@sydney-runkle](https://github.com/sydney-runkle) in [#10928](https://github.com/pydantic/pydantic/pull/10928) -* Use the `globals` of the function when evaluating the return type of serializers and `computed_field`s by [@Viicos](https://github.com/Viicos) in [#10929](https://github.com/pydantic/pydantic/pull/10929) -* Fix URL constraint application by [@sydney-runkle](https://github.com/sydney-runkle) in [#10922](https://github.com/pydantic/pydantic/pull/10922) -* Fix URL equality with different validation methods by [@sydney-runkle](https://github.com/sydney-runkle) in [#10934](https://github.com/pydantic/pydantic/pull/10934) -* Fix JSON schema title when specified as `''` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10936](https://github.com/pydantic/pydantic/pull/10936) -* Fix `python` mode serialization for `complex` inference by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic-core#1549](https://github.com/pydantic/pydantic-core/pull/1549) - -### New Contributors - -## v2.10.0 (2024-11-20) - -The code released in v2.10.0 is practically identical to that of v2.10.0b2. - -[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0) - -See the [v2.10 release blog post](https://pydantic.dev/articles/pydantic-v2-10-release) for the highlights! - -### What's Changed - -#### Packaging - -* Bump `pydantic-core` to `v2.27.0` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10825](https://github.com/pydantic/pydantic/pull/10825) -* Replaced pdm with uv by [@frfahim](https://github.com/frfahim) in [#10727](https://github.com/pydantic/pydantic/pull/10727) - -#### New Features - -* Support `fractions.Fraction` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10318](https://github.com/pydantic/pydantic/pull/10318) -* Support `Hashable` for json validation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10324](https://github.com/pydantic/pydantic/pull/10324) -* Add a `SocketPath` type for `linux` systems by [@theunkn0wn1](https://github.com/theunkn0wn1) in [#10378](https://github.com/pydantic/pydantic/pull/10378) -* Allow arbitrary refs in JSON schema `examples` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10417](https://github.com/pydantic/pydantic/pull/10417) -* Support `defer_build` for Pydantic dataclasses by [@Viicos](https://github.com/Viicos) in [#10313](https://github.com/pydantic/pydantic/pull/10313) -* Adding v1 / v2 incompatibility warning for nested v1 model by [@sydney-runkle](https://github.com/sydney-runkle) in [#10431](https://github.com/pydantic/pydantic/pull/10431) -* Add support for unpacked `TypedDict` to type hint variadic keyword arguments with `@validate_call` by [@Viicos](https://github.com/Viicos) in [#10416](https://github.com/pydantic/pydantic/pull/10416) -* Support compiled patterns in `protected_namespaces` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10522](https://github.com/pydantic/pydantic/pull/10522) -* Add support for `propertyNames` in JSON schema by [@FlorianSW](https://github.com/FlorianSW) in [#10478](https://github.com/pydantic/pydantic/pull/10478) -* Adding `__replace__` protocol for Python 3.13+ support by [@sydney-runkle](https://github.com/sydney-runkle) in [#10596](https://github.com/pydantic/pydantic/pull/10596) -* Expose public `sort` method for JSON schema generation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10595](https://github.com/pydantic/pydantic/pull/10595) -* Add runtime validation of `@validate_call` callable argument by [@kc0506](https://github.com/kc0506) in [#10627](https://github.com/pydantic/pydantic/pull/10627) -* Add `experimental_allow_partial` support by [@samuelcolvin](https://github.com/samuelcolvin) in [#10748](https://github.com/pydantic/pydantic/pull/10748) -* Support default factories taking validated data as an argument by [@Viicos](https://github.com/Viicos) in [#10678](https://github.com/pydantic/pydantic/pull/10678) -* Allow subclassing `ValidationError` and `PydanticCustomError` by [@Youssefares](https://github.com/Youssefares) in [pydantic/pydantic-core#1413](https://github.com/pydantic/pydantic-core/pull/1413) -* Add `trailing-strings` support to `experimental_allow_partial` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10825](https://github.com/pydantic/pydantic/pull/10825) -* Add `rebuild()` method for `TypeAdapter` and simplify `defer_build` patterns by [@sydney-runkle](https://github.com/sydney-runkle) in [#10537](https://github.com/pydantic/pydantic/pull/10537) -* Improve `TypeAdapter` instance repr by [@sydney-runkle](https://github.com/sydney-runkle) in [#10872](https://github.com/pydantic/pydantic/pull/10872) - -#### Changes - -* Don't allow customization of `SchemaGenerator` until interface is more stable by [@sydney-runkle](https://github.com/sydney-runkle) in [#10303](https://github.com/pydantic/pydantic/pull/10303) -* Cleanly `defer_build` on `TypeAdapters`, removing experimental flag by [@sydney-runkle](https://github.com/sydney-runkle) in [#10329](https://github.com/pydantic/pydantic/pull/10329) -* Fix `mro` of generic subclass by [@kc0506](https://github.com/kc0506) in [#10100](https://github.com/pydantic/pydantic/pull/10100) -* Strip whitespaces on JSON Schema title generation by [@sydney-runkle](https://github.com/sydney-runkle) in [#10404](https://github.com/pydantic/pydantic/pull/10404) -* Use `b64decode` and `b64encode` for `Base64Bytes` type by [@sydney-runkle](https://github.com/sydney-runkle) in [#10486](https://github.com/pydantic/pydantic/pull/10486) -* Relax protected namespace config default by [@sydney-runkle](https://github.com/sydney-runkle) in [#10441](https://github.com/pydantic/pydantic/pull/10441) -* Revalidate parametrized generics if instance's origin is subclass of OG class by [@sydney-runkle](https://github.com/sydney-runkle) in [#10666](https://github.com/pydantic/pydantic/pull/10666) -* Warn if configuration is specified on the `@dataclass` decorator and with the `__pydantic_config__` attribute by [@sydney-runkle](https://github.com/sydney-runkle) in [#10406](https://github.com/pydantic/pydantic/pull/10406) -* Recommend against using `Ellipsis` (...) with `Field` by [@Viicos](https://github.com/Viicos) in [#10661](https://github.com/pydantic/pydantic/pull/10661) -* Migrate to subclassing instead of annotated approach for pydantic url types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10662](https://github.com/pydantic/pydantic/pull/10662) -* Change JSON schema generation of `Literal`s and `Enums` by [@Viicos](https://github.com/Viicos) in [#10692](https://github.com/pydantic/pydantic/pull/10692) -* Simplify unions involving `Any` or `Never` when replacing type variables by [@Viicos](https://github.com/Viicos) in [#10338](https://github.com/pydantic/pydantic/pull/10338) -* Do not require padding when decoding `base64` bytes by [@bschoenmaeckers](https://github.com/bschoenmaeckers) in [pydantic/pydantic-core#1448](https://github.com/pydantic/pydantic-core/pull/1448) -* Support dates all the way to 1BC by [@changhc](https://github.com/changhc) in [pydantic/speedate#77](https://github.com/pydantic/speedate/pull/77) - -#### Performance - -* Schema cleaning: skip unnecessary copies during schema walking by [@Viicos](https://github.com/Viicos) in [#10286](https://github.com/pydantic/pydantic/pull/10286) -* Refactor namespace logic for annotations evaluation by [@Viicos](https://github.com/Viicos) in [#10530](https://github.com/pydantic/pydantic/pull/10530) -* Improve email regexp on edge cases by [@AlekseyLobanov](https://github.com/AlekseyLobanov) in [#10601](https://github.com/pydantic/pydantic/pull/10601) -* `CoreMetadata` refactor with an emphasis on documentation, schema build time performance, and reducing complexity by [@sydney-runkle](https://github.com/sydney-runkle) in [#10675](https://github.com/pydantic/pydantic/pull/10675) - -#### Fixes - -* Remove guarding check on `computed_field` with `field_serializer` by [@nix010](https://github.com/nix010) in [#10390](https://github.com/pydantic/pydantic/pull/10390) -* Fix `Predicate` issue in `v2.9.0` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10321](https://github.com/pydantic/pydantic/pull/10321) -* Fixing `annotated-types` bound by [@sydney-runkle](https://github.com/sydney-runkle) in [#10327](https://github.com/pydantic/pydantic/pull/10327) -* Turn `tzdata` install requirement into optional `timezone` dependency by [@jakob-keller](https://github.com/jakob-keller) in [#10331](https://github.com/pydantic/pydantic/pull/10331) -* Use correct types namespace when building `namedtuple` core schemas by [@Viicos](https://github.com/Viicos) in [#10337](https://github.com/pydantic/pydantic/pull/10337) -* Fix evaluation of stringified annotations during namespace inspection by [@Viicos](https://github.com/Viicos) in [#10347](https://github.com/pydantic/pydantic/pull/10347) -* Fix `IncEx` type alias definition by [@Viicos](https://github.com/Viicos) in [#10339](https://github.com/pydantic/pydantic/pull/10339) -* Do not error when trying to evaluate annotations of private attributes by [@Viicos](https://github.com/Viicos) in [#10358](https://github.com/pydantic/pydantic/pull/10358) -* Fix nested type statement by [@kc0506](https://github.com/kc0506) in [#10369](https://github.com/pydantic/pydantic/pull/10369) -* Improve typing of `ModelMetaclass.mro` by [@Viicos](https://github.com/Viicos) in [#10372](https://github.com/pydantic/pydantic/pull/10372) -* Fix class access of deprecated `computed_field`s by [@Viicos](https://github.com/Viicos) in [#10391](https://github.com/pydantic/pydantic/pull/10391) -* Make sure `inspect.iscoroutinefunction` works on coroutines decorated with `@validate_call` by [@MovisLi](https://github.com/MovisLi) in [#10374](https://github.com/pydantic/pydantic/pull/10374) -* Fix `NameError` when using `validate_call` with PEP 695 on a class by [@kc0506](https://github.com/kc0506) in [#10380](https://github.com/pydantic/pydantic/pull/10380) -* Fix `ZoneInfo` with various invalid types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10408](https://github.com/pydantic/pydantic/pull/10408) -* Fix `PydanticUserError` on empty `model_config` with annotations by [@cdwilson](https://github.com/cdwilson) in [#10412](https://github.com/pydantic/pydantic/pull/10412) -* Fix variance issue in `_IncEx` type alias, only allow `True` by [@Viicos](https://github.com/Viicos) in [#10414](https://github.com/pydantic/pydantic/pull/10414) -* Fix serialization schema generation when using `PlainValidator` by [@Viicos](https://github.com/Viicos) in [#10427](https://github.com/pydantic/pydantic/pull/10427) -* Fix schema generation error when serialization schema holds references by [@Viicos](https://github.com/Viicos) in [#10444](https://github.com/pydantic/pydantic/pull/10444) -* Inline references if possible when generating schema for `json_schema_input_type` by [@Viicos](https://github.com/Viicos) in [#10439](https://github.com/pydantic/pydantic/pull/10439) -* Fix recursive arguments in `Representation` by [@Viicos](https://github.com/Viicos) in [#10480](https://github.com/pydantic/pydantic/pull/10480) -* Fix representation for builtin function types by [@kschwab](https://github.com/kschwab) in [#10479](https://github.com/pydantic/pydantic/pull/10479) -* Add python validators for decimal constraints (`max_digits` and `decimal_places`) by [@sydney-runkle](https://github.com/sydney-runkle) in [#10506](https://github.com/pydantic/pydantic/pull/10506) -* Only fetch `__pydantic_core_schema__` from the current class during schema generation by [@Viicos](https://github.com/Viicos) in [#10518](https://github.com/pydantic/pydantic/pull/10518) -* Fix `stacklevel` on deprecation warnings for `BaseModel` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10520](https://github.com/pydantic/pydantic/pull/10520) -* Fix warning `stacklevel` in `BaseModel.__init__` by [@Viicos](https://github.com/Viicos) in [#10526](https://github.com/pydantic/pydantic/pull/10526) -* Improve error handling for in-evaluable refs for discriminator application by [@sydney-runkle](https://github.com/sydney-runkle) in [#10440](https://github.com/pydantic/pydantic/pull/10440) -* Change the signature of `ConfigWrapper.core_config` to take the title directly by [@Viicos](https://github.com/Viicos) in [#10562](https://github.com/pydantic/pydantic/pull/10562) -* Do not use the previous config from the stack for dataclasses without config by [@Viicos](https://github.com/Viicos) in [#10576](https://github.com/pydantic/pydantic/pull/10576) -* Fix serialization for IP types with `mode='python'` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10594](https://github.com/pydantic/pydantic/pull/10594) -* Support constraint application for `Base64Etc` types by [@sydney-runkle](https://github.com/sydney-runkle) in [#10584](https://github.com/pydantic/pydantic/pull/10584) -* Fix `validate_call` ignoring `Field` in `Annotated` by [@kc0506](https://github.com/kc0506) in [#10610](https://github.com/pydantic/pydantic/pull/10610) -* Raise an error when `Self` is invalid by [@kc0506](https://github.com/kc0506) in [#10609](https://github.com/pydantic/pydantic/pull/10609) -* Using `core_schema.InvalidSchema` instead of metadata injection + checks by [@sydney-runkle](https://github.com/sydney-runkle) in [#10523](https://github.com/pydantic/pydantic/pull/10523) -* Tweak type alias logic by [@kc0506](https://github.com/kc0506) in [#10643](https://github.com/pydantic/pydantic/pull/10643) -* Support usage of `type` with `typing.Self` and type aliases by [@kc0506](https://github.com/kc0506) in [#10621](https://github.com/pydantic/pydantic/pull/10621) -* Use overloads for `Field` and `PrivateAttr` functions by [@Viicos](https://github.com/Viicos) in [#10651](https://github.com/pydantic/pydantic/pull/10651) -* Clean up the `mypy` plugin implementation by [@Viicos](https://github.com/Viicos) in [#10669](https://github.com/pydantic/pydantic/pull/10669) -* Properly check for `typing_extensions` variant of `TypeAliasType` by [@Daraan](https://github.com/Daraan) in [#10713](https://github.com/pydantic/pydantic/pull/10713) -* Allow any mapping in `BaseModel.model_copy()` by [@Viicos](https://github.com/Viicos) in [#10751](https://github.com/pydantic/pydantic/pull/10751) -* Fix `isinstance` behavior for urls by [@sydney-runkle](https://github.com/sydney-runkle) in [#10766](https://github.com/pydantic/pydantic/pull/10766) -* Ensure `cached_property` can be set on Pydantic models by [@Viicos](https://github.com/Viicos) in [#10774](https://github.com/pydantic/pydantic/pull/10774) -* Fix equality checks for primitives in literals by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1459](https://github.com/pydantic/pydantic-core/pull/1459) -* Properly enforce `host_required` for URLs by [@Viicos](https://github.com/Viicos) in [pydantic/pydantic-core#1488](https://github.com/pydantic/pydantic-core/pull/1488) -* Fix when `coerce_numbers_to_str` enabled and string has invalid Unicode character by [@andrey-berenda](https://github.com/andrey-berenda) in [pydantic/pydantic-core#1515](https://github.com/pydantic/pydantic-core/pull/1515) -* Fix serializing `complex` values in `Enum`s by [@changhc](https://github.com/changhc) in [pydantic/pydantic-core#1524](https://github.com/pydantic/pydantic-core/pull/1524) -* Refactor `_typing_extra` module by [@Viicos](https://github.com/Viicos) in [#10725](https://github.com/pydantic/pydantic/pull/10725) -* Support intuitive equality for urls by [@sydney-runkle](https://github.com/sydney-runkle) in [#10798](https://github.com/pydantic/pydantic/pull/10798) -* Add `bytearray` to `TypeAdapter.validate_json` signature by [@samuelcolvin](https://github.com/samuelcolvin) in [#10802](https://github.com/pydantic/pydantic/pull/10802) -* Ensure class access of method descriptors is performed when used as a default with `Field` by [@Viicos](https://github.com/Viicos) in [#10816](https://github.com/pydantic/pydantic/pull/10816) -* Fix circular import with `validate_call` by [@sydney-runkle](https://github.com/sydney-runkle) in [#10807](https://github.com/pydantic/pydantic/pull/10807) -* Fix error when using type aliases referencing other type aliases by [@Viicos](https://github.com/Viicos) in [#10809](https://github.com/pydantic/pydantic/pull/10809) -* Fix `IncEx` type alias to be compatible with mypy by [@Viicos](https://github.com/Viicos) in [#10813](https://github.com/pydantic/pydantic/pull/10813) -* Make `__signature__` a lazy property, do not deepcopy defaults by [@Viicos](https://github.com/Viicos) in [#10818](https://github.com/pydantic/pydantic/pull/10818) -* Make `__signature__` lazy for dataclasses, too by [@sydney-runkle](https://github.com/sydney-runkle) in [#10832](https://github.com/pydantic/pydantic/pull/10832) -* Subclass all single host url classes from `AnyUrl` to preserve behavior from v2.9 by [@sydney-runkle](https://github.com/sydney-runkle) in [#10856](https://github.com/pydantic/pydantic/pull/10856) - -### New Contributors - -* [@jakob-keller](https://github.com/jakob-keller) made their first contribution in [#10331](https://github.com/pydantic/pydantic/pull/10331) -* [@MovisLi](https://github.com/MovisLi) made their first contribution in [#10374](https://github.com/pydantic/pydantic/pull/10374) -* [@joaopalmeiro](https://github.com/joaopalmeiro) made their first contribution in [#10405](https://github.com/pydantic/pydantic/pull/10405) -* [@theunkn0wn1](https://github.com/theunkn0wn1) made their first contribution in [#10378](https://github.com/pydantic/pydantic/pull/10378) -* [@cdwilson](https://github.com/cdwilson) made their first contribution in [#10412](https://github.com/pydantic/pydantic/pull/10412) -* [@dlax](https://github.com/dlax) made their first contribution in [#10421](https://github.com/pydantic/pydantic/pull/10421) -* [@kschwab](https://github.com/kschwab) made their first contribution in [#10479](https://github.com/pydantic/pydantic/pull/10479) -* [@santibreo](https://github.com/santibreo) made their first contribution in [#10453](https://github.com/pydantic/pydantic/pull/10453) -* [@FlorianSW](https://github.com/FlorianSW) made their first contribution in [#10478](https://github.com/pydantic/pydantic/pull/10478) -* [@tkasuz](https://github.com/tkasuz) made their first contribution in [#10555](https://github.com/pydantic/pydantic/pull/10555) -* [@AlekseyLobanov](https://github.com/AlekseyLobanov) made their first contribution in [#10601](https://github.com/pydantic/pydantic/pull/10601) -* [@NiclasvanEyk](https://github.com/NiclasvanEyk) made their first contribution in [#10667](https://github.com/pydantic/pydantic/pull/10667) -* [@mschoettle](https://github.com/mschoettle) made their first contribution in [#10677](https://github.com/pydantic/pydantic/pull/10677) -* [@Daraan](https://github.com/Daraan) made their first contribution in [#10713](https://github.com/pydantic/pydantic/pull/10713) -* [@k4nar](https://github.com/k4nar) made their first contribution in [#10736](https://github.com/pydantic/pydantic/pull/10736) -* [@UriyaHarpeness](https://github.com/UriyaHarpeness) made their first contribution in [#10740](https://github.com/pydantic/pydantic/pull/10740) -* [@frfahim](https://github.com/frfahim) made their first contribution in [#10727](https://github.com/pydantic/pydantic/pull/10727) - -## v2.10.0b2 (2024-11-13) - -Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0b2) for details. - -## v2.10.0b1 (2024-11-06) - -Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.10.0b1) for details. - - -... see [here](https://docs.pydantic.dev/changelog/#v0322-2019-08-17) for earlier changes. diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/RECORD deleted file mode 100644 index 532d6f6c..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/RECORD +++ /dev/null @@ -1,217 +0,0 @@ -pydantic-2.12.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pydantic-2.12.5.dist-info/METADATA,sha256=o7oj6JUZH-1puDI8vLzcgphMoLajzcYsSKI0GIapwI0,90587 -pydantic-2.12.5.dist-info/RECORD,, -pydantic-2.12.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 -pydantic-2.12.5.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129 -pydantic/__init__.py,sha256=5iEnJ4wHv1OEzdKQPzaKaZKfO4pSQAC65ODrYI6_S8Y,15812 -pydantic/__pycache__/__init__.cpython-312.pyc,, -pydantic/__pycache__/_migration.cpython-312.pyc,, -pydantic/__pycache__/alias_generators.cpython-312.pyc,, -pydantic/__pycache__/aliases.cpython-312.pyc,, -pydantic/__pycache__/annotated_handlers.cpython-312.pyc,, -pydantic/__pycache__/class_validators.cpython-312.pyc,, -pydantic/__pycache__/color.cpython-312.pyc,, -pydantic/__pycache__/config.cpython-312.pyc,, -pydantic/__pycache__/dataclasses.cpython-312.pyc,, -pydantic/__pycache__/datetime_parse.cpython-312.pyc,, -pydantic/__pycache__/decorator.cpython-312.pyc,, -pydantic/__pycache__/env_settings.cpython-312.pyc,, -pydantic/__pycache__/error_wrappers.cpython-312.pyc,, -pydantic/__pycache__/errors.cpython-312.pyc,, -pydantic/__pycache__/fields.cpython-312.pyc,, -pydantic/__pycache__/functional_serializers.cpython-312.pyc,, -pydantic/__pycache__/functional_validators.cpython-312.pyc,, -pydantic/__pycache__/generics.cpython-312.pyc,, -pydantic/__pycache__/json.cpython-312.pyc,, -pydantic/__pycache__/json_schema.cpython-312.pyc,, -pydantic/__pycache__/main.cpython-312.pyc,, -pydantic/__pycache__/mypy.cpython-312.pyc,, -pydantic/__pycache__/networks.cpython-312.pyc,, -pydantic/__pycache__/parse.cpython-312.pyc,, -pydantic/__pycache__/root_model.cpython-312.pyc,, -pydantic/__pycache__/schema.cpython-312.pyc,, -pydantic/__pycache__/tools.cpython-312.pyc,, -pydantic/__pycache__/type_adapter.cpython-312.pyc,, -pydantic/__pycache__/types.cpython-312.pyc,, -pydantic/__pycache__/typing.cpython-312.pyc,, -pydantic/__pycache__/utils.cpython-312.pyc,, -pydantic/__pycache__/validate_call_decorator.cpython-312.pyc,, -pydantic/__pycache__/validators.cpython-312.pyc,, -pydantic/__pycache__/version.cpython-312.pyc,, -pydantic/__pycache__/warnings.cpython-312.pyc,, -pydantic/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pydantic/_internal/__pycache__/__init__.cpython-312.pyc,, -pydantic/_internal/__pycache__/_config.cpython-312.pyc,, -pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc,, -pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc,, -pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc,, -pydantic/_internal/__pycache__/_decorators.cpython-312.pyc,, -pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc,, -pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc,, -pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc,, -pydantic/_internal/__pycache__/_fields.cpython-312.pyc,, -pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc,, -pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc,, -pydantic/_internal/__pycache__/_generics.cpython-312.pyc,, -pydantic/_internal/__pycache__/_git.cpython-312.pyc,, -pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc,, -pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc,, -pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc,, -pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc,, -pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc,, -pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc,, -pydantic/_internal/__pycache__/_repr.cpython-312.pyc,, -pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc,, -pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc,, -pydantic/_internal/__pycache__/_serializers.cpython-312.pyc,, -pydantic/_internal/__pycache__/_signature.cpython-312.pyc,, -pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc,, -pydantic/_internal/__pycache__/_utils.cpython-312.pyc,, -pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc,, -pydantic/_internal/__pycache__/_validators.cpython-312.pyc,, -pydantic/_internal/_config.py,sha256=TWZwg3c0bZHiT3boR5-YYqkouHcwjRdenmyGHofV7E0,14674 -pydantic/_internal/_core_metadata.py,sha256=Y_g2t3i7uluK-wXCZvzJfRFMPUM23aBYLfae4FzBPy0,5162 -pydantic/_internal/_core_utils.py,sha256=1jru4VbJ0x63R6dtVcuOI-dKQTC_d_lSnJWEBQzGNEQ,6487 -pydantic/_internal/_dataclasses.py,sha256=Tk1mEafhad1kV7K5tPX5BwxWSXY7C-MKwf0OLFgIlEA,13158 -pydantic/_internal/_decorators.py,sha256=PnyAoKSg3BNbCVSZnwqw9naEg1UDtYvDT9LluigPiO8,33529 -pydantic/_internal/_decorators_v1.py,sha256=tfdfdpQKY4R2XCOwqHbZeoQMur6VNigRrfhudXBHx38,6185 -pydantic/_internal/_discriminated_union.py,sha256=aMl0SRSyQyHfW4-klnMTHNvwSRoqE3H3PRV_05vRsTg,25478 -pydantic/_internal/_docs_extraction.py,sha256=fyznSAHh5AzohnXZStV0HvH-nRbavNHPyg-knx-S_EE,4127 -pydantic/_internal/_fields.py,sha256=YSfEKq21FgjLJ6YqYXKh0eEEs5nxMPvQ6hp9pA8Nzfw,28093 -pydantic/_internal/_forward_ref.py,sha256=5n3Y7-3AKLn8_FS3Yc7KutLiPUhyXmAtkEZOaFnonwM,611 -pydantic/_internal/_generate_schema.py,sha256=TT49vzYzqH90rWrv5ptNoZgjzOsR0KPlSkqPVFrnrBw,132665 -pydantic/_internal/_generics.py,sha256=ELqjT6LMzQzWAK0EB5_9qke_iAazz0OQ4gunp_uKuYY,23822 -pydantic/_internal/_git.py,sha256=IwPh3DPfa2Xq3rBuB9Nx8luR2A1i69QdeTfWWXIuCVg,809 -pydantic/_internal/_import_utils.py,sha256=TRhxD5OuY6CUosioBdBcJUs0om7IIONiZdYAV7zQ8jM,402 -pydantic/_internal/_internal_dataclass.py,sha256=_bedc1XbuuygRGiLZqkUkwwFpQaoR1hKLlR501nyySY,144 -pydantic/_internal/_known_annotated_metadata.py,sha256=Jc7KTNFZoB3f-0ibP_NgJINOeVvYE3q3OTBQDjVMk3U,16765 -pydantic/_internal/_mock_val_ser.py,sha256=wmRRFSBvqfcLbI41PsFliB4u2AZ3mJpZeiERbD3xKTo,8885 -pydantic/_internal/_model_construction.py,sha256=wk-bNGDAJvduaGvn0U0_8zEl0GERu0shJvN8_ZfkYaw,37783 -pydantic/_internal/_namespace_utils.py,sha256=hl3-TRAr82U2jTyPP3t-QqsvKLirxtkLfNfrN-fp0x8,12878 -pydantic/_internal/_repr.py,sha256=jQfnJuyDxQpSRNhG29II9PX8e4Nv2qWZrEw2lqih3UE,5172 -pydantic/_internal/_schema_gather.py,sha256=VLEv51TYEeeND2czsyrmJq1MVnJqTOmnLan7VG44c8A,9114 -pydantic/_internal/_schema_generation_shared.py,sha256=F_rbQbrkoomgxsskdHpP0jUJ7TCfe0BADAEkq6CJ4nM,4842 -pydantic/_internal/_serializers.py,sha256=YIWvSmAR5fnbGSWCOQduWt1yB4ZQY42eAruc-enrb6c,1491 -pydantic/_internal/_signature.py,sha256=8EljPJe4pSnapuirG5DkBAgD1hggHxEAyzFPH-9H0zE,6779 -pydantic/_internal/_typing_extra.py,sha256=_GRYopNi4a9USi5UQ285ObrlsYmvqKEWTNbBoJFSK2c,30309 -pydantic/_internal/_utils.py,sha256=c6Naqf3bds4jBctepiW5jV0xISQQQk5EBUhMNmVQ3Nk,15912 -pydantic/_internal/_validate_call.py,sha256=PfdVnSzhXOrENtaDoDw3PFWPVYD5W_gNYPe8p3Ug6Lg,5321 -pydantic/_internal/_validators.py,sha256=dv0a2Nkc4zcYqv31Gh_QId2lcf-W0kQpV0oSNzgEdfg,20588 -pydantic/_migration.py,sha256=VF73LRCUz3Irb5xVt13jb3NAcXVnEF6T1-J0OLfeZ5A,12160 -pydantic/alias_generators.py,sha256=KM1n3u4JfLSBl1UuYg3hoYHzXJD-yvgrnq8u1ccwh_A,2124 -pydantic/aliases.py,sha256=vhCHyoSWnX-EJ-wWb5qj4xyRssgGWnTQfzQp4GSZ9ug,4937 -pydantic/annotated_handlers.py,sha256=WfyFSqwoEIFXBh7T73PycKloI1DiX45GWi0-JOsCR4Y,4407 -pydantic/class_validators.py,sha256=i_V3j-PYdGLSLmj_IJZekTRjunO8SIVz8LMlquPyP7E,148 -pydantic/color.py,sha256=AzqGfVQHF92_ZctDcue0DM4yTp2P6tekkwRINTWrLIo,21481 -pydantic/config.py,sha256=5MjjzlAR0_xq7C1yAEPf7qWp5qraQwStRvma9nzbqVI,44267 -pydantic/dataclasses.py,sha256=VlknbEulg08xdmPg_60hBsCVIw-W603OJWY2n5gyXA0,18936 -pydantic/datetime_parse.py,sha256=QC-WgMxMr_wQ_mNXUS7AVf-2hLEhvvsPY1PQyhSGOdk,150 -pydantic/decorator.py,sha256=YX-jUApu5AKaVWKPoaV-n-4l7UbS69GEt9Ra3hszmKI,145 -pydantic/deprecated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pydantic/deprecated/__pycache__/__init__.cpython-312.pyc,, -pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc,, -pydantic/deprecated/__pycache__/config.cpython-312.pyc,, -pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc,, -pydantic/deprecated/__pycache__/decorator.cpython-312.pyc,, -pydantic/deprecated/__pycache__/json.cpython-312.pyc,, -pydantic/deprecated/__pycache__/parse.cpython-312.pyc,, -pydantic/deprecated/__pycache__/tools.cpython-312.pyc,, -pydantic/deprecated/class_validators.py,sha256=EAcaVQM5zp2wBml0ybN62CfQfyJvDLx5Qd9Pk4_tb4U,10273 -pydantic/deprecated/config.py,sha256=k_lsVk57paxLJOcBueH07cu1OgEgWdVBxm6lfaC3CCU,2663 -pydantic/deprecated/copy_internals.py,sha256=Ghd-vkMd5EYCCgyCGtPKO58np9cEKBQC6qkBeIEFI2g,7618 -pydantic/deprecated/decorator.py,sha256=TBm6bJ7wJsNih_8Wq5IzDcwP32m9_vfxs96desLuk00,10845 -pydantic/deprecated/json.py,sha256=HlWCG35RRrxyzuTS6LTQiZBwRhmDZWmeqQH8rLW6wA8,4657 -pydantic/deprecated/parse.py,sha256=Gzd6b_g8zJXcuE7QRq5adhx_EMJahXfcpXCF0RgrqqI,2511 -pydantic/deprecated/tools.py,sha256=Nrm9oFRZWp8-jlfvPgJILEsywp4YzZD52XIGPDLxHcI,3330 -pydantic/env_settings.py,sha256=6IHeeWEqlUPRUv3V-AXiF_W91fg2Jw_M3O0l34J_eyA,148 -pydantic/error_wrappers.py,sha256=RK6mqATc9yMD-KBD9IJS9HpKCprWHd8wo84Bnm-3fR8,150 -pydantic/errors.py,sha256=7ctBNCtt57kZFx71Ls2H86IufQARv4wPKf8DhdsVn5w,6002 -pydantic/experimental/__init__.py,sha256=QT7rKYdDsCiTJ9GEjmsQdWHScwpKrrNkGq6vqONP6RQ,104 -pydantic/experimental/__pycache__/__init__.cpython-312.pyc,, -pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc,, -pydantic/experimental/__pycache__/missing_sentinel.cpython-312.pyc,, -pydantic/experimental/__pycache__/pipeline.cpython-312.pyc,, -pydantic/experimental/arguments_schema.py,sha256=EFnjX_ulp-tPyUjQX5pmQtug1OFL_Acc8bcMbLd-fVY,1866 -pydantic/experimental/missing_sentinel.py,sha256=hQejgtF00wUuQMni9429evg-eXyIwpKvjsD8ofqfj-w,127 -pydantic/experimental/pipeline.py,sha256=Kv_dvcexKumazfRL0y69AayeA6H37SrmsZ3SUl_n0qY,23582 -pydantic/fields.py,sha256=WuDGOvB22KWuuW3fXnS4Wvg4qX_tdp8X7BrAlza4sw8,79194 -pydantic/functional_serializers.py,sha256=rEzH391zqy3o_bWk2QEuvySmcQNZmwXmJQLC3ZGF7QA,17151 -pydantic/functional_validators.py,sha256=c_-7weWpGNcOYfRfVUFu11jrxMVMdfY_c-4istwk95Y,31839 -pydantic/generics.py,sha256=0ZqZ9O9annIj_3mGBRqps4htey3b5lV1-d2tUxPMMnA,144 -pydantic/json.py,sha256=ZH8RkI7h4Bz-zp8OdTAxbJUoVvcoU-jhMdRZ0B-k0xc,140 -pydantic/json_schema.py,sha256=-h8c7vsNGAJCIxR-n52-69Q54w38EM-j0AGC_4VGt30,123653 -pydantic/main.py,sha256=WZTxwW81igl75Y00zHJJmoU3qCNSy-1KCEmEsBPftiQ,84205 -pydantic/mypy.py,sha256=p6KU1GwPHazF7E5vJq1uLd4tHd6DE6bre4-m5Ln23ms,58986 -pydantic/networks.py,sha256=Smf_RyImQ-F5FZLCgFwHPfROYxW_e-Hz68R_8LW0sZ0,42099 -pydantic/parse.py,sha256=wkd82dgtvWtD895U_I6E1htqMlGhBSYEV39cuBSeo3A,141 -pydantic/plugin/__init__.py,sha256=a7Tw366U6K3kltCCNZY76nc9ss-7uGGQ40TXad9OypQ,7333 -pydantic/plugin/__pycache__/__init__.cpython-312.pyc,, -pydantic/plugin/__pycache__/_loader.cpython-312.pyc,, -pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc,, -pydantic/plugin/_loader.py,sha256=9QLXneLEmvyhXka_9j4Lrkbme4qPv6qYphlsjF2MGsA,2210 -pydantic/plugin/_schema_validator.py,sha256=QbmqsG33MBmftNQ2nNiuN22LhbrexUA7ipDVv3J02BU,5267 -pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pydantic/root_model.py,sha256=BvmLtW4i11dJk-dLOM3rl-jnJdQGeeQTFBcmEOq6pMg,6311 -pydantic/schema.py,sha256=Vqqjvq_LnapVknebUd3Bp_J1p2gXZZnZRgL48bVEG7o,142 -pydantic/tools.py,sha256=iHQpd8SJ5DCTtPV5atAV06T89bjSaMFeZZ2LX9lasZY,141 -pydantic/type_adapter.py,sha256=VT--yg4a27shSBzWHBPKz493f3iQ9obdkEkhjZKlE7Q,35653 -pydantic/types.py,sha256=nqdS-J2ZXqTh2qeyJOzBTBtHWyZ5YRFe8gaMV59d9HE,105431 -pydantic/typing.py,sha256=P7feA35MwTcLsR1uL7db0S-oydBxobmXa55YDoBgajQ,138 -pydantic/utils.py,sha256=15nR2QpqTBFlQV4TNtTItMyTJx_fbyV-gPmIEY1Gooc,141 -pydantic/v1/__init__.py,sha256=FLQ8ISp6MVZRfjnS7fQ4m1FxQxFCF2QVikE4DK-4PhE,3164 -pydantic/v1/__pycache__/__init__.cpython-312.pyc,, -pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc,, -pydantic/v1/__pycache__/annotated_types.cpython-312.pyc,, -pydantic/v1/__pycache__/class_validators.cpython-312.pyc,, -pydantic/v1/__pycache__/color.cpython-312.pyc,, -pydantic/v1/__pycache__/config.cpython-312.pyc,, -pydantic/v1/__pycache__/dataclasses.cpython-312.pyc,, -pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc,, -pydantic/v1/__pycache__/decorator.cpython-312.pyc,, -pydantic/v1/__pycache__/env_settings.cpython-312.pyc,, -pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc,, -pydantic/v1/__pycache__/errors.cpython-312.pyc,, -pydantic/v1/__pycache__/fields.cpython-312.pyc,, -pydantic/v1/__pycache__/generics.cpython-312.pyc,, -pydantic/v1/__pycache__/json.cpython-312.pyc,, -pydantic/v1/__pycache__/main.cpython-312.pyc,, -pydantic/v1/__pycache__/mypy.cpython-312.pyc,, -pydantic/v1/__pycache__/networks.cpython-312.pyc,, -pydantic/v1/__pycache__/parse.cpython-312.pyc,, -pydantic/v1/__pycache__/schema.cpython-312.pyc,, -pydantic/v1/__pycache__/tools.cpython-312.pyc,, -pydantic/v1/__pycache__/types.cpython-312.pyc,, -pydantic/v1/__pycache__/typing.cpython-312.pyc,, -pydantic/v1/__pycache__/utils.cpython-312.pyc,, -pydantic/v1/__pycache__/validators.cpython-312.pyc,, -pydantic/v1/__pycache__/version.cpython-312.pyc,, -pydantic/v1/_hypothesis_plugin.py,sha256=5ES5xWuw1FQAsymLezy8QgnVz0ZpVfU3jkmT74H27VQ,14847 -pydantic/v1/annotated_types.py,sha256=uk2NAAxqiNELKjiHhyhxKaIOh8F1lYW_LzrW3X7oZBc,3157 -pydantic/v1/class_validators.py,sha256=ULOaIUgYUDBsHL7EEVEarcM-UubKUggoN8hSbDonsFE,14672 -pydantic/v1/color.py,sha256=iZABLYp6OVoo2AFkP9Ipri_wSc6-Kklu8YuhSartd5g,16844 -pydantic/v1/config.py,sha256=a6P0Wer9x4cbwKW7Xv8poSUqM4WP-RLWwX6YMpYq9AA,6532 -pydantic/v1/dataclasses.py,sha256=784cqvInbwIPWr9usfpX3ch7z4t3J2tTK6N067_wk1o,18172 -pydantic/v1/datetime_parse.py,sha256=4Qy1kQpq3rNVZJeIHeSPDpuS2Bvhp1KPtzJG1xu-H00,7724 -pydantic/v1/decorator.py,sha256=zaaxxxoWPCm818D1bs0yhapRjXm32V8G0ZHWCdM1uXA,10339 -pydantic/v1/env_settings.py,sha256=A9VXwtRl02AY-jH0C0ouy5VNw3fi6F_pkzuHDjgAAOM,14105 -pydantic/v1/error_wrappers.py,sha256=6625Mfw9qkC2NwitB_JFAWe8B-Xv6zBU7rL9k28tfyo,5196 -pydantic/v1/errors.py,sha256=mIwPED5vGM5Q5v4C4Z1JPldTRH-omvEylH6ksMhOmPw,17726 -pydantic/v1/fields.py,sha256=VqWJCriUNiEyptXroDVJ501JpVA0en2VANcksqXL2b8,50649 -pydantic/v1/generics.py,sha256=VzC9YUV-EbPpQ3aAfk1cNFej79_IzznkQ7WrmTTZS9E,17871 -pydantic/v1/json.py,sha256=WQ5Hy_hIpfdR3YS8k6N2E6KMJzsdbBi_ldWOPJaV81M,3390 -pydantic/v1/main.py,sha256=zuNpdN5Q0V0wG2UUTKt0HUy3XJ4OAvPSZDdiXY-FIzs,44824 -pydantic/v1/mypy.py,sha256=Cl8XRfCmIcVE3j5AEU52C8iDh8lcX__D3hz2jIWxMAs,38860 -pydantic/v1/networks.py,sha256=HYNtKAfOmOnKJpsDg1g6SIkj9WPhU_-i8l5e2JKBpG4,22124 -pydantic/v1/parse.py,sha256=BJtdqiZRtav9VRFCmOxoY-KImQmjPy-A_NoojiFUZxY,1821 -pydantic/v1/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pydantic/v1/schema.py,sha256=aqBuA--cq8gAVkim5BJPFASHzOZ8dFtmFX_fNGr6ip4,47801 -pydantic/v1/tools.py,sha256=1lDdXHk0jL5uP3u5RCYAvUAlGClgAO-45lkq9j7fyBA,2881 -pydantic/v1/types.py,sha256=Bzl-RcnitPBHnqwwj9iv7JjHuN1GpnWH24dKkF3l9e8,35455 -pydantic/v1/typing.py,sha256=7GdBg1YTHULU81thB_9cjRNDfZfn4khoX7nGtw_keCE,19677 -pydantic/v1/utils.py,sha256=M5FRyfNUb1A2mk9laGgCVdfHHb3AtQgrjO5qfyBf4xA,25989 -pydantic/v1/validators.py,sha256=lyUkn1MWhHxlCX5ZfEgFj_CAHojoiPcaQeMdEM9XviU,22187 -pydantic/v1/version.py,sha256=HXnXW-1bMW5qKhlr5RgOEPohrZDCDSuyy8-gi8GCgZo,1039 -pydantic/validate_call_decorator.py,sha256=8jqLlgXTjWEj4dXDg0wI3EGQKkb0JnCsL_JSUjbU5Sg,4389 -pydantic/validators.py,sha256=pwbIJXVb1CV2mAE4w_EGfNj7DwzsKaWw_tTL6cviTus,146 -pydantic/version.py,sha256=XNmGSyOP87Mqa_A9HFzfDcNippfnqfRK3ZUiGyBb4-A,3985 -pydantic/warnings.py,sha256=Wu1VGzrvFZw4T6yCIKHjH7LSY66HjbtyCFbn5uWoMJ4,4802 diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/METADATA new file mode 100644 index 00000000..09e8216b --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/METADATA @@ -0,0 +1,1464 @@ +Metadata-Version: 2.1 +Name: pydantic +Version: 2.5.0 +Summary: Data validation using Python type hints +Project-URL: Homepage, https://github.com/pydantic/pydantic +Project-URL: Documentation, https://docs.pydantic.dev +Project-URL: Funding, https://github.com/sponsors/samuelcolvin +Project-URL: Source, https://github.com/pydantic/pydantic +Project-URL: Changelog, https://docs.pydantic.dev/latest/changelog/ +Author-email: Samuel Colvin , Eric Jolibois , Hasan Ramezani , Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Terrence Dorsey , David Montague , Serge Matveenko , Marcelo Trylesinski , Sydney Runkle , David Hewitt +License-Expression: MIT +License-File: LICENSE +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Framework :: Hypothesis +Classifier: Framework :: Pydantic +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +Requires-Dist: annotated-types>=0.4.0 +Requires-Dist: importlib-metadata; python_version == '3.7' +Requires-Dist: pydantic-core==2.14.1 +Requires-Dist: typing-extensions>=4.6.1 +Provides-Extra: email +Requires-Dist: email-validator>=2.0.0; extra == 'email' +Description-Content-Type: text/markdown + +# Pydantic + +[![CI](https://github.com/pydantic/pydantic/workflows/CI/badge.svg?event=push)](https://github.com/pydantic/pydantic/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![Coverage](https://coverage-badge.samuelcolvin.workers.dev/pydantic/pydantic.svg)](https://coverage-badge.samuelcolvin.workers.dev/redirect/pydantic/pydantic) +[![pypi](https://img.shields.io/pypi/v/pydantic.svg)](https://pypi.python.org/pypi/pydantic) +[![CondaForge](https://img.shields.io/conda/v/conda-forge/pydantic.svg)](https://anaconda.org/conda-forge/pydantic) +[![downloads](https://static.pepy.tech/badge/pydantic/month)](https://pepy.tech/project/pydantic) +[![versions](https://img.shields.io/pypi/pyversions/pydantic.svg)](https://github.com/pydantic/pydantic) +[![license](https://img.shields.io/github/license/pydantic/pydantic.svg)](https://github.com/pydantic/pydantic/blob/main/LICENSE) +[![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://docs.pydantic.dev/latest/contributing/#badges) + +Data validation using Python type hints. + +Fast and extensible, Pydantic plays nicely with your linters/IDE/brain. +Define how data should be in pure, canonical Python 3.7+; validate it with Pydantic. + +## Pydantic Company :rocket: + +We've started a company based on the principles that I believe have led to Pydantic's success. +Learning more from the [Company Announcement](https://pydantic.dev/announcement/). + +## Pydantic V1.10 vs. V2 + +Pydantic V2 is a ground-up rewrite that offers many new features, performance improvements, and some breaking changes compared to Pydantic V1. + +If you're using Pydantic V1 you may want to look at the +[pydantic V1.10 Documentation](https://docs.pydantic.dev/) or, +[`1.10.X-fixes` git branch](https://github.com/pydantic/pydantic/tree/1.10.X-fixes). Pydantic V2 also ships with the latest version of Pydantic V1 built in so that you can incrementally upgrade your code base and projects: `from pydantic import v1 as pydantic_v1`. + +## Help + +See [documentation](https://docs.pydantic.dev/) for more details. + +## Installation + +Install using `pip install -U pydantic` or `conda install pydantic -c conda-forge`. +For more installation options to make Pydantic even faster, +see the [Install](https://docs.pydantic.dev/install/) section in the documentation. + +## A Simple Example + +```py +from datetime import datetime +from typing import List, Optional +from pydantic import BaseModel + +class User(BaseModel): + id: int + name: str = 'John Doe' + signup_ts: Optional[datetime] = None + friends: List[int] = [] + +external_data = {'id': '123', 'signup_ts': '2017-06-01 12:22', 'friends': [1, '2', b'3']} +user = User(**external_data) +print(user) +#> User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] +print(user.id) +#> 123 +``` + +## Contributing + +For guidance on setting up a development environment and how to make a +contribution to Pydantic, see +[Contributing to Pydantic](https://docs.pydantic.dev/contributing/). + +## Reporting a Security Vulnerability + +See our [security policy](https://github.com/pydantic/pydantic/security/policy). + +## Changelog + +## v2.5.0 (2023-11-13) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.5.0) + +The code released in v2.5.0 is functionally identical to that of v2.5.0b1. + +### What's Changed + +#### Packaging + +* Update pydantic-core from 2.10.1 to 2.14.1, significant changes from these updates are described below, full changelog [here](https://github.com/pydantic/pydantic-core/compare/v2.10.1...v2.14.1) +* Update to `pyright==1.1.335` by [@Viicos](https://github.com/Viicos) in [#8075](https://github.com/pydantic/pydantic/pull/8075) + +#### New Features + +* Allow plugins to catch non `ValidationError` errors by [@adriangb](https://github.com/adriangb) in [#7806](https://github.com/pydantic/pydantic/pull/7806) +* Support `__doc__` argument in `create_model()` by [@chris-spann](https://github.com/chris-spann) in [#7863](https://github.com/pydantic/pydantic/pull/7863) +* Expose `regex_engine` flag - meaning you can use with the Rust or Python regex libraries in constraints by [@utkini](https://github.com/utkini) in [#7768](https://github.com/pydantic/pydantic/pull/7768) +* Save return type generated from type annotation in `ComputedFieldInfo` by [@alexmojaki](https://github.com/alexmojaki) in [#7889](https://github.com/pydantic/pydantic/pull/7889) +* Adopting `ruff` formatter by [@Luca-Blight](https://github.com/Luca-Blight) in [#7930](https://github.com/pydantic/pydantic/pull/7930) +* Added `validation_error_cause` to config by [@zakstucke](https://github.com/zakstucke) in [#7626](https://github.com/pydantic/pydantic/pull/7626) +* Make path of the item to validate available in plugin by [@hramezani](https://github.com/hramezani) in [#7861](https://github.com/pydantic/pydantic/pull/7861) +* Add `CallableDiscriminator` and `Tag` by [@dmontagu](https://github.com/dmontagu) in [#7983](https://github.com/pydantic/pydantic/pull/7983) +* Make union case tags affect union error messages by [@dmontagu](https://github.com/dmontagu) in [#8001](https://github.com/pydantic/pydantic/pull/8001) +* Add `examples` and `json_schema_extra` to `@computed_field` by [@alexmojaki](https://github.com/alexmojaki) in [#8013](https://github.com/pydantic/pydantic/pull/8013) +* Add `JsonValue` type by [@dmontagu](https://github.com/dmontagu) in [#7998](https://github.com/pydantic/pydantic/pull/7998) +* Allow `str` as argument to `Discriminator` by [@dmontagu](https://github.com/dmontagu) in [#8047](https://github.com/pydantic/pydantic/pull/8047) +* Add `SchemaSerializer.__reduce__` method to enable pickle serialization by [@edoakes](https://github.com/edoakes) in [pydantic/pydantic-core#1006](https://github.com/pydantic/pydantic-core/pull/1006) + +#### Changes + +* **Significant Change:** replace `ultra_strict` with new smart union implementation, the way unions are validated has changed significantly to improve performance and correctness, we have worked hard to absolutely minimise the number of cases where behaviour has changed, see the PR for details - by [@davidhewitt](https://github.com/davidhewitt) in [pydantic/pydantic-core#867](https://github.com/pydantic/pydantic-core/pull/867) +* Add support for instance method reassignment when `extra='allow'` by [@sydney-runkle](https://github.com/sydney-runkle) in [#7683](https://github.com/pydantic/pydantic/pull/7683) +* Support JSON schema generation for `Enum` types with no cases by [@sydney-runkle](https://github.com/sydney-runkle) in [#7927](https://github.com/pydantic/pydantic/pull/7927) +* Warn if a class inherits from `Generic` before `BaseModel` by [@alexmojaki](https://github.com/alexmojaki) in [#7891](https://github.com/pydantic/pydantic/pull/7891) + +#### Performance + +* New custom JSON parser, `jiter` by [@samuelcolvin](https://github.com/samuelcolvin) in [pydantic/pydantic-core#974](https://github.com/pydantic/pydantic-core/pull/974) +* PGO build for MacOS M1 by [@samuelcolvin](https://github.com/samuelcolvin) in [pydantic/pydantic-core#1063](https://github.com/pydantic/pydantic-core/pull/1063) +* Use `__getattr__` for all package imports, improve import time by [@samuelcolvin](https://github.com/samuelcolvin) in [#7947](https://github.com/pydantic/pydantic/pull/7947) + +#### Fixes + +* Fix `mypy` issue with subclasses of `RootModel` by [@sydney-runkle](https://github.com/sydney-runkle) in [#7677](https://github.com/pydantic/pydantic/pull/7677) +* Properly rebuild the `FieldInfo` when a forward ref gets evaluated by [@dmontagu](https://github.com/dmontagu) in [#7698](https://github.com/pydantic/pydantic/pull/7698) +* Fix failure to load `SecretStr` from JSON (regression in v2.4) by [@sydney-runkle](https://github.com/sydney-runkle) in [#7729](https://github.com/pydantic/pydantic/pull/7729) +* Fix `defer_build` behavior with `TypeAdapter` by [@sydney-runkle](https://github.com/sydney-runkle) in [#7736](https://github.com/pydantic/pydantic/pull/7736) +* Improve compatibility with legacy `mypy` versions by [@dmontagu](https://github.com/dmontagu) in [#7742](https://github.com/pydantic/pydantic/pull/7742) +* Fix: update `TypeVar` handling when default is not set by [@pmmmwh](https://github.com/pmmmwh) in [#7719](https://github.com/pydantic/pydantic/pull/7719) +* Support specification of `strict` on `Enum` type fields by [@sydney-runkle](https://github.com/sydney-runkle) in [#7761](https://github.com/pydantic/pydantic/pull/7761) +* Wrap `weakref.ref` instead of subclassing to fix `cloudpickle` serialization by [@edoakes](https://github.com/edoakes) in [#7780](https://github.com/pydantic/pydantic/pull/7780) +* Keep values of private attributes set within `model_post_init` in subclasses by [@alexmojaki](https://github.com/alexmojaki) in [#7775](https://github.com/pydantic/pydantic/pull/7775) +* Add more specific type for non-callable `json_schema_extra` by [@alexmojaki](https://github.com/alexmojaki) in [#7803](https://github.com/pydantic/pydantic/pull/7803) +* Raise an error when deleting frozen (model) fields by [@alexmojaki](https://github.com/alexmojaki) in [#7800](https://github.com/pydantic/pydantic/pull/7800) +* Fix schema sorting bug with default values by [@sydney-runkle](https://github.com/sydney-runkle) in [#7817](https://github.com/pydantic/pydantic/pull/7817) +* Use generated alias for aliases that are not specified otherwise by [@alexmojaki](https://github.com/alexmojaki) in [#7802](https://github.com/pydantic/pydantic/pull/7802) +* Support `strict` specification for `UUID` types by [@sydney-runkle](https://github.com/sydney-runkle) in [#7865](https://github.com/pydantic/pydantic/pull/7865) +* JSON schema: fix extra parameter handling by [@me-and](https://github.com/me-and) in [#7810](https://github.com/pydantic/pydantic/pull/7810) +* Fix: support `pydantic.Field(kw_only=True)` with inherited dataclasses by [@PrettyWood](https://github.com/PrettyWood) in [#7827](https://github.com/pydantic/pydantic/pull/7827) +* Support `validate_call` decorator for methods in classes with `__slots__` by [@sydney-runkle](https://github.com/sydney-runkle) in [#7883](https://github.com/pydantic/pydantic/pull/7883) +* Fix pydantic dataclass problem with `dataclasses.field` default by [@hramezani](https://github.com/hramezani) in [#7898](https://github.com/pydantic/pydantic/pull/7898) +* Fix schema generation for generics with union type bounds by [@sydney-runkle](https://github.com/sydney-runkle) in [#7899](https://github.com/pydantic/pydantic/pull/7899) +* Fix version for `importlib_metadata` on python 3.7 by [@sydney-runkle](https://github.com/sydney-runkle) in [#7904](https://github.com/pydantic/pydantic/pull/7904) +* Support `|` operator (Union) in PydanticRecursiveRef by [@alexmojaki](https://github.com/alexmojaki) in [#7892](https://github.com/pydantic/pydantic/pull/7892) +* Fix `display_as_type` for `TypeAliasType` in python 3.12 by [@dmontagu](https://github.com/dmontagu) in [#7929](https://github.com/pydantic/pydantic/pull/7929) +* Add support for `NotRequired` generics in `TypedDict` by [@sydney-runkle](https://github.com/sydney-runkle) in [#7932](https://github.com/pydantic/pydantic/pull/7932) +* Make generic `TypeAliasType` specifications produce different schema definitions by [@alexdrydew](https://github.com/alexdrydew) in [#7893](https://github.com/pydantic/pydantic/pull/7893) +* Added fix for signature of inherited dataclass by [@howsunjow](https://github.com/howsunjow) in [#7925](https://github.com/pydantic/pydantic/pull/7925) +* Make the model name generation more robust in JSON schema by [@joakimnordling](https://github.com/joakimnordling) in [#7881](https://github.com/pydantic/pydantic/pull/7881) +* Fix plurals in validation error messages (in tests) by [@Iipin](https://github.com/Iipin) in [#7972](https://github.com/pydantic/pydantic/pull/7972) +* `PrivateAttr` is passed from `Annotated` default position by [@tabassco](https://github.com/tabassco) in [#8004](https://github.com/pydantic/pydantic/pull/8004) +* Don't decode bytes (which may not be UTF8) when displaying SecretBytes by [@alexmojaki](https://github.com/alexmojaki) in [#8012](https://github.com/pydantic/pydantic/pull/8012) +* Use `classmethod` instead of `classmethod[Any, Any, Any]` by [@Mr-Pepe](https://github.com/Mr-Pepe) in [#7979](https://github.com/pydantic/pydantic/pull/7979) +* Clearer error on invalid Plugin by [@samuelcolvin](https://github.com/samuelcolvin) in [#8023](https://github.com/pydantic/pydantic/pull/8023) +* Correct pydantic dataclasses import by [@samuelcolvin](https://github.com/samuelcolvin) in [#8027](https://github.com/pydantic/pydantic/pull/8027) +* Fix misbehavior for models referencing redefined type aliases by [@dmontagu](https://github.com/dmontagu) in [#8050](https://github.com/pydantic/pydantic/pull/8050) +* Fix `Optional` field with `validate_default` only performing one field validation by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1002](https://github.com/pydantic/pydantic-core/pull/1002) +* Fix `definition-ref` bug with `Dict` keys by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1014](https://github.com/pydantic/pydantic-core/pull/1014) +* Fix bug allowing validation of `bool` types with `coerce_numbers_to_str=True` by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1017](https://github.com/pydantic/pydantic-core/pull/1017) +* Don't accept `NaN` in float and decimal constraints by [@davidhewitt](https://github.com/davidhewitt) in [pydantic/pydantic-core#1037](https://github.com/pydantic/pydantic-core/pull/1037) +* Add `lax_str` and `lax_int` support for enum values not inherited from str/int by [@michaelhly](https://github.com/michaelhly) in [pydantic/pydantic-core#1015](https://github.com/pydantic/pydantic-core/pull/1015) +* Support subclasses in lists in `Union` of `List` types by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1039](https://github.com/pydantic/pydantic-core/pull/1039) +* Allow validation against `max_digits` and `decimals` to pass if normalized or non-normalized input is valid by [@sydney-runkle](https://github.com/sydney-runkle) in [pydantic/pydantic-core#1049](https://github.com/pydantic/pydantic-core/pull/1049) +* Fix: proper pluralization in `ValidationError` messages by [@Iipin](https://github.com/Iipin) in [pydantic/pydantic-core#1050](https://github.com/pydantic/pydantic-core/pull/1050) +* Disallow the string `'-'` as `datetime` input by [@davidhewitt](https://github.com/davidhewitt) in [pydantic/speedate#52](https://github.com/pydantic/speedate/pull/52) & [pydantic/pydantic-core#1060](https://github.com/pydantic/pydantic-core/pull/1060) +* Fix: NaN and Inf float serialization by [@davidhewitt](https://github.com/davidhewitt) in [pydantic/pydantic-core#1062](https://github.com/pydantic/pydantic-core/pull/1062) +* Restore manylinux-compatible PGO builds by [@davidhewitt](https://github.com/davidhewitt) in [pydantic/pydantic-core#1068](https://github.com/pydantic/pydantic-core/pull/1068) + +### New Contributors + +#### `pydantic` +* [@schneebuzz](https://github.com/schneebuzz) made their first contribution in [#7699](https://github.com/pydantic/pydantic/pull/7699) +* [@edoakes](https://github.com/edoakes) made their first contribution in [#7780](https://github.com/pydantic/pydantic/pull/7780) +* [@alexmojaki](https://github.com/alexmojaki) made their first contribution in [#7775](https://github.com/pydantic/pydantic/pull/7775) +* [@NickG123](https://github.com/NickG123) made their first contribution in [#7751](https://github.com/pydantic/pydantic/pull/7751) +* [@gowthamgts](https://github.com/gowthamgts) made their first contribution in [#7830](https://github.com/pydantic/pydantic/pull/7830) +* [@jamesbraza](https://github.com/jamesbraza) made their first contribution in [#7848](https://github.com/pydantic/pydantic/pull/7848) +* [@laundmo](https://github.com/laundmo) made their first contribution in [#7850](https://github.com/pydantic/pydantic/pull/7850) +* [@rahmatnazali](https://github.com/rahmatnazali) made their first contribution in [#7870](https://github.com/pydantic/pydantic/pull/7870) +* [@waterfountain1996](https://github.com/waterfountain1996) made their first contribution in [#7878](https://github.com/pydantic/pydantic/pull/7878) +* [@chris-spann](https://github.com/chris-spann) made their first contribution in [#7863](https://github.com/pydantic/pydantic/pull/7863) +* [@me-and](https://github.com/me-and) made their first contribution in [#7810](https://github.com/pydantic/pydantic/pull/7810) +* [@utkini](https://github.com/utkini) made their first contribution in [#7768](https://github.com/pydantic/pydantic/pull/7768) +* [@bn-l](https://github.com/bn-l) made their first contribution in [#7744](https://github.com/pydantic/pydantic/pull/7744) +* [@alexdrydew](https://github.com/alexdrydew) made their first contribution in [#7893](https://github.com/pydantic/pydantic/pull/7893) +* [@Luca-Blight](https://github.com/Luca-Blight) made their first contribution in [#7930](https://github.com/pydantic/pydantic/pull/7930) +* [@howsunjow](https://github.com/howsunjow) made their first contribution in [#7925](https://github.com/pydantic/pydantic/pull/7925) +* [@joakimnordling](https://github.com/joakimnordling) made their first contribution in [#7881](https://github.com/pydantic/pydantic/pull/7881) +* [@icfly2](https://github.com/icfly2) made their first contribution in [#7976](https://github.com/pydantic/pydantic/pull/7976) +* [@Yummy-Yums](https://github.com/Yummy-Yums) made their first contribution in [#8003](https://github.com/pydantic/pydantic/pull/8003) +* [@Iipin](https://github.com/Iipin) made their first contribution in [#7972](https://github.com/pydantic/pydantic/pull/7972) +* [@tabassco](https://github.com/tabassco) made their first contribution in [#8004](https://github.com/pydantic/pydantic/pull/8004) +* [@Mr-Pepe](https://github.com/Mr-Pepe) made their first contribution in [#7979](https://github.com/pydantic/pydantic/pull/7979) +* [@0x00cl](https://github.com/0x00cl) made their first contribution in [#8010](https://github.com/pydantic/pydantic/pull/8010) +* [@barraponto](https://github.com/barraponto) made their first contribution in [#8032](https://github.com/pydantic/pydantic/pull/8032) + +#### `pydantic-core` +* [@sisp](https://github.com/sisp) made their first contribution in [pydantic/pydantic-core#995](https://github.com/pydantic/pydantic-core/pull/995) +* [@michaelhly](https://github.com/michaelhly) made their first contribution in [pydantic/pydantic-core#1015](https://github.com/pydantic/pydantic-core/pull/1015) + +## v2.5.0b1 (2023-11-09) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.5.0b1) for details. + +## v2.4.2 (2023-09-27) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.4.2) + +### What's Changed + +#### Fixes + +* Fix bug with JSON schema for sequence of discriminated union by [@dmontagu](https://github.com/dmontagu) in [#7647](https://github.com/pydantic/pydantic/pull/7647) +* Fix schema references in discriminated unions by [@adriangb](https://github.com/adriangb) in [#7646](https://github.com/pydantic/pydantic/pull/7646) +* Fix json schema generation for recursive models by [@adriangb](https://github.com/adriangb) in [#7653](https://github.com/pydantic/pydantic/pull/7653) +* Fix `models_json_schema` for generic models by [@adriangb](https://github.com/adriangb) in [#7654](https://github.com/pydantic/pydantic/pull/7654) +* Fix xfailed test for generic model signatures by [@adriangb](https://github.com/adriangb) in [#7658](https://github.com/pydantic/pydantic/pull/7658) + +### New Contributors + +* [@austinorr](https://github.com/austinorr) made their first contribution in [#7657](https://github.com/pydantic/pydantic/pull/7657) +* [@peterHoburg](https://github.com/peterHoburg) made their first contribution in [#7670](https://github.com/pydantic/pydantic/pull/7670) + +## v2.4.1 (2023-09-26) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.4.1) + +### What's Changed + +#### Packaging + +* Update pydantic-core to 2.10.1 by [@davidhewitt](https://github.com/davidhewitt) in [#7633](https://github.com/pydantic/pydantic/pull/7633) + +#### Fixes + +* Serialize unsubstituted type vars as `Any` by [@adriangb](https://github.com/adriangb) in [#7606](https://github.com/pydantic/pydantic/pull/7606) +* Remove schema building caches by [@adriangb](https://github.com/adriangb) in [#7624](https://github.com/pydantic/pydantic/pull/7624) +* Fix an issue where JSON schema extras weren't JSON encoded by [@dmontagu](https://github.com/dmontagu) in [#7625](https://github.com/pydantic/pydantic/pull/7625) + +## v2.4.0 (2023-09-22) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.4.0) + +### What's Changed + +#### Packaging + +* Update pydantic-core to 2.10.0 by [@samuelcolvin](https://github.com/samuelcolvin) in [#7542](https://github.com/pydantic/pydantic/pull/7542) + +#### New Features + +* Add `Base64Url` types by [@dmontagu](https://github.com/dmontagu) in [#7286](https://github.com/pydantic/pydantic/pull/7286) +* Implement optional `number` to `str` coercion by [@lig](https://github.com/lig) in [#7508](https://github.com/pydantic/pydantic/pull/7508) +* Allow access to `field_name` and `data` in all validators if there is data and a field name by [@samuelcolvin](https://github.com/samuelcolvin) in [#7542](https://github.com/pydantic/pydantic/pull/7542) +* Add `BaseModel.model_validate_strings` and `TypeAdapter.validate_strings` by [@hramezani](https://github.com/hramezani) in [#7552](https://github.com/pydantic/pydantic/pull/7552) +* Add Pydantic `plugins` experimental implementation by [@lig](https://github.com/lig) [@samuelcolvin](https://github.com/samuelcolvin) and [@Kludex](https://github.com/Kludex) in [#6820](https://github.com/pydantic/pydantic/pull/6820) + +#### Changes + +* Do not override `model_post_init` in subclass with private attrs by [@Viicos](https://github.com/Viicos) in [#7302](https://github.com/pydantic/pydantic/pull/7302) +* Make fields with defaults not required in the serialization schema by default by [@dmontagu](https://github.com/dmontagu) in [#7275](https://github.com/pydantic/pydantic/pull/7275) +* Mark `Extra` as deprecated by [@disrupted](https://github.com/disrupted) in [#7299](https://github.com/pydantic/pydantic/pull/7299) +* Make `EncodedStr` a dataclass by [@Kludex](https://github.com/Kludex) in [#7396](https://github.com/pydantic/pydantic/pull/7396) +* Move `annotated_handlers` to be public by [@samuelcolvin](https://github.com/samuelcolvin) in [#7569](https://github.com/pydantic/pydantic/pull/7569) + +#### Performance + +* Simplify flattening and inlining of `CoreSchema` by [@adriangb](https://github.com/adriangb) in [#7523](https://github.com/pydantic/pydantic/pull/7523) +* Remove unused copies in `CoreSchema` walking by [@adriangb](https://github.com/adriangb) in [#7528](https://github.com/pydantic/pydantic/pull/7528) +* Add caches for collecting definitions and invalid schemas from a CoreSchema by [@adriangb](https://github.com/adriangb) in [#7527](https://github.com/pydantic/pydantic/pull/7527) +* Eagerly resolve discriminated unions and cache cases where we can't by [@adriangb](https://github.com/adriangb) in [#7529](https://github.com/pydantic/pydantic/pull/7529) +* Replace `dict.get` and `dict.setdefault` with more verbose versions in `CoreSchema` building hot paths by [@adriangb](https://github.com/adriangb) in [#7536](https://github.com/pydantic/pydantic/pull/7536) +* Cache invalid `CoreSchema` discovery by [@adriangb](https://github.com/adriangb) in [#7535](https://github.com/pydantic/pydantic/pull/7535) +* Allow disabling `CoreSchema` validation for faster startup times by [@adriangb](https://github.com/adriangb) in [#7565](https://github.com/pydantic/pydantic/pull/7565) + +#### Fixes + +* Fix config detection for `TypedDict` from grandparent classes by [@dmontagu](https://github.com/dmontagu) in [#7272](https://github.com/pydantic/pydantic/pull/7272) +* Fix hash function generation for frozen models with unusual MRO by [@dmontagu](https://github.com/dmontagu) in [#7274](https://github.com/pydantic/pydantic/pull/7274) +* Make `strict` config overridable in field for Path by [@hramezani](https://github.com/hramezani) in [#7281](https://github.com/pydantic/pydantic/pull/7281) +* Use `ser_json_` on default in `GenerateJsonSchema` by [@Kludex](https://github.com/Kludex) in [#7269](https://github.com/pydantic/pydantic/pull/7269) +* Adding a check that alias is validated as an identifier for Python by [@andree0](https://github.com/andree0) in [#7319](https://github.com/pydantic/pydantic/pull/7319) +* Raise an error when computed field overrides field by [@sydney-runkle](https://github.com/sydney-runkle) in [#7346](https://github.com/pydantic/pydantic/pull/7346) +* Fix applying `SkipValidation` to referenced schemas by [@adriangb](https://github.com/adriangb) in [#7381](https://github.com/pydantic/pydantic/pull/7381) +* Enforce behavior of private attributes having double leading underscore by [@lig](https://github.com/lig) in [#7265](https://github.com/pydantic/pydantic/pull/7265) +* Standardize `__get_pydantic_core_schema__` signature by [@hramezani](https://github.com/hramezani) in [#7415](https://github.com/pydantic/pydantic/pull/7415) +* Fix generic dataclass fields mutation bug (when using `TypeAdapter`) by [@sydney-runkle](https://github.com/sydney-runkle) in [#7435](https://github.com/pydantic/pydantic/pull/7435) +* Fix `TypeError` on `model_validator` in `wrap` mode by [@pmmmwh](https://github.com/pmmmwh) in [#7496](https://github.com/pydantic/pydantic/pull/7496) +* Improve enum error message by [@hramezani](https://github.com/hramezani) in [#7506](https://github.com/pydantic/pydantic/pull/7506) +* Make `repr` work for instances that failed initialization when handling `ValidationError`s by [@dmontagu](https://github.com/dmontagu) in [#7439](https://github.com/pydantic/pydantic/pull/7439) +* Fixed a regular expression denial of service issue by limiting whitespaces by [@prodigysml](https://github.com/prodigysml) in [#7360](https://github.com/pydantic/pydantic/pull/7360) +* Fix handling of `UUID` values having `UUID.version=None` by [@lig](https://github.com/lig) in [#7566](https://github.com/pydantic/pydantic/pull/7566) +* Fix `__iter__` returning private `cached_property` info by [@sydney-runkle](https://github.com/sydney-runkle) in [#7570](https://github.com/pydantic/pydantic/pull/7570) +* Improvements to version info message by [@samuelcolvin](https://github.com/samuelcolvin) in [#7594](https://github.com/pydantic/pydantic/pull/7594) + +### New Contributors +* [@15498th](https://github.com/15498th) made their first contribution in [#7238](https://github.com/pydantic/pydantic/pull/7238) +* [@GabrielCappelli](https://github.com/GabrielCappelli) made their first contribution in [#7213](https://github.com/pydantic/pydantic/pull/7213) +* [@tobni](https://github.com/tobni) made their first contribution in [#7184](https://github.com/pydantic/pydantic/pull/7184) +* [@redruin1](https://github.com/redruin1) made their first contribution in [#7282](https://github.com/pydantic/pydantic/pull/7282) +* [@FacerAin](https://github.com/FacerAin) made their first contribution in [#7288](https://github.com/pydantic/pydantic/pull/7288) +* [@acdha](https://github.com/acdha) made their first contribution in [#7297](https://github.com/pydantic/pydantic/pull/7297) +* [@andree0](https://github.com/andree0) made their first contribution in [#7319](https://github.com/pydantic/pydantic/pull/7319) +* [@gordonhart](https://github.com/gordonhart) made their first contribution in [#7375](https://github.com/pydantic/pydantic/pull/7375) +* [@pmmmwh](https://github.com/pmmmwh) made their first contribution in [#7496](https://github.com/pydantic/pydantic/pull/7496) +* [@disrupted](https://github.com/disrupted) made their first contribution in [#7299](https://github.com/pydantic/pydantic/pull/7299) +* [@prodigysml](https://github.com/prodigysml) made their first contribution in [#7360](https://github.com/pydantic/pydantic/pull/7360) + +## v2.3.0 (2023-08-23) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.3.0) + +* 🔥 Remove orphaned changes file from repo by [@lig](https://github.com/lig) in [#7168](https://github.com/pydantic/pydantic/pull/7168) +* Add copy button on documentation by [@Kludex](https://github.com/Kludex) in [#7190](https://github.com/pydantic/pydantic/pull/7190) +* Fix docs on JSON type by [@Kludex](https://github.com/Kludex) in [#7189](https://github.com/pydantic/pydantic/pull/7189) +* Update mypy 1.5.0 to 1.5.1 in CI by [@hramezani](https://github.com/hramezani) in [#7191](https://github.com/pydantic/pydantic/pull/7191) +* fix download links badge by [@samuelcolvin](https://github.com/samuelcolvin) in [#7200](https://github.com/pydantic/pydantic/pull/7200) +* add 2.2.1 to changelog by [@samuelcolvin](https://github.com/samuelcolvin) in [#7212](https://github.com/pydantic/pydantic/pull/7212) +* Make ModelWrapValidator protocols generic by [@dmontagu](https://github.com/dmontagu) in [#7154](https://github.com/pydantic/pydantic/pull/7154) +* Correct `Field(..., exclude: bool)` docs by [@samuelcolvin](https://github.com/samuelcolvin) in [#7214](https://github.com/pydantic/pydantic/pull/7214) +* Make shadowing attributes a warning instead of an error by [@adriangb](https://github.com/adriangb) in [#7193](https://github.com/pydantic/pydantic/pull/7193) +* Document `Base64Str` and `Base64Bytes` by [@Kludex](https://github.com/Kludex) in [#7192](https://github.com/pydantic/pydantic/pull/7192) +* Fix `config.defer_build` for serialization first cases by [@samuelcolvin](https://github.com/samuelcolvin) in [#7024](https://github.com/pydantic/pydantic/pull/7024) +* clean Model docstrings in JSON Schema by [@samuelcolvin](https://github.com/samuelcolvin) in [#7210](https://github.com/pydantic/pydantic/pull/7210) +* fix [#7228](https://github.com/pydantic/pydantic/pull/7228) (typo): docs in `validators.md` to correct `validate_default` kwarg by [@lmmx](https://github.com/lmmx) in [#7229](https://github.com/pydantic/pydantic/pull/7229) +* ✅ Implement `tzinfo.fromutc` method for `TzInfo` in `pydantic-core` by [@lig](https://github.com/lig) in [#7019](https://github.com/pydantic/pydantic/pull/7019) +* Support `__get_validators__` by [@hramezani](https://github.com/hramezani) in [#7197](https://github.com/pydantic/pydantic/pull/7197) + +## v2.2.1 (2023-08-18) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.2.1) + +* Make `xfail`ing test for root model extra stop `xfail`ing by [@dmontagu](https://github.com/dmontagu) in [#6937](https://github.com/pydantic/pydantic/pull/6937) +* Optimize recursion detection by stopping on the second visit for the same object by [@mciucu](https://github.com/mciucu) in [#7160](https://github.com/pydantic/pydantic/pull/7160) +* fix link in docs by [@tlambert03](https://github.com/tlambert03) in [#7166](https://github.com/pydantic/pydantic/pull/7166) +* Replace MiMalloc w/ default allocator by [@adriangb](https://github.com/adriangb) in [pydantic/pydantic-core#900](https://github.com/pydantic/pydantic-core/pull/900) +* Bump pydantic-core to 2.6.1 and prepare 2.2.1 release by [@adriangb](https://github.com/adriangb) in [#7176](https://github.com/pydantic/pydantic/pull/7176) + +## v2.2.0 (2023-08-17) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.2.0) + +* Split "pipx install" setup command into two commands on the documentation site by [@nomadmtb](https://github.com/nomadmtb) in [#6869](https://github.com/pydantic/pydantic/pull/6869) +* Deprecate `Field.include` by [@hramezani](https://github.com/hramezani) in [#6852](https://github.com/pydantic/pydantic/pull/6852) +* Fix typo in default factory error msg by [@hramezani](https://github.com/hramezani) in [#6880](https://github.com/pydantic/pydantic/pull/6880) +* Simplify handling of typing.Annotated in GenerateSchema by [@dmontagu](https://github.com/dmontagu) in [#6887](https://github.com/pydantic/pydantic/pull/6887) +* Re-enable fastapi tests in CI by [@dmontagu](https://github.com/dmontagu) in [#6883](https://github.com/pydantic/pydantic/pull/6883) +* Make it harder to hit collisions with json schema defrefs by [@dmontagu](https://github.com/dmontagu) in [#6566](https://github.com/pydantic/pydantic/pull/6566) +* Cleaner error for invalid input to `Path` fields by [@samuelcolvin](https://github.com/samuelcolvin) in [#6903](https://github.com/pydantic/pydantic/pull/6903) +* :memo: support Coordinate Type by [@yezz123](https://github.com/yezz123) in [#6906](https://github.com/pydantic/pydantic/pull/6906) +* Fix `ForwardRef` wrapper for py 3.10.0 (shim until bpo-45166) by [@randomir](https://github.com/randomir) in [#6919](https://github.com/pydantic/pydantic/pull/6919) +* Fix misbehavior related to copying of RootModel by [@dmontagu](https://github.com/dmontagu) in [#6918](https://github.com/pydantic/pydantic/pull/6918) +* Fix issue with recursion error caused by ParamSpec by [@dmontagu](https://github.com/dmontagu) in [#6923](https://github.com/pydantic/pydantic/pull/6923) +* Add section about Constrained classes to the Migration Guide by [@Kludex](https://github.com/Kludex) in [#6924](https://github.com/pydantic/pydantic/pull/6924) +* Use `main` branch for badge links by [@Viicos](https://github.com/Viicos) in [#6925](https://github.com/pydantic/pydantic/pull/6925) +* Add test for v1/v2 Annotated discrepancy by [@carlbordum](https://github.com/carlbordum) in [#6926](https://github.com/pydantic/pydantic/pull/6926) +* Make the v1 mypy plugin work with both v1 and v2 by [@dmontagu](https://github.com/dmontagu) in [#6921](https://github.com/pydantic/pydantic/pull/6921) +* Fix issue where generic models couldn't be parametrized with BaseModel by [@dmontagu](https://github.com/dmontagu) in [#6933](https://github.com/pydantic/pydantic/pull/6933) +* Remove xfail for discriminated union with alias by [@dmontagu](https://github.com/dmontagu) in [#6938](https://github.com/pydantic/pydantic/pull/6938) +* add field_serializer to computed_field by [@andresliszt](https://github.com/andresliszt) in [#6965](https://github.com/pydantic/pydantic/pull/6965) +* Use union_schema with Type[Union[...]] by [@JeanArhancet](https://github.com/JeanArhancet) in [#6952](https://github.com/pydantic/pydantic/pull/6952) +* Fix inherited typeddict attributes / config by [@adriangb](https://github.com/adriangb) in [#6981](https://github.com/pydantic/pydantic/pull/6981) +* fix dataclass annotated before validator called twice by [@davidhewitt](https://github.com/davidhewitt) in [#6998](https://github.com/pydantic/pydantic/pull/6998) +* Update test-fastapi deselected tests by [@hramezani](https://github.com/hramezani) in [#7014](https://github.com/pydantic/pydantic/pull/7014) +* Fix validator doc format by [@hramezani](https://github.com/hramezani) in [#7015](https://github.com/pydantic/pydantic/pull/7015) +* Fix typo in docstring of model_json_schema by [@AdamVinch-Federated](https://github.com/AdamVinch-Federated) in [#7032](https://github.com/pydantic/pydantic/pull/7032) +* remove unused "type ignores" with pyright by [@samuelcolvin](https://github.com/samuelcolvin) in [#7026](https://github.com/pydantic/pydantic/pull/7026) +* Add benchmark representing FastAPI startup time by [@adriangb](https://github.com/adriangb) in [#7030](https://github.com/pydantic/pydantic/pull/7030) +* Fix json_encoders for Enum subclasses by [@adriangb](https://github.com/adriangb) in [#7029](https://github.com/pydantic/pydantic/pull/7029) +* Update docstring of `ser_json_bytes` regarding base64 encoding by [@Viicos](https://github.com/Viicos) in [#7052](https://github.com/pydantic/pydantic/pull/7052) +* Allow `@validate_call` to work on async methods by [@adriangb](https://github.com/adriangb) in [#7046](https://github.com/pydantic/pydantic/pull/7046) +* Fix: mypy error with `Settings` and `SettingsConfigDict` by [@JeanArhancet](https://github.com/JeanArhancet) in [#7002](https://github.com/pydantic/pydantic/pull/7002) +* Fix some typos (repeated words and it's/its) by [@eumiro](https://github.com/eumiro) in [#7063](https://github.com/pydantic/pydantic/pull/7063) +* Fix the typo in docstring by [@harunyasar](https://github.com/harunyasar) in [#7062](https://github.com/pydantic/pydantic/pull/7062) +* Docs: Fix broken URL in the pydantic-settings package recommendation by [@swetjen](https://github.com/swetjen) in [#6995](https://github.com/pydantic/pydantic/pull/6995) +* Handle constraints being applied to schemas that don't accept it by [@adriangb](https://github.com/adriangb) in [#6951](https://github.com/pydantic/pydantic/pull/6951) +* Replace almost_equal_floats with math.isclose by [@eumiro](https://github.com/eumiro) in [#7082](https://github.com/pydantic/pydantic/pull/7082) +* bump pydantic-core to 2.5.0 by [@davidhewitt](https://github.com/davidhewitt) in [#7077](https://github.com/pydantic/pydantic/pull/7077) +* Add `short_version` and use it in links by [@hramezani](https://github.com/hramezani) in [#7115](https://github.com/pydantic/pydantic/pull/7115) +* 📝 Add usage link to `RootModel` by [@Kludex](https://github.com/Kludex) in [#7113](https://github.com/pydantic/pydantic/pull/7113) +* Revert "Fix default port for mongosrv DSNs (#6827)" by [@Kludex](https://github.com/Kludex) in [#7116](https://github.com/pydantic/pydantic/pull/7116) +* Clarify validate_default and _Unset handling in usage docs and migration guide by [@benbenbang](https://github.com/benbenbang) in [#6950](https://github.com/pydantic/pydantic/pull/6950) +* Tweak documentation of `Field.exclude` by [@Viicos](https://github.com/Viicos) in [#7086](https://github.com/pydantic/pydantic/pull/7086) +* Do not require `validate_assignment` to use `Field.frozen` by [@Viicos](https://github.com/Viicos) in [#7103](https://github.com/pydantic/pydantic/pull/7103) +* tweaks to `_core_utils` by [@samuelcolvin](https://github.com/samuelcolvin) in [#7040](https://github.com/pydantic/pydantic/pull/7040) +* Make DefaultDict working with set by [@hramezani](https://github.com/hramezani) in [#7126](https://github.com/pydantic/pydantic/pull/7126) +* Don't always require typing.Generic as a base for partially parametrized models by [@dmontagu](https://github.com/dmontagu) in [#7119](https://github.com/pydantic/pydantic/pull/7119) +* Fix issue with JSON schema incorrectly using parent class core schema by [@dmontagu](https://github.com/dmontagu) in [#7020](https://github.com/pydantic/pydantic/pull/7020) +* Fix xfailed test related to TypedDict and alias_generator by [@dmontagu](https://github.com/dmontagu) in [#6940](https://github.com/pydantic/pydantic/pull/6940) +* Improve error message for NameEmail by [@dmontagu](https://github.com/dmontagu) in [#6939](https://github.com/pydantic/pydantic/pull/6939) +* Fix generic computed fields by [@dmontagu](https://github.com/dmontagu) in [#6988](https://github.com/pydantic/pydantic/pull/6988) +* Reflect namedtuple default values during validation by [@dmontagu](https://github.com/dmontagu) in [#7144](https://github.com/pydantic/pydantic/pull/7144) +* Update dependencies, fix pydantic-core usage, fix CI issues by [@dmontagu](https://github.com/dmontagu) in [#7150](https://github.com/pydantic/pydantic/pull/7150) +* Add mypy 1.5.0 by [@hramezani](https://github.com/hramezani) in [#7118](https://github.com/pydantic/pydantic/pull/7118) +* Handle non-json native enum values by [@adriangb](https://github.com/adriangb) in [#7056](https://github.com/pydantic/pydantic/pull/7056) +* document `round_trip` in Json type documentation by [@jc-louis](https://github.com/jc-louis) in [#7137](https://github.com/pydantic/pydantic/pull/7137) +* Relax signature checks to better support builtins and C extension functions as validators by [@adriangb](https://github.com/adriangb) in [#7101](https://github.com/pydantic/pydantic/pull/7101) +* add union_mode='left_to_right' by [@davidhewitt](https://github.com/davidhewitt) in [#7151](https://github.com/pydantic/pydantic/pull/7151) +* Include an error message hint for inherited ordering by [@yvalencia91](https://github.com/yvalencia91) in [#7124](https://github.com/pydantic/pydantic/pull/7124) +* Fix one docs link and resolve some warnings for two others by [@dmontagu](https://github.com/dmontagu) in [#7153](https://github.com/pydantic/pydantic/pull/7153) +* Include Field extra keys name in warning by [@hramezani](https://github.com/hramezani) in [#7136](https://github.com/pydantic/pydantic/pull/7136) + +## v2.1.1 (2023-07-25) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.1.1) + +* Skip FieldInfo merging when unnecessary by [@dmontagu](https://github.com/dmontagu) in [#6862](https://github.com/pydantic/pydantic/pull/6862) + +## v2.1.0 (2023-07-25) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.1.0) + +* Add `StringConstraints` for use as Annotated metadata by [@adriangb](https://github.com/adriangb) in [#6605](https://github.com/pydantic/pydantic/pull/6605) +* Try to fix intermittently failing CI by [@adriangb](https://github.com/adriangb) in [#6683](https://github.com/pydantic/pydantic/pull/6683) +* Remove redundant example of optional vs default. by [@ehiggs-deliverect](https://github.com/ehiggs-deliverect) in [#6676](https://github.com/pydantic/pydantic/pull/6676) +* Docs update by [@samuelcolvin](https://github.com/samuelcolvin) in [#6692](https://github.com/pydantic/pydantic/pull/6692) +* Remove the Validate always section in validator docs by [@adriangb](https://github.com/adriangb) in [#6679](https://github.com/pydantic/pydantic/pull/6679) +* Fix recursion error in json schema generation by [@adriangb](https://github.com/adriangb) in [#6720](https://github.com/pydantic/pydantic/pull/6720) +* Fix incorrect subclass check for secretstr by [@AlexVndnblcke](https://github.com/AlexVndnblcke) in [#6730](https://github.com/pydantic/pydantic/pull/6730) +* update pdm / pdm lockfile to 2.8.0 by [@davidhewitt](https://github.com/davidhewitt) in [#6714](https://github.com/pydantic/pydantic/pull/6714) +* unpin pdm on more CI jobs by [@davidhewitt](https://github.com/davidhewitt) in [#6755](https://github.com/pydantic/pydantic/pull/6755) +* improve source locations for auxiliary packages in docs by [@davidhewitt](https://github.com/davidhewitt) in [#6749](https://github.com/pydantic/pydantic/pull/6749) +* Assume builtins don't accept an info argument by [@adriangb](https://github.com/adriangb) in [#6754](https://github.com/pydantic/pydantic/pull/6754) +* Fix bug where calling `help(BaseModelSubclass)` raises errors by [@hramezani](https://github.com/hramezani) in [#6758](https://github.com/pydantic/pydantic/pull/6758) +* Fix mypy plugin handling of `@model_validator(mode="after")` by [@ljodal](https://github.com/ljodal) in [#6753](https://github.com/pydantic/pydantic/pull/6753) +* update pydantic-core to 2.3.1 by [@davidhewitt](https://github.com/davidhewitt) in [#6756](https://github.com/pydantic/pydantic/pull/6756) +* Mypy plugin for settings by [@hramezani](https://github.com/hramezani) in [#6760](https://github.com/pydantic/pydantic/pull/6760) +* Use `contentSchema` keyword for JSON schema by [@dmontagu](https://github.com/dmontagu) in [#6715](https://github.com/pydantic/pydantic/pull/6715) +* fast-path checking finite decimals by [@davidhewitt](https://github.com/davidhewitt) in [#6769](https://github.com/pydantic/pydantic/pull/6769) +* Docs update by [@samuelcolvin](https://github.com/samuelcolvin) in [#6771](https://github.com/pydantic/pydantic/pull/6771) +* Improve json schema doc by [@hramezani](https://github.com/hramezani) in [#6772](https://github.com/pydantic/pydantic/pull/6772) +* Update validator docs by [@adriangb](https://github.com/adriangb) in [#6695](https://github.com/pydantic/pydantic/pull/6695) +* Fix typehint for wrap validator by [@dmontagu](https://github.com/dmontagu) in [#6788](https://github.com/pydantic/pydantic/pull/6788) +* 🐛 Fix validation warning for unions of Literal and other type by [@lig](https://github.com/lig) in [#6628](https://github.com/pydantic/pydantic/pull/6628) +* Update documentation for generics support in V2 by [@tpdorsey](https://github.com/tpdorsey) in [#6685](https://github.com/pydantic/pydantic/pull/6685) +* add pydantic-core build info to `version_info()` by [@samuelcolvin](https://github.com/samuelcolvin) in [#6785](https://github.com/pydantic/pydantic/pull/6785) +* Fix pydantic dataclasses that use slots with default values by [@dmontagu](https://github.com/dmontagu) in [#6796](https://github.com/pydantic/pydantic/pull/6796) +* Fix inheritance of hash function for frozen models by [@dmontagu](https://github.com/dmontagu) in [#6789](https://github.com/pydantic/pydantic/pull/6789) +* ✨ Add `SkipJsonSchema` annotation by [@Kludex](https://github.com/Kludex) in [#6653](https://github.com/pydantic/pydantic/pull/6653) +* Error if an invalid field name is used with Field by [@dmontagu](https://github.com/dmontagu) in [#6797](https://github.com/pydantic/pydantic/pull/6797) +* Add `GenericModel` to `MOVED_IN_V2` by [@adriangb](https://github.com/adriangb) in [#6776](https://github.com/pydantic/pydantic/pull/6776) +* Remove unused code from `docs/usage/types/custom.md` by [@hramezani](https://github.com/hramezani) in [#6803](https://github.com/pydantic/pydantic/pull/6803) +* Fix `float` -> `Decimal` coercion precision loss by [@adriangb](https://github.com/adriangb) in [#6810](https://github.com/pydantic/pydantic/pull/6810) +* remove email validation from the north star benchmark by [@davidhewitt](https://github.com/davidhewitt) in [#6816](https://github.com/pydantic/pydantic/pull/6816) +* Fix link to mypy by [@progsmile](https://github.com/progsmile) in [#6824](https://github.com/pydantic/pydantic/pull/6824) +* Improve initialization hooks example by [@hramezani](https://github.com/hramezani) in [#6822](https://github.com/pydantic/pydantic/pull/6822) +* Fix default port for mongosrv DSNs by [@dmontagu](https://github.com/dmontagu) in [#6827](https://github.com/pydantic/pydantic/pull/6827) +* Improve API documentation, in particular more links between usage and API docs by [@samuelcolvin](https://github.com/samuelcolvin) in [#6780](https://github.com/pydantic/pydantic/pull/6780) +* update pydantic-core to 2.4.0 by [@davidhewitt](https://github.com/davidhewitt) in [#6831](https://github.com/pydantic/pydantic/pull/6831) +* Fix `annotated_types.MaxLen` validator for custom sequence types by [@ImogenBits](https://github.com/ImogenBits) in [#6809](https://github.com/pydantic/pydantic/pull/6809) +* Update V1 by [@hramezani](https://github.com/hramezani) in [#6833](https://github.com/pydantic/pydantic/pull/6833) +* Make it so callable JSON schema extra works by [@dmontagu](https://github.com/dmontagu) in [#6798](https://github.com/pydantic/pydantic/pull/6798) +* Fix serialization issue with `InstanceOf` by [@dmontagu](https://github.com/dmontagu) in [#6829](https://github.com/pydantic/pydantic/pull/6829) +* Add back support for `json_encoders` by [@adriangb](https://github.com/adriangb) in [#6811](https://github.com/pydantic/pydantic/pull/6811) +* Update field annotations when building the schema by [@dmontagu](https://github.com/dmontagu) in [#6838](https://github.com/pydantic/pydantic/pull/6838) +* Use `WeakValueDictionary` to fix generic memory leak by [@dmontagu](https://github.com/dmontagu) in [#6681](https://github.com/pydantic/pydantic/pull/6681) +* Add `config.defer_build` to optionally make model building lazy by [@samuelcolvin](https://github.com/samuelcolvin) in [#6823](https://github.com/pydantic/pydantic/pull/6823) +* delegate `UUID` serialization to pydantic-core by [@davidhewitt](https://github.com/davidhewitt) in [#6850](https://github.com/pydantic/pydantic/pull/6850) +* Update `json_encoders` docs by [@adriangb](https://github.com/adriangb) in [#6848](https://github.com/pydantic/pydantic/pull/6848) +* Fix error message for `staticmethod`/`classmethod` order with validate_call by [@dmontagu](https://github.com/dmontagu) in [#6686](https://github.com/pydantic/pydantic/pull/6686) +* Improve documentation for `Config` by [@samuelcolvin](https://github.com/samuelcolvin) in [#6847](https://github.com/pydantic/pydantic/pull/6847) +* Update serialization doc to mention `Field.exclude` takes priority over call-time `include/exclude` by [@hramezani](https://github.com/hramezani) in [#6851](https://github.com/pydantic/pydantic/pull/6851) +* Allow customizing core schema generation by making `GenerateSchema` public by [@adriangb](https://github.com/adriangb) in [#6737](https://github.com/pydantic/pydantic/pull/6737) + +## v2.0.3 (2023-07-05) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.0.3) + +* Mention PyObject (v1) moving to ImportString (v2) in migration doc by [@slafs](https://github.com/slafs) in [#6456](https://github.com/pydantic/pydantic/pull/6456) +* Fix release-tweet CI by [@Kludex](https://github.com/Kludex) in [#6461](https://github.com/pydantic/pydantic/pull/6461) +* Revise the section on required / optional / nullable fields. by [@ybressler](https://github.com/ybressler) in [#6468](https://github.com/pydantic/pydantic/pull/6468) +* Warn if a type hint is not in fact a type by [@adriangb](https://github.com/adriangb) in [#6479](https://github.com/pydantic/pydantic/pull/6479) +* Replace TransformSchema with GetPydanticSchema by [@dmontagu](https://github.com/dmontagu) in [#6484](https://github.com/pydantic/pydantic/pull/6484) +* Fix the un-hashability of various annotation types, for use in caching generic containers by [@dmontagu](https://github.com/dmontagu) in [#6480](https://github.com/pydantic/pydantic/pull/6480) +* PYD-164: Rework custom types docs by [@adriangb](https://github.com/adriangb) in [#6490](https://github.com/pydantic/pydantic/pull/6490) +* Fix ci by [@adriangb](https://github.com/adriangb) in [#6507](https://github.com/pydantic/pydantic/pull/6507) +* Fix forward ref in generic by [@adriangb](https://github.com/adriangb) in [#6511](https://github.com/pydantic/pydantic/pull/6511) +* Fix generation of serialization JSON schemas for core_schema.ChainSchema by [@dmontagu](https://github.com/dmontagu) in [#6515](https://github.com/pydantic/pydantic/pull/6515) +* Document the change in `Field.alias` behavior in Pydantic V2 by [@hramezani](https://github.com/hramezani) in [#6508](https://github.com/pydantic/pydantic/pull/6508) +* Give better error message attempting to compute the json schema of a model with undefined fields by [@dmontagu](https://github.com/dmontagu) in [#6519](https://github.com/pydantic/pydantic/pull/6519) +* Document `alias_priority` by [@tpdorsey](https://github.com/tpdorsey) in [#6520](https://github.com/pydantic/pydantic/pull/6520) +* Add redirect for types documentation by [@tpdorsey](https://github.com/tpdorsey) in [#6513](https://github.com/pydantic/pydantic/pull/6513) +* Allow updating docs without release by [@samuelcolvin](https://github.com/samuelcolvin) in [#6551](https://github.com/pydantic/pydantic/pull/6551) +* Ensure docs tests always run in the right folder by [@dmontagu](https://github.com/dmontagu) in [#6487](https://github.com/pydantic/pydantic/pull/6487) +* Defer evaluation of return type hints for serializer functions by [@dmontagu](https://github.com/dmontagu) in [#6516](https://github.com/pydantic/pydantic/pull/6516) +* Disable E501 from Ruff and rely on just Black by [@adriangb](https://github.com/adriangb) in [#6552](https://github.com/pydantic/pydantic/pull/6552) +* Update JSON Schema documentation for V2 by [@tpdorsey](https://github.com/tpdorsey) in [#6492](https://github.com/pydantic/pydantic/pull/6492) +* Add documentation of cyclic reference handling by [@dmontagu](https://github.com/dmontagu) in [#6493](https://github.com/pydantic/pydantic/pull/6493) +* Remove the need for change files by [@samuelcolvin](https://github.com/samuelcolvin) in [#6556](https://github.com/pydantic/pydantic/pull/6556) +* add "north star" benchmark by [@davidhewitt](https://github.com/davidhewitt) in [#6547](https://github.com/pydantic/pydantic/pull/6547) +* Update Dataclasses docs by [@tpdorsey](https://github.com/tpdorsey) in [#6470](https://github.com/pydantic/pydantic/pull/6470) +* ♻️ Use different error message on v1 redirects by [@Kludex](https://github.com/Kludex) in [#6595](https://github.com/pydantic/pydantic/pull/6595) +* ⬆ Upgrade `pydantic-core` to v2.2.0 by [@lig](https://github.com/lig) in [#6589](https://github.com/pydantic/pydantic/pull/6589) +* Fix serialization for IPvAny by [@dmontagu](https://github.com/dmontagu) in [#6572](https://github.com/pydantic/pydantic/pull/6572) +* Improve CI by using PDM instead of pip to install typing-extensions by [@adriangb](https://github.com/adriangb) in [#6602](https://github.com/pydantic/pydantic/pull/6602) +* Add `enum` error type docs by [@lig](https://github.com/lig) in [#6603](https://github.com/pydantic/pydantic/pull/6603) +* 🐛 Fix `max_length` for unicode strings by [@lig](https://github.com/lig) in [#6559](https://github.com/pydantic/pydantic/pull/6559) +* Add documentation for accessing features via `pydantic.v1` by [@tpdorsey](https://github.com/tpdorsey) in [#6604](https://github.com/pydantic/pydantic/pull/6604) +* Include extra when iterating over a model by [@adriangb](https://github.com/adriangb) in [#6562](https://github.com/pydantic/pydantic/pull/6562) +* Fix typing of model_validator by [@adriangb](https://github.com/adriangb) in [#6514](https://github.com/pydantic/pydantic/pull/6514) +* Touch up Decimal validator by [@adriangb](https://github.com/adriangb) in [#6327](https://github.com/pydantic/pydantic/pull/6327) +* Fix various docstrings using fixed pytest-examples by [@dmontagu](https://github.com/dmontagu) in [#6607](https://github.com/pydantic/pydantic/pull/6607) +* Handle function validators in a discriminated union by [@dmontagu](https://github.com/dmontagu) in [#6570](https://github.com/pydantic/pydantic/pull/6570) +* Review json_schema.md by [@tpdorsey](https://github.com/tpdorsey) in [#6608](https://github.com/pydantic/pydantic/pull/6608) +* Make validate_call work on basemodel methods by [@dmontagu](https://github.com/dmontagu) in [#6569](https://github.com/pydantic/pydantic/pull/6569) +* add test for big int json serde by [@davidhewitt](https://github.com/davidhewitt) in [#6614](https://github.com/pydantic/pydantic/pull/6614) +* Fix pydantic dataclass problem with dataclasses.field default_factory by [@hramezani](https://github.com/hramezani) in [#6616](https://github.com/pydantic/pydantic/pull/6616) +* Fixed mypy type inference for TypeAdapter by [@zakstucke](https://github.com/zakstucke) in [#6617](https://github.com/pydantic/pydantic/pull/6617) +* Make it work to use None as a generic parameter by [@dmontagu](https://github.com/dmontagu) in [#6609](https://github.com/pydantic/pydantic/pull/6609) +* Make it work to use `$ref` as an alias by [@dmontagu](https://github.com/dmontagu) in [#6568](https://github.com/pydantic/pydantic/pull/6568) +* add note to migration guide about changes to `AnyUrl` etc by [@davidhewitt](https://github.com/davidhewitt) in [#6618](https://github.com/pydantic/pydantic/pull/6618) +* 🐛 Support defining `json_schema_extra` on `RootModel` using `Field` by [@lig](https://github.com/lig) in [#6622](https://github.com/pydantic/pydantic/pull/6622) +* Update pre-commit to prevent commits to main branch on accident by [@dmontagu](https://github.com/dmontagu) in [#6636](https://github.com/pydantic/pydantic/pull/6636) +* Fix PDM CI for python 3.7 on MacOS/windows by [@dmontagu](https://github.com/dmontagu) in [#6627](https://github.com/pydantic/pydantic/pull/6627) +* Produce more accurate signatures for pydantic dataclasses by [@dmontagu](https://github.com/dmontagu) in [#6633](https://github.com/pydantic/pydantic/pull/6633) +* Updates to Url types for Pydantic V2 by [@tpdorsey](https://github.com/tpdorsey) in [#6638](https://github.com/pydantic/pydantic/pull/6638) +* Fix list markdown in `transform` docstring by [@StefanBRas](https://github.com/StefanBRas) in [#6649](https://github.com/pydantic/pydantic/pull/6649) +* simplify slots_dataclass construction to appease mypy by [@davidhewitt](https://github.com/davidhewitt) in [#6639](https://github.com/pydantic/pydantic/pull/6639) +* Update TypedDict schema generation docstring by [@adriangb](https://github.com/adriangb) in [#6651](https://github.com/pydantic/pydantic/pull/6651) +* Detect and lint-error for prints by [@dmontagu](https://github.com/dmontagu) in [#6655](https://github.com/pydantic/pydantic/pull/6655) +* Add xfailing test for pydantic-core PR 766 by [@dmontagu](https://github.com/dmontagu) in [#6641](https://github.com/pydantic/pydantic/pull/6641) +* Ignore unrecognized fields from dataclasses metadata by [@dmontagu](https://github.com/dmontagu) in [#6634](https://github.com/pydantic/pydantic/pull/6634) +* Make non-existent class getattr a mypy error by [@dmontagu](https://github.com/dmontagu) in [#6658](https://github.com/pydantic/pydantic/pull/6658) +* Update pydantic-core to 2.3.0 by [@hramezani](https://github.com/hramezani) in [#6648](https://github.com/pydantic/pydantic/pull/6648) +* Use OrderedDict from typing_extensions by [@dmontagu](https://github.com/dmontagu) in [#6664](https://github.com/pydantic/pydantic/pull/6664) +* Fix typehint for JSON schema extra callable by [@dmontagu](https://github.com/dmontagu) in [#6659](https://github.com/pydantic/pydantic/pull/6659) + +## v2.0.2 (2023-07-05) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.0.2) + +* Fix bug where round-trip pickling/unpickling a `RootModel` would change the value of `__dict__`, [#6457](https://github.com/pydantic/pydantic/pull/6457) by [@dmontagu](https://github.com/dmontagu) +* Allow single-item discriminated unions, [#6405](https://github.com/pydantic/pydantic/pull/6405) by [@dmontagu](https://github.com/dmontagu) +* Fix issue with union parsing of enums, [#6440](https://github.com/pydantic/pydantic/pull/6440) by [@dmontagu](https://github.com/dmontagu) +* Docs: Fixed `constr` documentation, renamed old `regex` to new `pattern`, [#6452](https://github.com/pydantic/pydantic/pull/6452) by [@miili](https://github.com/miili) +* Change `GenerateJsonSchema.generate_definitions` signature, [#6436](https://github.com/pydantic/pydantic/pull/6436) by [@dmontagu](https://github.com/dmontagu) + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0.2) + +## v2.0.1 (2023-07-04) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.0.1) + +First patch release of Pydantic V2 + +* Extra fields added via `setattr` (i.e. `m.some_extra_field = 'extra_value'`) + are added to `.model_extra` if `model_config` `extra='allowed'`. Fixed [#6333](https://github.com/pydantic/pydantic/pull/6333), [#6365](https://github.com/pydantic/pydantic/pull/6365) by [@aaraney](https://github.com/aaraney) +* Automatically unpack JSON schema '$ref' for custom types, [#6343](https://github.com/pydantic/pydantic/pull/6343) by [@adriangb](https://github.com/adriangb) +* Fix tagged unions multiple processing in submodels, [#6340](https://github.com/pydantic/pydantic/pull/6340) by [@suharnikov](https://github.com/suharnikov) + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0.1) + +## v2.0 (2023-06-30) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.0) + +Pydantic V2 is here! :tada: + +See [this post](https://docs.pydantic.dev/2.0/blog/pydantic-v2-final/) for more details. + +## v2.0b3 (2023-06-16) + +Third beta pre-release of Pydantic V2 + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0b3) + +## v2.0b2 (2023-06-03) + +Add `from_attributes` runtime flag to `TypeAdapter.validate_python` and `BaseModel.model_validate`. + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0b2) + +## v2.0b1 (2023-06-01) + +First beta pre-release of Pydantic V2 + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0b1) + +## v2.0a4 (2023-05-05) + +Fourth pre-release of Pydantic V2 + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0a4) + +## v2.0a3 (2023-04-20) + +Third pre-release of Pydantic V2 + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0a3) + +## v2.0a2 (2023-04-12) + +Second pre-release of Pydantic V2 + +See the full changelog [here](https://github.com/pydantic/pydantic/releases/tag/v2.0a2) + +## v2.0a1 (2023-04-03) + +First pre-release of Pydantic V2! + +See [this post](https://docs.pydantic.dev/blog/pydantic-v2-alpha/) for more details. + +## v1.10.13 (2023-09-27) + +* Fix: Add max length check to `pydantic.validate_email`, [#7673](https://github.com/pydantic/pydantic/issues/7673) by [@hramezani](https://github.com/hramezani) +* Docs: Fix pip commands to install v1, [#6930](https://github.com/pydantic/pydantic/issues/6930) by [@chbndrhnns](https://github.com/chbndrhnns) + +## v1.10.12 (2023-07-24) + +* Fixes the `maxlen` property being dropped on `deque` validation. Happened only if the deque item has been typed. Changes the `_validate_sequence_like` func, [#6581](https://github.com/pydantic/pydantic/pull/6581) by [@maciekglowka](https://github.com/maciekglowka) + +## v1.10.11 (2023-07-04) + +* Importing create_model in tools.py through relative path instead of absolute path - so that it doesn't import V2 code when copied over to V2 branch, [#6361](https://github.com/pydantic/pydantic/pull/6361) by [@SharathHuddar](https://github.com/SharathHuddar) + +## v1.10.10 (2023-06-30) + +* Add Pydantic `Json` field support to settings management, [#6250](https://github.com/pydantic/pydantic/pull/6250) by [@hramezani](https://github.com/hramezani) +* Fixed literal validator errors for unhashable values, [#6188](https://github.com/pydantic/pydantic/pull/6188) by [@markus1978](https://github.com/markus1978) +* Fixed bug with generics receiving forward refs, [#6130](https://github.com/pydantic/pydantic/pull/6130) by [@mark-todd](https://github.com/mark-todd) +* Update install method of FastAPI for internal tests in CI, [#6117](https://github.com/pydantic/pydantic/pull/6117) by [@Kludex](https://github.com/Kludex) + +## v1.10.9 (2023-06-07) + +* Fix trailing zeros not ignored in Decimal validation, [#5968](https://github.com/pydantic/pydantic/pull/5968) by [@hramezani](https://github.com/hramezani) +* Fix mypy plugin for v1.4.0, [#5928](https://github.com/pydantic/pydantic/pull/5928) by [@cdce8p](https://github.com/cdce8p) +* Add future and past date hypothesis strategies, [#5850](https://github.com/pydantic/pydantic/pull/5850) by [@bschoenmaeckers](https://github.com/bschoenmaeckers) +* Discourage usage of Cython 3 with Pydantic 1.x, [#5845](https://github.com/pydantic/pydantic/pull/5845) by [@lig](https://github.com/lig) + +## v1.10.8 (2023-05-23) + +* Fix a bug in `Literal` usage with `typing-extension==4.6.0`, [#5826](https://github.com/pydantic/pydantic/pull/5826) by [@hramezani](https://github.com/hramezani) +* This solves the (closed) issue [#3849](https://github.com/pydantic/pydantic/pull/3849) where aliased fields that use discriminated union fail to validate when the data contains the non-aliased field name, [#5736](https://github.com/pydantic/pydantic/pull/5736) by [@benwah](https://github.com/benwah) +* Update email-validator dependency to >=2.0.0post2, [#5627](https://github.com/pydantic/pydantic/pull/5627) by [@adriangb](https://github.com/adriangb) +* update `AnyClassMethod` for changes in [python/typeshed#9771](https://github.com/python/typeshed/issues/9771), [#5505](https://github.com/pydantic/pydantic/pull/5505) by [@ITProKyle](https://github.com/ITProKyle) + +## v1.10.7 (2023-03-22) + +* Fix creating schema from model using `ConstrainedStr` with `regex` as dict key, [#5223](https://github.com/pydantic/pydantic/pull/5223) by [@matejetz](https://github.com/matejetz) +* Address bug in mypy plugin caused by explicit_package_bases=True, [#5191](https://github.com/pydantic/pydantic/pull/5191) by [@dmontagu](https://github.com/dmontagu) +* Add implicit defaults in the mypy plugin for Field with no default argument, [#5190](https://github.com/pydantic/pydantic/pull/5190) by [@dmontagu](https://github.com/dmontagu) +* Fix schema generated for Enum values used as Literals in discriminated unions, [#5188](https://github.com/pydantic/pydantic/pull/5188) by [@javibookline](https://github.com/javibookline) +* Fix mypy failures caused by the pydantic mypy plugin when users define `from_orm` in their own classes, [#5187](https://github.com/pydantic/pydantic/pull/5187) by [@dmontagu](https://github.com/dmontagu) +* Fix `InitVar` usage with pydantic dataclasses, mypy version `1.1.1` and the custom mypy plugin, [#5162](https://github.com/pydantic/pydantic/pull/5162) by [@cdce8p](https://github.com/cdce8p) + +## v1.10.6 (2023-03-08) + +* Implement logic to support creating validators from non standard callables by using defaults to identify them and unwrapping `functools.partial` and `functools.partialmethod` when checking the signature, [#5126](https://github.com/pydantic/pydantic/pull/5126) by [@JensHeinrich](https://github.com/JensHeinrich) +* Fix mypy plugin for v1.1.1, and fix `dataclass_transform` decorator for pydantic dataclasses, [#5111](https://github.com/pydantic/pydantic/pull/5111) by [@cdce8p](https://github.com/cdce8p) +* Raise `ValidationError`, not `ConfigError`, when a discriminator value is unhashable, [#4773](https://github.com/pydantic/pydantic/pull/4773) by [@kurtmckee](https://github.com/kurtmckee) + +## v1.10.5 (2023-02-15) + +* Fix broken parametrized bases handling with `GenericModel`s with complex sets of models, [#5052](https://github.com/pydantic/pydantic/pull/5052) by [@MarkusSintonen](https://github.com/MarkusSintonen) +* Invalidate mypy cache if plugin config changes, [#5007](https://github.com/pydantic/pydantic/pull/5007) by [@cdce8p](https://github.com/cdce8p) +* Fix `RecursionError` when deep-copying dataclass types wrapped by pydantic, [#4949](https://github.com/pydantic/pydantic/pull/4949) by [@mbillingr](https://github.com/mbillingr) +* Fix `X | Y` union syntax breaking `GenericModel`, [#4146](https://github.com/pydantic/pydantic/pull/4146) by [@thenx](https://github.com/thenx) +* Switch coverage badge to show coverage for this branch/release, [#5060](https://github.com/pydantic/pydantic/pull/5060) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.10.4 (2022-12-30) + +* Change dependency to `typing-extensions>=4.2.0`, [#4885](https://github.com/pydantic/pydantic/pull/4885) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.10.3 (2022-12-29) + +**NOTE: v1.10.3 was ["yanked"](https://pypi.org/help/#yanked) from PyPI due to [#4885](https://github.com/pydantic/pydantic/pull/4885) which is fixed in v1.10.4** + +* fix parsing of custom root models, [#4883](https://github.com/pydantic/pydantic/pull/4883) by [@gou177](https://github.com/gou177) +* fix: use dataclass proxy for frozen or empty dataclasses, [#4878](https://github.com/pydantic/pydantic/pull/4878) by [@PrettyWood](https://github.com/PrettyWood) +* Fix `schema` and `schema_json` on models where a model instance is a one of default values, [#4781](https://github.com/pydantic/pydantic/pull/4781) by [@Bobronium](https://github.com/Bobronium) +* Add Jina AI to sponsors on docs index page, [#4767](https://github.com/pydantic/pydantic/pull/4767) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix: support assignment on `DataclassProxy`, [#4695](https://github.com/pydantic/pydantic/pull/4695) by [@PrettyWood](https://github.com/PrettyWood) +* Add `postgresql+psycopg` as allowed scheme for `PostgreDsn` to make it usable with SQLAlchemy 2, [#4689](https://github.com/pydantic/pydantic/pull/4689) by [@morian](https://github.com/morian) +* Allow dict schemas to have both `patternProperties` and `additionalProperties`, [#4641](https://github.com/pydantic/pydantic/pull/4641) by [@jparise](https://github.com/jparise) +* Fixes error passing None for optional lists with `unique_items`, [#4568](https://github.com/pydantic/pydantic/pull/4568) by [@mfulgo](https://github.com/mfulgo) +* Fix `GenericModel` with `Callable` param raising a `TypeError`, [#4551](https://github.com/pydantic/pydantic/pull/4551) by [@mfulgo](https://github.com/mfulgo) +* Fix field regex with `StrictStr` type annotation, [#4538](https://github.com/pydantic/pydantic/pull/4538) by [@sisp](https://github.com/sisp) +* Correct `dataclass_transform` keyword argument name from `field_descriptors` to `field_specifiers`, [#4500](https://github.com/pydantic/pydantic/pull/4500) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix: avoid multiple calls of `__post_init__` when dataclasses are inherited, [#4487](https://github.com/pydantic/pydantic/pull/4487) by [@PrettyWood](https://github.com/PrettyWood) +* Reduce the size of binary wheels, [#2276](https://github.com/pydantic/pydantic/pull/2276) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.10.2 (2022-09-05) + +* **Revert Change:** Revert percent encoding of URL parts which was originally added in [#4224](https://github.com/pydantic/pydantic/pull/4224), [#4470](https://github.com/pydantic/pydantic/pull/4470) by [@samuelcolvin](https://github.com/samuelcolvin) +* Prevent long (length > `4_300`) strings/bytes as input to int fields, see + [python/cpython#95778](https://github.com/python/cpython/issues/95778) and + [CVE-2020-10735](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-10735), [#1477](https://github.com/pydantic/pydantic/pull/1477) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix: dataclass wrapper was not always called, [#4477](https://github.com/pydantic/pydantic/pull/4477) by [@PrettyWood](https://github.com/PrettyWood) +* Use `tomllib` on Python 3.11 when parsing `mypy` configuration, [#4476](https://github.com/pydantic/pydantic/pull/4476) by [@hauntsaninja](https://github.com/hauntsaninja) +* Basic fix of `GenericModel` cache to detect order of arguments in `Union` models, [#4474](https://github.com/pydantic/pydantic/pull/4474) by [@sveinugu](https://github.com/sveinugu) +* Fix mypy plugin when using bare types like `list` and `dict` as `default_factory`, [#4457](https://github.com/pydantic/pydantic/pull/4457) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.10.1 (2022-08-31) + +* Add `__hash__` method to `pydancic.color.Color` class, [#4454](https://github.com/pydantic/pydantic/pull/4454) by [@czaki](https://github.com/czaki) + +## v1.10.0 (2022-08-30) + +* Refactor the whole _pydantic_ `dataclass` decorator to really act like its standard lib equivalent. + It hence keeps `__eq__`, `__hash__`, ... and makes comparison with its non-validated version possible. + It also fixes usage of `frozen` dataclasses in fields and usage of `default_factory` in nested dataclasses. + The support of `Config.extra` has been added. + Finally, config customization directly via a `dict` is now possible, [#2557](https://github.com/pydantic/pydantic/pull/2557) by [@PrettyWood](https://github.com/PrettyWood) +

+ **BREAKING CHANGES:** + - The `compiled` boolean (whether _pydantic_ is compiled with cython) has been moved from `main.py` to `version.py` + - Now that `Config.extra` is supported, `dataclass` ignores by default extra arguments (like `BaseModel`) +* Fix PEP487 `__set_name__` protocol in `BaseModel` for PrivateAttrs, [#4407](https://github.com/pydantic/pydantic/pull/4407) by [@tlambert03](https://github.com/tlambert03) +* Allow for custom parsing of environment variables via `parse_env_var` in `Config`, [#4406](https://github.com/pydantic/pydantic/pull/4406) by [@acmiyaguchi](https://github.com/acmiyaguchi) +* Rename `master` to `main`, [#4405](https://github.com/pydantic/pydantic/pull/4405) by [@hramezani](https://github.com/hramezani) +* Fix `StrictStr` does not raise `ValidationError` when `max_length` is present in `Field`, [#4388](https://github.com/pydantic/pydantic/pull/4388) by [@hramezani](https://github.com/hramezani) +* Make `SecretStr` and `SecretBytes` hashable, [#4387](https://github.com/pydantic/pydantic/pull/4387) by [@chbndrhnns](https://github.com/chbndrhnns) +* Fix `StrictBytes` does not raise `ValidationError` when `max_length` is present in `Field`, [#4380](https://github.com/pydantic/pydantic/pull/4380) by [@JeanArhancet](https://github.com/JeanArhancet) +* Add support for bare `type`, [#4375](https://github.com/pydantic/pydantic/pull/4375) by [@hramezani](https://github.com/hramezani) +* Support Python 3.11, including binaries for 3.11 in PyPI, [#4374](https://github.com/pydantic/pydantic/pull/4374) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add support for `re.Pattern`, [#4366](https://github.com/pydantic/pydantic/pull/4366) by [@hramezani](https://github.com/hramezani) +* Fix `__post_init_post_parse__` is incorrectly passed keyword arguments when no `__post_init__` is defined, [#4361](https://github.com/pydantic/pydantic/pull/4361) by [@hramezani](https://github.com/hramezani) +* Fix implicitly importing `ForwardRef` and `Callable` from `pydantic.typing` instead of `typing` and also expose `MappingIntStrAny`, [#4358](https://github.com/pydantic/pydantic/pull/4358) by [@aminalaee](https://github.com/aminalaee) +* remove `Any` types from the `dataclass` decorator so it can be used with the `disallow_any_expr` mypy option, [#4356](https://github.com/pydantic/pydantic/pull/4356) by [@DetachHead](https://github.com/DetachHead) +* moved repo to `pydantic/pydantic`, [#4348](https://github.com/pydantic/pydantic/pull/4348) by [@yezz123](https://github.com/yezz123) +* fix "extra fields not permitted" error when dataclass with `Extra.forbid` is validated multiple times, [#4343](https://github.com/pydantic/pydantic/pull/4343) by [@detachhead](https://github.com/detachhead) +* Add Python 3.9 and 3.10 examples to docs, [#4339](https://github.com/pydantic/pydantic/pull/4339) by [@Bobronium](https://github.com/Bobronium) +* Discriminated union models now use `oneOf` instead of `anyOf` when generating OpenAPI schema definitions, [#4335](https://github.com/pydantic/pydantic/pull/4335) by [@MaxwellPayne](https://github.com/MaxwellPayne) +* Allow type checkers to infer inner type of `Json` type. `Json[list[str]]` will be now inferred as `list[str]`, + `Json[Any]` should be used instead of plain `Json`. + Runtime behaviour is not changed, [#4332](https://github.com/pydantic/pydantic/pull/4332) by [@Bobronium](https://github.com/Bobronium) +* Allow empty string aliases by using a `alias is not None` check, rather than `bool(alias)`, [#4253](https://github.com/pydantic/pydantic/pull/4253) by [@sergeytsaplin](https://github.com/sergeytsaplin) +* Update `ForwardRef`s in `Field.outer_type_`, [#4249](https://github.com/pydantic/pydantic/pull/4249) by [@JacobHayes](https://github.com/JacobHayes) +* The use of `__dataclass_transform__` has been replaced by `typing_extensions.dataclass_transform`, which is the preferred way to mark pydantic models as a dataclass under [PEP 681](https://peps.python.org/pep-0681/), [#4241](https://github.com/pydantic/pydantic/pull/4241) by [@multimeric](https://github.com/multimeric) +* Use parent model's `Config` when validating nested `NamedTuple` fields, [#4219](https://github.com/pydantic/pydantic/pull/4219) by [@synek](https://github.com/synek) +* Update `BaseModel.construct` to work with aliased Fields, [#4192](https://github.com/pydantic/pydantic/pull/4192) by [@kylebamos](https://github.com/kylebamos) +* Catch certain raised errors in `smart_deepcopy` and revert to `deepcopy` if so, [#4184](https://github.com/pydantic/pydantic/pull/4184) by [@coneybeare](https://github.com/coneybeare) +* Add `Config.anystr_upper` and `to_upper` kwarg to constr and conbytes, [#4165](https://github.com/pydantic/pydantic/pull/4165) by [@satheler](https://github.com/satheler) +* Fix JSON schema for `set` and `frozenset` when they include default values, [#4155](https://github.com/pydantic/pydantic/pull/4155) by [@aminalaee](https://github.com/aminalaee) +* Teach the mypy plugin that methods decorated by `@validator` are classmethods, [#4102](https://github.com/pydantic/pydantic/pull/4102) by [@DMRobertson](https://github.com/DMRobertson) +* Improve mypy plugin's ability to detect required fields, [#4086](https://github.com/pydantic/pydantic/pull/4086) by [@richardxia](https://github.com/richardxia) +* Support fields of type `Type[]` in schema, [#4051](https://github.com/pydantic/pydantic/pull/4051) by [@aminalaee](https://github.com/aminalaee) +* Add `default` value in JSON Schema when `const=True`, [#4031](https://github.com/pydantic/pydantic/pull/4031) by [@aminalaee](https://github.com/aminalaee) +* Adds reserved word check to signature generation logic, [#4011](https://github.com/pydantic/pydantic/pull/4011) by [@strue36](https://github.com/strue36) +* Fix Json strategy failure for the complex nested field, [#4005](https://github.com/pydantic/pydantic/pull/4005) by [@sergiosim](https://github.com/sergiosim) +* Add JSON-compatible float constraint `allow_inf_nan`, [#3994](https://github.com/pydantic/pydantic/pull/3994) by [@tiangolo](https://github.com/tiangolo) +* Remove undefined behaviour when `env_prefix` had characters in common with `env_nested_delimiter`, [#3975](https://github.com/pydantic/pydantic/pull/3975) by [@arsenron](https://github.com/arsenron) +* Support generics model with `create_model`, [#3945](https://github.com/pydantic/pydantic/pull/3945) by [@hot123s](https://github.com/hot123s) +* allow submodels to overwrite extra field info, [#3934](https://github.com/pydantic/pydantic/pull/3934) by [@PrettyWood](https://github.com/PrettyWood) +* Document and test structural pattern matching ([PEP 636](https://peps.python.org/pep-0636/)) on `BaseModel`, [#3920](https://github.com/pydantic/pydantic/pull/3920) by [@irgolic](https://github.com/irgolic) +* Fix incorrect deserialization of python timedelta object to ISO 8601 for negative time deltas. + Minus was serialized in incorrect place ("P-1DT23H59M59.888735S" instead of correct "-P1DT23H59M59.888735S"), [#3899](https://github.com/pydantic/pydantic/pull/3899) by [@07pepa](https://github.com/07pepa) +* Fix validation of discriminated union fields with an alias when passing a model instance, [#3846](https://github.com/pydantic/pydantic/pull/3846) by [@chornsby](https://github.com/chornsby) +* Add a CockroachDsn type to validate CockroachDB connection strings. The type + supports the following schemes: `cockroachdb`, `cockroachdb+psycopg2` and `cockroachdb+asyncpg`, [#3839](https://github.com/pydantic/pydantic/pull/3839) by [@blubber](https://github.com/blubber) +* Fix MyPy plugin to not override pre-existing `__init__` method in models, [#3824](https://github.com/pydantic/pydantic/pull/3824) by [@patrick91](https://github.com/patrick91) +* Fix mypy version checking, [#3783](https://github.com/pydantic/pydantic/pull/3783) by [@KotlinIsland](https://github.com/KotlinIsland) +* support overwriting dunder attributes of `BaseModel` instances, [#3777](https://github.com/pydantic/pydantic/pull/3777) by [@PrettyWood](https://github.com/PrettyWood) +* Added `ConstrainedDate` and `condate`, [#3740](https://github.com/pydantic/pydantic/pull/3740) by [@hottwaj](https://github.com/hottwaj) +* Support `kw_only` in dataclasses, [#3670](https://github.com/pydantic/pydantic/pull/3670) by [@detachhead](https://github.com/detachhead) +* Add comparison method for `Color` class, [#3646](https://github.com/pydantic/pydantic/pull/3646) by [@aminalaee](https://github.com/aminalaee) +* Drop support for python3.6, associated cleanup, [#3605](https://github.com/pydantic/pydantic/pull/3605) by [@samuelcolvin](https://github.com/samuelcolvin) +* created new function `to_lower_camel()` for "non pascal case" camel case, [#3463](https://github.com/pydantic/pydantic/pull/3463) by [@schlerp](https://github.com/schlerp) +* Add checks to `default` and `default_factory` arguments in Mypy plugin, [#3430](https://github.com/pydantic/pydantic/pull/3430) by [@klaa97](https://github.com/klaa97) +* fix mangling of `inspect.signature` for `BaseModel`, [#3413](https://github.com/pydantic/pydantic/pull/3413) by [@fix-inspect-signature](https://github.com/fix-inspect-signature) +* Adds the `SecretField` abstract class so that all the current and future secret fields like `SecretStr` and `SecretBytes` will derive from it, [#3409](https://github.com/pydantic/pydantic/pull/3409) by [@expobrain](https://github.com/expobrain) +* Support multi hosts validation in `PostgresDsn`, [#3337](https://github.com/pydantic/pydantic/pull/3337) by [@rglsk](https://github.com/rglsk) +* Fix parsing of very small numeric timedelta values, [#3315](https://github.com/pydantic/pydantic/pull/3315) by [@samuelcolvin](https://github.com/samuelcolvin) +* Update `SecretsSettingsSource` to respect `config.case_sensitive`, [#3273](https://github.com/pydantic/pydantic/pull/3273) by [@JeanArhancet](https://github.com/JeanArhancet) +* Add MongoDB network data source name (DSN) schema, [#3229](https://github.com/pydantic/pydantic/pull/3229) by [@snosratiershad](https://github.com/snosratiershad) +* Add support for multiple dotenv files, [#3222](https://github.com/pydantic/pydantic/pull/3222) by [@rekyungmin](https://github.com/rekyungmin) +* Raise an explicit `ConfigError` when multiple fields are incorrectly set for a single validator, [#3215](https://github.com/pydantic/pydantic/pull/3215) by [@SunsetOrange](https://github.com/SunsetOrange) +* Allow ellipsis on `Field`s inside `Annotated` for `TypedDicts` required, [#3133](https://github.com/pydantic/pydantic/pull/3133) by [@ezegomez](https://github.com/ezegomez) +* Catch overflow errors in `int_validator`, [#3112](https://github.com/pydantic/pydantic/pull/3112) by [@ojii](https://github.com/ojii) +* Adds a `__rich_repr__` method to `Representation` class which enables pretty printing with [Rich](https://github.com/willmcgugan/rich), [#3099](https://github.com/pydantic/pydantic/pull/3099) by [@willmcgugan](https://github.com/willmcgugan) +* Add percent encoding in `AnyUrl` and descendent types, [#3061](https://github.com/pydantic/pydantic/pull/3061) by [@FaresAhmedb](https://github.com/FaresAhmedb) +* `validate_arguments` decorator now supports `alias`, [#3019](https://github.com/pydantic/pydantic/pull/3019) by [@MAD-py](https://github.com/MAD-py) +* Avoid `__dict__` and `__weakref__` attributes in `AnyUrl` and IP address fields, [#2890](https://github.com/pydantic/pydantic/pull/2890) by [@nuno-andre](https://github.com/nuno-andre) +* Add ability to use `Final` in a field type annotation, [#2766](https://github.com/pydantic/pydantic/pull/2766) by [@uriyyo](https://github.com/uriyyo) +* Update requirement to `typing_extensions>=4.1.0` to guarantee `dataclass_transform` is available, [#4424](https://github.com/pydantic/pydantic/pull/4424) by [@commonism](https://github.com/commonism) +* Add Explosion and AWS to main sponsors, [#4413](https://github.com/pydantic/pydantic/pull/4413) by [@samuelcolvin](https://github.com/samuelcolvin) +* Update documentation for `copy_on_model_validation` to reflect recent changes, [#4369](https://github.com/pydantic/pydantic/pull/4369) by [@samuelcolvin](https://github.com/samuelcolvin) +* Runtime warning if `__slots__` is passed to `create_model`, `__slots__` is then ignored, [#4432](https://github.com/pydantic/pydantic/pull/4432) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add type hints to `BaseSettings.Config` to avoid mypy errors, also correct mypy version compatibility notice in docs, [#4450](https://github.com/pydantic/pydantic/pull/4450) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.10.0b1 (2022-08-24) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v1.10.0b1) for details. + +## v1.10.0a2 (2022-08-24) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v1.10.0a2) for details. + +## v1.10.0a1 (2022-08-22) + +Pre-release, see [the GitHub release](https://github.com/pydantic/pydantic/releases/tag/v1.10.0a1) for details. + +## v1.9.2 (2022-08-11) + +**Revert Breaking Change**: _v1.9.1_ introduced a breaking change where model fields were +deep copied by default, this release reverts the default behaviour to match _v1.9.0_ and before, +while also allow deep-copy behaviour via `copy_on_model_validation = 'deep'`. See [#4092](https://github.com/pydantic/pydantic/pull/4092) for more information. + +* Allow for shallow copies of model fields, `Config.copy_on_model_validation` is now a str which must be + `'none'`, `'deep'`, or `'shallow'` corresponding to not copying, deep copy & shallow copy; default `'shallow'`, + [#4093](https://github.com/pydantic/pydantic/pull/4093) by [@timkpaine](https://github.com/timkpaine) + +## v1.9.1 (2022-05-19) + +Thank you to pydantic's sponsors: +[@tiangolo](https://github.com/tiangolo), [@stellargraph](https://github.com/stellargraph), [@JonasKs](https://github.com/JonasKs), [@grillazz](https://github.com/grillazz), [@Mazyod](https://github.com/Mazyod), [@kevinalh](https://github.com/kevinalh), [@chdsbd](https://github.com/chdsbd), [@povilasb](https://github.com/povilasb), [@povilasb](https://github.com/povilasb), [@jina-ai](https://github.com/jina-ai), +[@mainframeindustries](https://github.com/mainframeindustries), [@robusta-dev](https://github.com/robusta-dev), [@SendCloud](https://github.com/SendCloud), [@rszamszur](https://github.com/rszamszur), [@jodal](https://github.com/jodal), [@hardbyte](https://github.com/hardbyte), [@corleyma](https://github.com/corleyma), [@daddycocoaman](https://github.com/daddycocoaman), +[@Rehket](https://github.com/Rehket), [@jokull](https://github.com/jokull), [@reillysiemens](https://github.com/reillysiemens), [@westonsteimel](https://github.com/westonsteimel), [@primer-io](https://github.com/primer-io), [@koxudaxi](https://github.com/koxudaxi), [@browniebroke](https://github.com/browniebroke), [@stradivari96](https://github.com/stradivari96), +[@adriangb](https://github.com/adriangb), [@kamalgill](https://github.com/kamalgill), [@jqueguiner](https://github.com/jqueguiner), [@dev-zero](https://github.com/dev-zero), [@datarootsio](https://github.com/datarootsio), [@RedCarpetUp](https://github.com/RedCarpetUp) +for their kind support. + +* Limit the size of `generics._generic_types_cache` and `generics._assigned_parameters` + to avoid unlimited increase in memory usage, [#4083](https://github.com/pydantic/pydantic/pull/4083) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add Jupyverse and FPS as Jupyter projects using pydantic, [#4082](https://github.com/pydantic/pydantic/pull/4082) by [@davidbrochart](https://github.com/davidbrochart) +* Speedup `__isinstancecheck__` on pydantic models when the type is not a model, may also avoid memory "leaks", [#4081](https://github.com/pydantic/pydantic/pull/4081) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix in-place modification of `FieldInfo` that caused problems with PEP 593 type aliases, [#4067](https://github.com/pydantic/pydantic/pull/4067) by [@adriangb](https://github.com/adriangb) +* Add support for autocomplete in VS Code via `__dataclass_transform__` when using `pydantic.dataclasses.dataclass`, [#4006](https://github.com/pydantic/pydantic/pull/4006) by [@giuliano-oliveira](https://github.com/giuliano-oliveira) +* Remove benchmarks from codebase and docs, [#3973](https://github.com/pydantic/pydantic/pull/3973) by [@samuelcolvin](https://github.com/samuelcolvin) +* Typing checking with pyright in CI, improve docs on vscode/pylance/pyright, [#3972](https://github.com/pydantic/pydantic/pull/3972) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix nested Python dataclass schema regression, [#3819](https://github.com/pydantic/pydantic/pull/3819) by [@himbeles](https://github.com/himbeles) +* Update documentation about lazy evaluation of sources for Settings, [#3806](https://github.com/pydantic/pydantic/pull/3806) by [@garyd203](https://github.com/garyd203) +* Prevent subclasses of bytes being converted to bytes, [#3706](https://github.com/pydantic/pydantic/pull/3706) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fixed "error checking inheritance of" when using PEP585 and PEP604 type hints, [#3681](https://github.com/pydantic/pydantic/pull/3681) by [@aleksul](https://github.com/aleksul) +* Allow self referencing `ClassVar`s in models, [#3679](https://github.com/pydantic/pydantic/pull/3679) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change, see [#4106](https://github.com/pydantic/pydantic/pull/4106)**: Fix issue with self-referencing dataclass, [#3675](https://github.com/pydantic/pydantic/pull/3675) by [@uriyyo](https://github.com/uriyyo) +* Include non-standard port numbers in rendered URLs, [#3652](https://github.com/pydantic/pydantic/pull/3652) by [@dolfinus](https://github.com/dolfinus) +* `Config.copy_on_model_validation` does a deep copy and not a shallow one, [#3641](https://github.com/pydantic/pydantic/pull/3641) by [@PrettyWood](https://github.com/PrettyWood) +* fix: clarify that discriminated unions do not support singletons, [#3636](https://github.com/pydantic/pydantic/pull/3636) by [@tommilligan](https://github.com/tommilligan) +* Add `read_text(encoding='utf-8')` for `setup.py`, [#3625](https://github.com/pydantic/pydantic/pull/3625) by [@hswong3i](https://github.com/hswong3i) +* Fix JSON Schema generation for Discriminated Unions within lists, [#3608](https://github.com/pydantic/pydantic/pull/3608) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.9.0 (2021-12-31) + +Thank you to pydantic's sponsors: +[@sthagen](https://github.com/sthagen), [@timdrijvers](https://github.com/timdrijvers), [@toinbis](https://github.com/toinbis), [@koxudaxi](https://github.com/koxudaxi), [@ginomempin](https://github.com/ginomempin), [@primer-io](https://github.com/primer-io), [@and-semakin](https://github.com/and-semakin), [@westonsteimel](https://github.com/westonsteimel), [@reillysiemens](https://github.com/reillysiemens), +[@es3n1n](https://github.com/es3n1n), [@jokull](https://github.com/jokull), [@JonasKs](https://github.com/JonasKs), [@Rehket](https://github.com/Rehket), [@corleyma](https://github.com/corleyma), [@daddycocoaman](https://github.com/daddycocoaman), [@hardbyte](https://github.com/hardbyte), [@datarootsio](https://github.com/datarootsio), [@jodal](https://github.com/jodal), [@aminalaee](https://github.com/aminalaee), [@rafsaf](https://github.com/rafsaf), +[@jqueguiner](https://github.com/jqueguiner), [@chdsbd](https://github.com/chdsbd), [@kevinalh](https://github.com/kevinalh), [@Mazyod](https://github.com/Mazyod), [@grillazz](https://github.com/grillazz), [@JonasKs](https://github.com/JonasKs), [@simw](https://github.com/simw), [@leynier](https://github.com/leynier), [@xfenix](https://github.com/xfenix) +for their kind support. + +### Highlights + +* add Python 3.10 support, [#2885](https://github.com/pydantic/pydantic/pull/2885) by [@PrettyWood](https://github.com/PrettyWood) +* [Discriminated unions](https://docs.pydantic.dev/usage/types/#discriminated-unions-aka-tagged-unions), [#619](https://github.com/pydantic/pydantic/pull/619) by [@PrettyWood](https://github.com/PrettyWood) +* [`Config.smart_union` for better union logic](https://docs.pydantic.dev/usage/model_config/#smart-union), [#2092](https://github.com/pydantic/pydantic/pull/2092) by [@PrettyWood](https://github.com/PrettyWood) +* Binaries for Macos M1 CPUs, [#3498](https://github.com/pydantic/pydantic/pull/3498) by [@samuelcolvin](https://github.com/samuelcolvin) +* Complex types can be set via [nested environment variables](https://docs.pydantic.dev/usage/settings/#parsing-environment-variable-values), e.g. `foo___bar`, [#3159](https://github.com/pydantic/pydantic/pull/3159) by [@Air-Mark](https://github.com/Air-Mark) +* add a dark mode to _pydantic_ documentation, [#2913](https://github.com/pydantic/pydantic/pull/2913) by [@gbdlin](https://github.com/gbdlin) +* Add support for autocomplete in VS Code via `__dataclass_transform__`, [#2721](https://github.com/pydantic/pydantic/pull/2721) by [@tiangolo](https://github.com/tiangolo) +* Add "exclude" as a field parameter so that it can be configured using model config, [#660](https://github.com/pydantic/pydantic/pull/660) by [@daviskirk](https://github.com/daviskirk) + +### v1.9.0 (2021-12-31) Changes + +* Apply `update_forward_refs` to `Config.json_encodes` prevent name clashes in types defined via strings, [#3583](https://github.com/pydantic/pydantic/pull/3583) by [@samuelcolvin](https://github.com/samuelcolvin) +* Extend pydantic's mypy plugin to support mypy versions `0.910`, `0.920`, `0.921` & `0.930`, [#3573](https://github.com/pydantic/pydantic/pull/3573) & [#3594](https://github.com/pydantic/pydantic/pull/3594) by [@PrettyWood](https://github.com/PrettyWood), [@christianbundy](https://github.com/christianbundy), [@samuelcolvin](https://github.com/samuelcolvin) + +### v1.9.0a2 (2021-12-24) Changes + +* support generic models with discriminated union, [#3551](https://github.com/pydantic/pydantic/pull/3551) by [@PrettyWood](https://github.com/PrettyWood) +* keep old behaviour of `json()` by default, [#3542](https://github.com/pydantic/pydantic/pull/3542) by [@PrettyWood](https://github.com/PrettyWood) +* Removed typing-only `__root__` attribute from `BaseModel`, [#3540](https://github.com/pydantic/pydantic/pull/3540) by [@layday](https://github.com/layday) +* Build Python 3.10 wheels, [#3539](https://github.com/pydantic/pydantic/pull/3539) by [@mbachry](https://github.com/mbachry) +* Fix display of `extra` fields with model `__repr__`, [#3234](https://github.com/pydantic/pydantic/pull/3234) by [@cocolman](https://github.com/cocolman) +* models copied via `Config.copy_on_model_validation` always have all fields, [#3201](https://github.com/pydantic/pydantic/pull/3201) by [@PrettyWood](https://github.com/PrettyWood) +* nested ORM from nested dictionaries, [#3182](https://github.com/pydantic/pydantic/pull/3182) by [@PrettyWood](https://github.com/PrettyWood) +* fix link to discriminated union section by [@PrettyWood](https://github.com/PrettyWood) + +### v1.9.0a1 (2021-12-18) Changes + +* Add support for `Decimal`-specific validation configurations in `Field()`, additionally to using `condecimal()`, + to allow better support from editors and tooling, [#3507](https://github.com/pydantic/pydantic/pull/3507) by [@tiangolo](https://github.com/tiangolo) +* Add `arm64` binaries suitable for MacOS with an M1 CPU to PyPI, [#3498](https://github.com/pydantic/pydantic/pull/3498) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix issue where `None` was considered invalid when using a `Union` type containing `Any` or `object`, [#3444](https://github.com/pydantic/pydantic/pull/3444) by [@tharradine](https://github.com/tharradine) +* When generating field schema, pass optional `field` argument (of type + `pydantic.fields.ModelField`) to `__modify_schema__()` if present, [#3434](https://github.com/pydantic/pydantic/pull/3434) by [@jasujm](https://github.com/jasujm) +* Fix issue when pydantic fail to parse `typing.ClassVar` string type annotation, [#3401](https://github.com/pydantic/pydantic/pull/3401) by [@uriyyo](https://github.com/uriyyo) +* Mention Python >= 3.9.2 as an alternative to `typing_extensions.TypedDict`, [#3374](https://github.com/pydantic/pydantic/pull/3374) by [@BvB93](https://github.com/BvB93) +* Changed the validator method name in the [Custom Errors example](https://docs.pydantic.dev/usage/models/#custom-errors) + to more accurately describe what the validator is doing; changed from `name_must_contain_space` to ` value_must_equal_bar`, [#3327](https://github.com/pydantic/pydantic/pull/3327) by [@michaelrios28](https://github.com/michaelrios28) +* Add `AmqpDsn` class, [#3254](https://github.com/pydantic/pydantic/pull/3254) by [@kludex](https://github.com/kludex) +* Always use `Enum` value as default in generated JSON schema, [#3190](https://github.com/pydantic/pydantic/pull/3190) by [@joaommartins](https://github.com/joaommartins) +* Add support for Mypy 0.920, [#3175](https://github.com/pydantic/pydantic/pull/3175) by [@christianbundy](https://github.com/christianbundy) +* `validate_arguments` now supports `extra` customization (used to always be `Extra.forbid`), [#3161](https://github.com/pydantic/pydantic/pull/3161) by [@PrettyWood](https://github.com/PrettyWood) +* Complex types can be set by nested environment variables, [#3159](https://github.com/pydantic/pydantic/pull/3159) by [@Air-Mark](https://github.com/Air-Mark) +* Fix mypy plugin to collect fields based on `pydantic.utils.is_valid_field` so that it ignores untyped private variables, [#3146](https://github.com/pydantic/pydantic/pull/3146) by [@hi-ogawa](https://github.com/hi-ogawa) +* fix `validate_arguments` issue with `Config.validate_all`, [#3135](https://github.com/pydantic/pydantic/pull/3135) by [@PrettyWood](https://github.com/PrettyWood) +* avoid dict coercion when using dict subclasses as field type, [#3122](https://github.com/pydantic/pydantic/pull/3122) by [@PrettyWood](https://github.com/PrettyWood) +* add support for `object` type, [#3062](https://github.com/pydantic/pydantic/pull/3062) by [@PrettyWood](https://github.com/PrettyWood) +* Updates pydantic dataclasses to keep `_special` properties on parent classes, [#3043](https://github.com/pydantic/pydantic/pull/3043) by [@zulrang](https://github.com/zulrang) +* Add a `TypedDict` class for error objects, [#3038](https://github.com/pydantic/pydantic/pull/3038) by [@matthewhughes934](https://github.com/matthewhughes934) +* Fix support for using a subclass of an annotation as a default, [#3018](https://github.com/pydantic/pydantic/pull/3018) by [@JacobHayes](https://github.com/JacobHayes) +* make `create_model_from_typeddict` mypy compliant, [#3008](https://github.com/pydantic/pydantic/pull/3008) by [@PrettyWood](https://github.com/PrettyWood) +* Make multiple inheritance work when using `PrivateAttr`, [#2989](https://github.com/pydantic/pydantic/pull/2989) by [@hmvp](https://github.com/hmvp) +* Parse environment variables as JSON, if they have a `Union` type with a complex subfield, [#2936](https://github.com/pydantic/pydantic/pull/2936) by [@cbartz](https://github.com/cbartz) +* Prevent `StrictStr` permitting `Enum` values where the enum inherits from `str`, [#2929](https://github.com/pydantic/pydantic/pull/2929) by [@samuelcolvin](https://github.com/samuelcolvin) +* Make `SecretsSettingsSource` parse values being assigned to fields of complex types when sourced from a secrets file, + just as when sourced from environment variables, [#2917](https://github.com/pydantic/pydantic/pull/2917) by [@davidmreed](https://github.com/davidmreed) +* add a dark mode to _pydantic_ documentation, [#2913](https://github.com/pydantic/pydantic/pull/2913) by [@gbdlin](https://github.com/gbdlin) +* Make `pydantic-mypy` plugin compatible with `pyproject.toml` configuration, consistent with `mypy` changes. + See the [doc](https://docs.pydantic.dev/mypy_plugin/#configuring-the-plugin) for more information, [#2908](https://github.com/pydantic/pydantic/pull/2908) by [@jrwalk](https://github.com/jrwalk) +* add Python 3.10 support, [#2885](https://github.com/pydantic/pydantic/pull/2885) by [@PrettyWood](https://github.com/PrettyWood) +* Correctly parse generic models with `Json[T]`, [#2860](https://github.com/pydantic/pydantic/pull/2860) by [@geekingfrog](https://github.com/geekingfrog) +* Update contrib docs re: Python version to use for building docs, [#2856](https://github.com/pydantic/pydantic/pull/2856) by [@paxcodes](https://github.com/paxcodes) +* Clarify documentation about _pydantic_'s support for custom validation and strict type checking, + despite _pydantic_ being primarily a parsing library, [#2855](https://github.com/pydantic/pydantic/pull/2855) by [@paxcodes](https://github.com/paxcodes) +* Fix schema generation for `Deque` fields, [#2810](https://github.com/pydantic/pydantic/pull/2810) by [@sergejkozin](https://github.com/sergejkozin) +* fix an edge case when mixing constraints and `Literal`, [#2794](https://github.com/pydantic/pydantic/pull/2794) by [@PrettyWood](https://github.com/PrettyWood) +* Fix postponed annotation resolution for `NamedTuple` and `TypedDict` when they're used directly as the type of fields + within Pydantic models, [#2760](https://github.com/pydantic/pydantic/pull/2760) by [@jameysharp](https://github.com/jameysharp) +* Fix bug when `mypy` plugin fails on `construct` method call for `BaseSettings` derived classes, [#2753](https://github.com/pydantic/pydantic/pull/2753) by [@uriyyo](https://github.com/uriyyo) +* Add function overloading for a `pydantic.create_model` function, [#2748](https://github.com/pydantic/pydantic/pull/2748) by [@uriyyo](https://github.com/uriyyo) +* Fix mypy plugin issue with self field declaration, [#2743](https://github.com/pydantic/pydantic/pull/2743) by [@uriyyo](https://github.com/uriyyo) +* The colon at the end of the line "The fields which were supplied when user was initialised:" suggests that the code following it is related. + Changed it to a period, [#2733](https://github.com/pydantic/pydantic/pull/2733) by [@krisaoe](https://github.com/krisaoe) +* Renamed variable `schema` to `schema_` to avoid shadowing of global variable name, [#2724](https://github.com/pydantic/pydantic/pull/2724) by [@shahriyarr](https://github.com/shahriyarr) +* Add support for autocomplete in VS Code via `__dataclass_transform__`, [#2721](https://github.com/pydantic/pydantic/pull/2721) by [@tiangolo](https://github.com/tiangolo) +* add missing type annotations in `BaseConfig` and handle `max_length = 0`, [#2719](https://github.com/pydantic/pydantic/pull/2719) by [@PrettyWood](https://github.com/PrettyWood) +* Change `orm_mode` checking to allow recursive ORM mode parsing with dicts, [#2718](https://github.com/pydantic/pydantic/pull/2718) by [@nuno-andre](https://github.com/nuno-andre) +* Add episode 313 of the *Talk Python To Me* podcast, where Michael Kennedy and Samuel Colvin discuss Pydantic, to the docs, [#2712](https://github.com/pydantic/pydantic/pull/2712) by [@RatulMaharaj](https://github.com/RatulMaharaj) +* fix JSON schema generation when a field is of type `NamedTuple` and has a default value, [#2707](https://github.com/pydantic/pydantic/pull/2707) by [@PrettyWood](https://github.com/PrettyWood) +* `Enum` fields now properly support extra kwargs in schema generation, [#2697](https://github.com/pydantic/pydantic/pull/2697) by [@sammchardy](https://github.com/sammchardy) +* **Breaking Change, see [#3780](https://github.com/pydantic/pydantic/pull/3780)**: Make serialization of referenced pydantic models possible, [#2650](https://github.com/pydantic/pydantic/pull/2650) by [@PrettyWood](https://github.com/PrettyWood) +* Add `uniqueItems` option to `ConstrainedList`, [#2618](https://github.com/pydantic/pydantic/pull/2618) by [@nuno-andre](https://github.com/nuno-andre) +* Try to evaluate forward refs automatically at model creation, [#2588](https://github.com/pydantic/pydantic/pull/2588) by [@uriyyo](https://github.com/uriyyo) +* Switch docs preview and coverage display to use [smokeshow](https://smokeshow.helpmanual.io/), [#2580](https://github.com/pydantic/pydantic/pull/2580) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add `__version__` attribute to pydantic module, [#2572](https://github.com/pydantic/pydantic/pull/2572) by [@paxcodes](https://github.com/paxcodes) +* Add `postgresql+asyncpg`, `postgresql+pg8000`, `postgresql+psycopg2`, `postgresql+psycopg2cffi`, `postgresql+py-postgresql` + and `postgresql+pygresql` schemes for `PostgresDsn`, [#2567](https://github.com/pydantic/pydantic/pull/2567) by [@postgres-asyncpg](https://github.com/postgres-asyncpg) +* Enable the Hypothesis plugin to generate a constrained decimal when the `decimal_places` argument is specified, [#2524](https://github.com/pydantic/pydantic/pull/2524) by [@cwe5590](https://github.com/cwe5590) +* Allow `collections.abc.Callable` to be used as type in Python 3.9, [#2519](https://github.com/pydantic/pydantic/pull/2519) by [@daviskirk](https://github.com/daviskirk) +* Documentation update how to custom compile pydantic when using pip install, small change in `setup.py` + to allow for custom CFLAGS when compiling, [#2517](https://github.com/pydantic/pydantic/pull/2517) by [@peterroelants](https://github.com/peterroelants) +* remove side effect of `default_factory` to run it only once even if `Config.validate_all` is set, [#2515](https://github.com/pydantic/pydantic/pull/2515) by [@PrettyWood](https://github.com/PrettyWood) +* Add lookahead to ip regexes for `AnyUrl` hosts. This allows urls with DNS labels + looking like IPs to validate as they are perfectly valid host names, [#2512](https://github.com/pydantic/pydantic/pull/2512) by [@sbv-csis](https://github.com/sbv-csis) +* Set `minItems` and `maxItems` in generated JSON schema for fixed-length tuples, [#2497](https://github.com/pydantic/pydantic/pull/2497) by [@PrettyWood](https://github.com/PrettyWood) +* Add `strict` argument to `conbytes`, [#2489](https://github.com/pydantic/pydantic/pull/2489) by [@koxudaxi](https://github.com/koxudaxi) +* Support user defined generic field types in generic models, [#2465](https://github.com/pydantic/pydantic/pull/2465) by [@daviskirk](https://github.com/daviskirk) +* Add an example and a short explanation of subclassing `GetterDict` to docs, [#2463](https://github.com/pydantic/pydantic/pull/2463) by [@nuno-andre](https://github.com/nuno-andre) +* add `KafkaDsn` type, `HttpUrl` now has default port 80 for http and 443 for https, [#2447](https://github.com/pydantic/pydantic/pull/2447) by [@MihanixA](https://github.com/MihanixA) +* Add `PastDate` and `FutureDate` types, [#2425](https://github.com/pydantic/pydantic/pull/2425) by [@Kludex](https://github.com/Kludex) +* Support generating schema for `Generic` fields with subtypes, [#2375](https://github.com/pydantic/pydantic/pull/2375) by [@maximberg](https://github.com/maximberg) +* fix(encoder): serialize `NameEmail` to str, [#2341](https://github.com/pydantic/pydantic/pull/2341) by [@alecgerona](https://github.com/alecgerona) +* add `Config.smart_union` to prevent coercion in `Union` if possible, see + [the doc](https://docs.pydantic.dev/usage/model_config/#smart-union) for more information, [#2092](https://github.com/pydantic/pydantic/pull/2092) by [@PrettyWood](https://github.com/PrettyWood) +* Add ability to use `typing.Counter` as a model field type, [#2060](https://github.com/pydantic/pydantic/pull/2060) by [@uriyyo](https://github.com/uriyyo) +* Add parameterised subclasses to `__bases__` when constructing new parameterised classes, so that `A <: B => A[int] <: B[int]`, [#2007](https://github.com/pydantic/pydantic/pull/2007) by [@diabolo-dan](https://github.com/diabolo-dan) +* Create `FileUrl` type that allows URLs that conform to [RFC 8089](https://tools.ietf.org/html/rfc8089#section-2). + Add `host_required` parameter, which is `True` by default (`AnyUrl` and subclasses), `False` in `RedisDsn`, `FileUrl`, [#1983](https://github.com/pydantic/pydantic/pull/1983) by [@vgerak](https://github.com/vgerak) +* add `confrozenset()`, analogous to `conset()` and `conlist()`, [#1897](https://github.com/pydantic/pydantic/pull/1897) by [@PrettyWood](https://github.com/PrettyWood) +* stop calling parent class `root_validator` if overridden, [#1895](https://github.com/pydantic/pydantic/pull/1895) by [@PrettyWood](https://github.com/PrettyWood) +* Add `repr` (defaults to `True`) parameter to `Field`, to hide it from the default representation of the `BaseModel`, [#1831](https://github.com/pydantic/pydantic/pull/1831) by [@fnep](https://github.com/fnep) +* Accept empty query/fragment URL parts, [#1807](https://github.com/pydantic/pydantic/pull/1807) by [@xavier](https://github.com/xavier) + +## v1.8.2 (2021-05-11) + +!!! warning + A security vulnerability, level "moderate" is fixed in v1.8.2. Please upgrade **ASAP**. + See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) + +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` + (or their negative values) does not cause an infinite loop, + see security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) +* fix schema generation with Enum by generating a valid name, [#2575](https://github.com/pydantic/pydantic/pull/2575) by [@PrettyWood](https://github.com/PrettyWood) +* fix JSON schema generation with a `Literal` of an enum member, [#2536](https://github.com/pydantic/pydantic/pull/2536) by [@PrettyWood](https://github.com/PrettyWood) +* Fix bug with configurations declarations that are passed as + keyword arguments during class creation, [#2532](https://github.com/pydantic/pydantic/pull/2532) by [@uriyyo](https://github.com/uriyyo) +* Allow passing `json_encoders` in class kwargs, [#2521](https://github.com/pydantic/pydantic/pull/2521) by [@layday](https://github.com/layday) +* support arbitrary types with custom `__eq__`, [#2483](https://github.com/pydantic/pydantic/pull/2483) by [@PrettyWood](https://github.com/PrettyWood) +* support `Annotated` in `validate_arguments` and in generic models with Python 3.9, [#2483](https://github.com/pydantic/pydantic/pull/2483) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.8.1 (2021-03-03) + +Bug fixes for regressions and new features from `v1.8` + +* allow elements of `Config.field` to update elements of a `Field`, [#2461](https://github.com/pydantic/pydantic/pull/2461) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix validation with a `BaseModel` field and a custom root type, [#2449](https://github.com/pydantic/pydantic/pull/2449) by [@PrettyWood](https://github.com/PrettyWood) +* expose `Pattern` encoder to `fastapi`, [#2444](https://github.com/pydantic/pydantic/pull/2444) by [@PrettyWood](https://github.com/PrettyWood) +* enable the Hypothesis plugin to generate a constrained float when the `multiple_of` argument is specified, [#2442](https://github.com/pydantic/pydantic/pull/2442) by [@tobi-lipede-oodle](https://github.com/tobi-lipede-oodle) +* Avoid `RecursionError` when using some types like `Enum` or `Literal` with generic models, [#2436](https://github.com/pydantic/pydantic/pull/2436) by [@PrettyWood](https://github.com/PrettyWood) +* do not overwrite declared `__hash__` in subclasses of a model, [#2422](https://github.com/pydantic/pydantic/pull/2422) by [@PrettyWood](https://github.com/PrettyWood) +* fix `mypy` complaints on `Path` and `UUID` related custom types, [#2418](https://github.com/pydantic/pydantic/pull/2418) by [@PrettyWood](https://github.com/PrettyWood) +* Support properly variable length tuples of compound types, [#2416](https://github.com/pydantic/pydantic/pull/2416) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.8 (2021-02-26) + +Thank you to pydantic's sponsors: +[@jorgecarleitao](https://github.com/jorgecarleitao), [@BCarley](https://github.com/BCarley), [@chdsbd](https://github.com/chdsbd), [@tiangolo](https://github.com/tiangolo), [@matin](https://github.com/matin), [@linusg](https://github.com/linusg), [@kevinalh](https://github.com/kevinalh), [@koxudaxi](https://github.com/koxudaxi), [@timdrijvers](https://github.com/timdrijvers), [@mkeen](https://github.com/mkeen), [@meadsteve](https://github.com/meadsteve), +[@ginomempin](https://github.com/ginomempin), [@primer-io](https://github.com/primer-io), [@and-semakin](https://github.com/and-semakin), [@tomthorogood](https://github.com/tomthorogood), [@AjitZK](https://github.com/AjitZK), [@westonsteimel](https://github.com/westonsteimel), [@Mazyod](https://github.com/Mazyod), [@christippett](https://github.com/christippett), [@CarlosDomingues](https://github.com/CarlosDomingues), +[@Kludex](https://github.com/Kludex), [@r-m-n](https://github.com/r-m-n) +for their kind support. + +### Highlights + +* [Hypothesis plugin](https://docs.pydantic.dev/hypothesis_plugin/) for testing, [#2097](https://github.com/pydantic/pydantic/pull/2097) by [@Zac-HD](https://github.com/Zac-HD) +* support for [`NamedTuple` and `TypedDict`](https://docs.pydantic.dev/usage/types/#annotated-types), [#2216](https://github.com/pydantic/pydantic/pull/2216) by [@PrettyWood](https://github.com/PrettyWood) +* Support [`Annotated` hints on model fields](https://docs.pydantic.dev/usage/schema/#typingannotated-fields), [#2147](https://github.com/pydantic/pydantic/pull/2147) by [@JacobHayes](https://github.com/JacobHayes) +* [`frozen` parameter on `Config`](https://docs.pydantic.dev/usage/model_config/) to allow models to be hashed, [#1880](https://github.com/pydantic/pydantic/pull/1880) by [@rhuille](https://github.com/rhuille) + +### Changes + +* **Breaking Change**, remove old deprecation aliases from v1, [#2415](https://github.com/pydantic/pydantic/pull/2415) by [@samuelcolvin](https://github.com/samuelcolvin): + * remove notes on migrating to v1 in docs + * remove `Schema` which was replaced by `Field` + * remove `Config.case_insensitive` which was replaced by `Config.case_sensitive` (default `False`) + * remove `Config.allow_population_by_alias` which was replaced by `Config.allow_population_by_field_name` + * remove `model.fields` which was replaced by `model.__fields__` + * remove `model.to_string()` which was replaced by `str(model)` + * remove `model.__values__` which was replaced by `model.__dict__` +* **Breaking Change:** always validate only first sublevel items with `each_item`. + There were indeed some edge cases with some compound types where the validated items were the last sublevel ones, [#1933](https://github.com/pydantic/pydantic/pull/1933) by [@PrettyWood](https://github.com/PrettyWood) +* Update docs extensions to fix local syntax highlighting, [#2400](https://github.com/pydantic/pydantic/pull/2400) by [@daviskirk](https://github.com/daviskirk) +* fix: allow `utils.lenient_issubclass` to handle `typing.GenericAlias` objects like `list[str]` in Python >= 3.9, [#2399](https://github.com/pydantic/pydantic/pull/2399) by [@daviskirk](https://github.com/daviskirk) +* Improve field declaration for _pydantic_ `dataclass` by allowing the usage of _pydantic_ `Field` or `'metadata'` kwarg of `dataclasses.field`, [#2384](https://github.com/pydantic/pydantic/pull/2384) by [@PrettyWood](https://github.com/PrettyWood) +* Making `typing-extensions` a required dependency, [#2368](https://github.com/pydantic/pydantic/pull/2368) by [@samuelcolvin](https://github.com/samuelcolvin) +* Make `resolve_annotations` more lenient, allowing for missing modules, [#2363](https://github.com/pydantic/pydantic/pull/2363) by [@samuelcolvin](https://github.com/samuelcolvin) +* Allow configuring models through class kwargs, [#2356](https://github.com/pydantic/pydantic/pull/2356) by [@Bobronium](https://github.com/Bobronium) +* Prevent `Mapping` subclasses from always being coerced to `dict`, [#2325](https://github.com/pydantic/pydantic/pull/2325) by [@ofek](https://github.com/ofek) +* fix: allow `None` for type `Optional[conset / conlist]`, [#2320](https://github.com/pydantic/pydantic/pull/2320) by [@PrettyWood](https://github.com/PrettyWood) +* Support empty tuple type, [#2318](https://github.com/pydantic/pydantic/pull/2318) by [@PrettyWood](https://github.com/PrettyWood) +* fix: `python_requires` metadata to require >=3.6.1, [#2306](https://github.com/pydantic/pydantic/pull/2306) by [@hukkinj1](https://github.com/hukkinj1) +* Properly encode `Decimal` with, or without any decimal places, [#2293](https://github.com/pydantic/pydantic/pull/2293) by [@hultner](https://github.com/hultner) +* fix: update `__fields_set__` in `BaseModel.copy(update=…)`, [#2290](https://github.com/pydantic/pydantic/pull/2290) by [@PrettyWood](https://github.com/PrettyWood) +* fix: keep order of fields with `BaseModel.construct()`, [#2281](https://github.com/pydantic/pydantic/pull/2281) by [@PrettyWood](https://github.com/PrettyWood) +* Support generating schema for Generic fields, [#2262](https://github.com/pydantic/pydantic/pull/2262) by [@maximberg](https://github.com/maximberg) +* Fix `validate_decorator` so `**kwargs` doesn't exclude values when the keyword + has the same name as the `*args` or `**kwargs` names, [#2251](https://github.com/pydantic/pydantic/pull/2251) by [@cybojenix](https://github.com/cybojenix) +* Prevent overriding positional arguments with keyword arguments in + `validate_arguments`, as per behaviour with native functions, [#2249](https://github.com/pydantic/pydantic/pull/2249) by [@cybojenix](https://github.com/cybojenix) +* add documentation for `con*` type functions, [#2242](https://github.com/pydantic/pydantic/pull/2242) by [@tayoogunbiyi](https://github.com/tayoogunbiyi) +* Support custom root type (aka `__root__`) when using `parse_obj()` with nested models, [#2238](https://github.com/pydantic/pydantic/pull/2238) by [@PrettyWood](https://github.com/PrettyWood) +* Support custom root type (aka `__root__`) with `from_orm()`, [#2237](https://github.com/pydantic/pydantic/pull/2237) by [@PrettyWood](https://github.com/PrettyWood) +* ensure cythonized functions are left untouched when creating models, based on [#1944](https://github.com/pydantic/pydantic/pull/1944) by [@kollmats](https://github.com/kollmats), [#2228](https://github.com/pydantic/pydantic/pull/2228) by [@samuelcolvin](https://github.com/samuelcolvin) +* Resolve forward refs for stdlib dataclasses converted into _pydantic_ ones, [#2220](https://github.com/pydantic/pydantic/pull/2220) by [@PrettyWood](https://github.com/PrettyWood) +* Add support for `NamedTuple` and `TypedDict` types. + Those two types are now handled and validated when used inside `BaseModel` or _pydantic_ `dataclass`. + Two utils are also added `create_model_from_namedtuple` and `create_model_from_typeddict`, [#2216](https://github.com/pydantic/pydantic/pull/2216) by [@PrettyWood](https://github.com/PrettyWood) +* Do not ignore annotated fields when type is `Union[Type[...], ...]`, [#2213](https://github.com/pydantic/pydantic/pull/2213) by [@PrettyWood](https://github.com/PrettyWood) +* Raise a user-friendly `TypeError` when a `root_validator` does not return a `dict` (e.g. `None`), [#2209](https://github.com/pydantic/pydantic/pull/2209) by [@masalim2](https://github.com/masalim2) +* Add a `FrozenSet[str]` type annotation to the `allowed_schemes` argument on the `strict_url` field type, [#2198](https://github.com/pydantic/pydantic/pull/2198) by [@Midnighter](https://github.com/Midnighter) +* add `allow_mutation` constraint to `Field`, [#2195](https://github.com/pydantic/pydantic/pull/2195) by [@sblack-usu](https://github.com/sblack-usu) +* Allow `Field` with a `default_factory` to be used as an argument to a function + decorated with `validate_arguments`, [#2176](https://github.com/pydantic/pydantic/pull/2176) by [@thomascobb](https://github.com/thomascobb) +* Allow non-existent secrets directory by only issuing a warning, [#2175](https://github.com/pydantic/pydantic/pull/2175) by [@davidolrik](https://github.com/davidolrik) +* fix URL regex to parse fragment without query string, [#2168](https://github.com/pydantic/pydantic/pull/2168) by [@andrewmwhite](https://github.com/andrewmwhite) +* fix: ensure to always return one of the values in `Literal` field type, [#2166](https://github.com/pydantic/pydantic/pull/2166) by [@PrettyWood](https://github.com/PrettyWood) +* Support `typing.Annotated` hints on model fields. A `Field` may now be set in the type hint with `Annotated[..., Field(...)`; all other annotations are ignored but still visible with `get_type_hints(..., include_extras=True)`, [#2147](https://github.com/pydantic/pydantic/pull/2147) by [@JacobHayes](https://github.com/JacobHayes) +* Added `StrictBytes` type as well as `strict=False` option to `ConstrainedBytes`, [#2136](https://github.com/pydantic/pydantic/pull/2136) by [@rlizzo](https://github.com/rlizzo) +* added `Config.anystr_lower` and `to_lower` kwarg to `constr` and `conbytes`, [#2134](https://github.com/pydantic/pydantic/pull/2134) by [@tayoogunbiyi](https://github.com/tayoogunbiyi) +* Support plain `typing.Tuple` type, [#2132](https://github.com/pydantic/pydantic/pull/2132) by [@PrettyWood](https://github.com/PrettyWood) +* Add a bound method `validate` to functions decorated with `validate_arguments` + to validate parameters without actually calling the function, [#2127](https://github.com/pydantic/pydantic/pull/2127) by [@PrettyWood](https://github.com/PrettyWood) +* Add the ability to customize settings sources (add / disable / change priority order), [#2107](https://github.com/pydantic/pydantic/pull/2107) by [@kozlek](https://github.com/kozlek) +* Fix mypy complaints about most custom _pydantic_ types, [#2098](https://github.com/pydantic/pydantic/pull/2098) by [@PrettyWood](https://github.com/PrettyWood) +* Add a [Hypothesis](https://hypothesis.readthedocs.io/) plugin for easier [property-based testing](https://increment.com/testing/in-praise-of-property-based-testing/) with Pydantic's custom types - [usage details here](https://docs.pydantic.dev/hypothesis_plugin/), [#2097](https://github.com/pydantic/pydantic/pull/2097) by [@Zac-HD](https://github.com/Zac-HD) +* add validator for `None`, `NoneType` or `Literal[None]`, [#2095](https://github.com/pydantic/pydantic/pull/2095) by [@PrettyWood](https://github.com/PrettyWood) +* Handle properly fields of type `Callable` with a default value, [#2094](https://github.com/pydantic/pydantic/pull/2094) by [@PrettyWood](https://github.com/PrettyWood) +* Updated `create_model` return type annotation to return type which inherits from `__base__` argument, [#2071](https://github.com/pydantic/pydantic/pull/2071) by [@uriyyo](https://github.com/uriyyo) +* Add merged `json_encoders` inheritance, [#2064](https://github.com/pydantic/pydantic/pull/2064) by [@art049](https://github.com/art049) +* allow overwriting `ClassVar`s in sub-models without having to re-annotate them, [#2061](https://github.com/pydantic/pydantic/pull/2061) by [@layday](https://github.com/layday) +* add default encoder for `Pattern` type, [#2045](https://github.com/pydantic/pydantic/pull/2045) by [@PrettyWood](https://github.com/PrettyWood) +* Add `NonNegativeInt`, `NonPositiveInt`, `NonNegativeFloat`, `NonPositiveFloat`, [#1975](https://github.com/pydantic/pydantic/pull/1975) by [@mdavis-xyz](https://github.com/mdavis-xyz) +* Use % for percentage in string format of colors, [#1960](https://github.com/pydantic/pydantic/pull/1960) by [@EdwardBetts](https://github.com/EdwardBetts) +* Fixed issue causing `KeyError` to be raised when building schema from multiple `BaseModel` with the same names declared in separate classes, [#1912](https://github.com/pydantic/pydantic/pull/1912) by [@JSextonn](https://github.com/JSextonn) +* Add `rediss` (Redis over SSL) protocol to `RedisDsn` + Allow URLs without `user` part (e.g., `rediss://:pass@localhost`), [#1911](https://github.com/pydantic/pydantic/pull/1911) by [@TrDex](https://github.com/TrDex) +* Add a new `frozen` boolean parameter to `Config` (default: `False`). + Setting `frozen=True` does everything that `allow_mutation=False` does, and also generates a `__hash__()` method for the model. This makes instances of the model potentially hashable if all the attributes are hashable, [#1880](https://github.com/pydantic/pydantic/pull/1880) by [@rhuille](https://github.com/rhuille) +* fix schema generation with multiple Enums having the same name, [#1857](https://github.com/pydantic/pydantic/pull/1857) by [@PrettyWood](https://github.com/PrettyWood) +* Added support for 13/19 digits VISA credit cards in `PaymentCardNumber` type, [#1416](https://github.com/pydantic/pydantic/pull/1416) by [@AlexanderSov](https://github.com/AlexanderSov) +* fix: prevent `RecursionError` while using recursive `GenericModel`s, [#1370](https://github.com/pydantic/pydantic/pull/1370) by [@xppt](https://github.com/xppt) +* use `enum` for `typing.Literal` in JSON schema, [#1350](https://github.com/pydantic/pydantic/pull/1350) by [@PrettyWood](https://github.com/PrettyWood) +* Fix: some recursive models did not require `update_forward_refs` and silently behaved incorrectly, [#1201](https://github.com/pydantic/pydantic/pull/1201) by [@PrettyWood](https://github.com/PrettyWood) +* Fix bug where generic models with fields where the typevar is nested in another type `a: List[T]` are considered to be concrete. This allows these models to be subclassed and composed as expected, [#947](https://github.com/pydantic/pydantic/pull/947) by [@daviskirk](https://github.com/daviskirk) +* Add `Config.copy_on_model_validation` flag. When set to `False`, _pydantic_ will keep models used as fields + untouched on validation instead of reconstructing (copying) them, [#265](https://github.com/pydantic/pydantic/pull/265) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.7.4 (2021-05-11) + +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` + (or their negative values) does not cause an infinite loop, + See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) + +## v1.7.3 (2020-11-30) + +Thank you to pydantic's sponsors: +[@timdrijvers](https://github.com/timdrijvers), [@BCarley](https://github.com/BCarley), [@chdsbd](https://github.com/chdsbd), [@tiangolo](https://github.com/tiangolo), [@matin](https://github.com/matin), [@linusg](https://github.com/linusg), [@kevinalh](https://github.com/kevinalh), [@jorgecarleitao](https://github.com/jorgecarleitao), [@koxudaxi](https://github.com/koxudaxi), [@primer-api](https://github.com/primer-api), +[@mkeen](https://github.com/mkeen), [@meadsteve](https://github.com/meadsteve) for their kind support. + +* fix: set right default value for required (optional) fields, [#2142](https://github.com/pydantic/pydantic/pull/2142) by [@PrettyWood](https://github.com/PrettyWood) +* fix: support `underscore_attrs_are_private` with generic models, [#2138](https://github.com/pydantic/pydantic/pull/2138) by [@PrettyWood](https://github.com/PrettyWood) +* fix: update all modified field values in `root_validator` when `validate_assignment` is on, [#2116](https://github.com/pydantic/pydantic/pull/2116) by [@PrettyWood](https://github.com/PrettyWood) +* Allow pickling of `pydantic.dataclasses.dataclass` dynamically created from a built-in `dataclasses.dataclass`, [#2111](https://github.com/pydantic/pydantic/pull/2111) by [@aimestereo](https://github.com/aimestereo) +* Fix a regression where Enum fields would not propagate keyword arguments to the schema, [#2109](https://github.com/pydantic/pydantic/pull/2109) by [@bm424](https://github.com/bm424) +* Ignore `__doc__` as private attribute when `Config.underscore_attrs_are_private` is set, [#2090](https://github.com/pydantic/pydantic/pull/2090) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.7.2 (2020-11-01) + +* fix slow `GenericModel` concrete model creation, allow `GenericModel` concrete name reusing in module, [#2078](https://github.com/pydantic/pydantic/pull/2078) by [@Bobronium](https://github.com/Bobronium) +* keep the order of the fields when `validate_assignment` is set, [#2073](https://github.com/pydantic/pydantic/pull/2073) by [@PrettyWood](https://github.com/PrettyWood) +* forward all the params of the stdlib `dataclass` when converted into _pydantic_ `dataclass`, [#2065](https://github.com/pydantic/pydantic/pull/2065) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.7.1 (2020-10-28) + +Thank you to pydantic's sponsors: +[@timdrijvers](https://github.com/timdrijvers), [@BCarley](https://github.com/BCarley), [@chdsbd](https://github.com/chdsbd), [@tiangolo](https://github.com/tiangolo), [@matin](https://github.com/matin), [@linusg](https://github.com/linusg), [@kevinalh](https://github.com/kevinalh), [@jorgecarleitao](https://github.com/jorgecarleitao), [@koxudaxi](https://github.com/koxudaxi), [@primer-api](https://github.com/primer-api), [@mkeen](https://github.com/mkeen) +for their kind support. + +* fix annotation of `validate_arguments` when passing configuration as argument, [#2055](https://github.com/pydantic/pydantic/pull/2055) by [@layday](https://github.com/layday) +* Fix mypy assignment error when using `PrivateAttr`, [#2048](https://github.com/pydantic/pydantic/pull/2048) by [@aphedges](https://github.com/aphedges) +* fix `underscore_attrs_are_private` causing `TypeError` when overriding `__init__`, [#2047](https://github.com/pydantic/pydantic/pull/2047) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fixed regression introduced in v1.7 involving exception handling in field validators when `validate_assignment=True`, [#2044](https://github.com/pydantic/pydantic/pull/2044) by [@johnsabath](https://github.com/johnsabath) +* fix: _pydantic_ `dataclass` can inherit from stdlib `dataclass` + and `Config.arbitrary_types_allowed` is supported, [#2042](https://github.com/pydantic/pydantic/pull/2042) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.7 (2020-10-26) + +Thank you to pydantic's sponsors: +[@timdrijvers](https://github.com/timdrijvers), [@BCarley](https://github.com/BCarley), [@chdsbd](https://github.com/chdsbd), [@tiangolo](https://github.com/tiangolo), [@matin](https://github.com/matin), [@linusg](https://github.com/linusg), [@kevinalh](https://github.com/kevinalh), [@jorgecarleitao](https://github.com/jorgecarleitao), [@koxudaxi](https://github.com/koxudaxi), [@primer-api](https://github.com/primer-api) +for their kind support. + +### Highlights + +* Python 3.9 support, thanks [@PrettyWood](https://github.com/PrettyWood) +* [Private model attributes](https://docs.pydantic.dev/usage/models/#private-model-attributes), thanks [@Bobronium](https://github.com/Bobronium) +* ["secrets files" support in `BaseSettings`](https://docs.pydantic.dev/usage/settings/#secret-support), thanks [@mdgilene](https://github.com/mdgilene) +* [convert stdlib dataclasses to pydantic dataclasses and use stdlib dataclasses in models](https://docs.pydantic.dev/usage/dataclasses/#stdlib-dataclasses-and-pydantic-dataclasses), thanks [@PrettyWood](https://github.com/PrettyWood) + +### Changes + +* **Breaking Change:** remove `__field_defaults__`, add `default_factory` support with `BaseModel.construct`. + Use `.get_default()` method on fields in `__fields__` attribute instead, [#1732](https://github.com/pydantic/pydantic/pull/1732) by [@PrettyWood](https://github.com/PrettyWood) +* Rearrange CI to run linting as a separate job, split install recipes for different tasks, [#2020](https://github.com/pydantic/pydantic/pull/2020) by [@samuelcolvin](https://github.com/samuelcolvin) +* Allows subclasses of generic models to make some, or all, of the superclass's type parameters concrete, while + also defining new type parameters in the subclass, [#2005](https://github.com/pydantic/pydantic/pull/2005) by [@choogeboom](https://github.com/choogeboom) +* Call validator with the correct `values` parameter type in `BaseModel.__setattr__`, + when `validate_assignment = True` in model config, [#1999](https://github.com/pydantic/pydantic/pull/1999) by [@me-ransh](https://github.com/me-ransh) +* Force `fields.Undefined` to be a singleton object, fixing inherited generic model schemas, [#1981](https://github.com/pydantic/pydantic/pull/1981) by [@daviskirk](https://github.com/daviskirk) +* Include tests in source distributions, [#1976](https://github.com/pydantic/pydantic/pull/1976) by [@sbraz](https://github.com/sbraz) +* Add ability to use `min_length/max_length` constraints with secret types, [#1974](https://github.com/pydantic/pydantic/pull/1974) by [@uriyyo](https://github.com/uriyyo) +* Also check `root_validators` when `validate_assignment` is on, [#1971](https://github.com/pydantic/pydantic/pull/1971) by [@PrettyWood](https://github.com/PrettyWood) +* Fix const validators not running when custom validators are present, [#1957](https://github.com/pydantic/pydantic/pull/1957) by [@hmvp](https://github.com/hmvp) +* add `deque` to field types, [#1935](https://github.com/pydantic/pydantic/pull/1935) by [@wozniakty](https://github.com/wozniakty) +* add basic support for Python 3.9, [#1832](https://github.com/pydantic/pydantic/pull/1832) by [@PrettyWood](https://github.com/PrettyWood) +* Fix typo in the anchor of exporting_models.md#modelcopy and incorrect description, [#1821](https://github.com/pydantic/pydantic/pull/1821) by [@KimMachineGun](https://github.com/KimMachineGun) +* Added ability for `BaseSettings` to read "secret files", [#1820](https://github.com/pydantic/pydantic/pull/1820) by [@mdgilene](https://github.com/mdgilene) +* add `parse_raw_as` utility function, [#1812](https://github.com/pydantic/pydantic/pull/1812) by [@PrettyWood](https://github.com/PrettyWood) +* Support home directory relative paths for `dotenv` files (e.g. `~/.env`), [#1803](https://github.com/pydantic/pydantic/pull/1803) by [@PrettyWood](https://github.com/PrettyWood) +* Clarify documentation for `parse_file` to show that the argument + should be a file *path* not a file-like object, [#1794](https://github.com/pydantic/pydantic/pull/1794) by [@mdavis-xyz](https://github.com/mdavis-xyz) +* Fix false positive from mypy plugin when a class nested within a `BaseModel` is named `Model`, [#1770](https://github.com/pydantic/pydantic/pull/1770) by [@selimb](https://github.com/selimb) +* add basic support of Pattern type in schema generation, [#1767](https://github.com/pydantic/pydantic/pull/1767) by [@PrettyWood](https://github.com/PrettyWood) +* Support custom title, description and default in schema of enums, [#1748](https://github.com/pydantic/pydantic/pull/1748) by [@PrettyWood](https://github.com/PrettyWood) +* Properly represent `Literal` Enums when `use_enum_values` is True, [#1747](https://github.com/pydantic/pydantic/pull/1747) by [@noelevans](https://github.com/noelevans) +* Allows timezone information to be added to strings to be formatted as time objects. Permitted formats are `Z` for UTC + or an offset for absolute positive or negative time shifts. Or the timezone data can be omitted, [#1744](https://github.com/pydantic/pydantic/pull/1744) by [@noelevans](https://github.com/noelevans) +* Add stub `__init__` with Python 3.6 signature for `ForwardRef`, [#1738](https://github.com/pydantic/pydantic/pull/1738) by [@sirtelemak](https://github.com/sirtelemak) +* Fix behaviour with forward refs and optional fields in nested models, [#1736](https://github.com/pydantic/pydantic/pull/1736) by [@PrettyWood](https://github.com/PrettyWood) +* add `Enum` and `IntEnum` as valid types for fields, [#1735](https://github.com/pydantic/pydantic/pull/1735) by [@PrettyWood](https://github.com/PrettyWood) +* Change default value of `__module__` argument of `create_model` from `None` to `'pydantic.main'`. + Set reference of created concrete model to it's module to allow pickling (not applied to models created in + functions), [#1686](https://github.com/pydantic/pydantic/pull/1686) by [@Bobronium](https://github.com/Bobronium) +* Add private attributes support, [#1679](https://github.com/pydantic/pydantic/pull/1679) by [@Bobronium](https://github.com/Bobronium) +* add `config` to `@validate_arguments`, [#1663](https://github.com/pydantic/pydantic/pull/1663) by [@samuelcolvin](https://github.com/samuelcolvin) +* Allow descendant Settings models to override env variable names for the fields defined in parent Settings models with + `env` in their `Config`. Previously only `env_prefix` configuration option was applicable, [#1561](https://github.com/pydantic/pydantic/pull/1561) by [@ojomio](https://github.com/ojomio) +* Support `ref_template` when creating schema `$ref`s, [#1479](https://github.com/pydantic/pydantic/pull/1479) by [@kilo59](https://github.com/kilo59) +* Add a `__call__` stub to `PyObject` so that mypy will know that it is callable, [#1352](https://github.com/pydantic/pydantic/pull/1352) by [@brianmaissy](https://github.com/brianmaissy) +* `pydantic.dataclasses.dataclass` decorator now supports built-in `dataclasses.dataclass`. + It is hence possible to convert an existing `dataclass` easily to add Pydantic validation. + Moreover nested dataclasses are also supported, [#744](https://github.com/pydantic/pydantic/pull/744) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.6.2 (2021-05-11) + +* **Security fix:** Fix `date` and `datetime` parsing so passing either `'infinity'` or `float('inf')` + (or their negative values) does not cause an infinite loop, + See security advisory [CVE-2021-29510](https://github.com/pydantic/pydantic/security/advisories/GHSA-5jqp-qgf6-3pvh) + +## v1.6.1 (2020-07-15) + +* fix validation and parsing of nested models with `default_factory`, [#1710](https://github.com/pydantic/pydantic/pull/1710) by [@PrettyWood](https://github.com/PrettyWood) + +## v1.6 (2020-07-11) + +Thank you to pydantic's sponsors: [@matin](https://github.com/matin), [@tiangolo](https://github.com/tiangolo), [@chdsbd](https://github.com/chdsbd), [@jorgecarleitao](https://github.com/jorgecarleitao), and 1 anonymous sponsor for their kind support. + +* Modify validators for `conlist` and `conset` to not have `always=True`, [#1682](https://github.com/pydantic/pydantic/pull/1682) by [@samuelcolvin](https://github.com/samuelcolvin) +* add port check to `AnyUrl` (can't exceed 65536) ports are 16 insigned bits: `0 <= port <= 2**16-1` src: [rfc793 header format](https://tools.ietf.org/html/rfc793#section-3.1), [#1654](https://github.com/pydantic/pydantic/pull/1654) by [@flapili](https://github.com/flapili) +* Document default `regex` anchoring semantics, [#1648](https://github.com/pydantic/pydantic/pull/1648) by [@yurikhan](https://github.com/yurikhan) +* Use `chain.from_iterable` in class_validators.py. This is a faster and more idiomatic way of using `itertools.chain`. + Instead of computing all the items in the iterable and storing them in memory, they are computed one-by-one and never + stored as a huge list. This can save on both runtime and memory space, [#1642](https://github.com/pydantic/pydantic/pull/1642) by [@cool-RR](https://github.com/cool-RR) +* Add `conset()`, analogous to `conlist()`, [#1623](https://github.com/pydantic/pydantic/pull/1623) by [@patrickkwang](https://github.com/patrickkwang) +* make Pydantic errors (un)pickable, [#1616](https://github.com/pydantic/pydantic/pull/1616) by [@PrettyWood](https://github.com/PrettyWood) +* Allow custom encoding for `dotenv` files, [#1615](https://github.com/pydantic/pydantic/pull/1615) by [@PrettyWood](https://github.com/PrettyWood) +* Ensure `SchemaExtraCallable` is always defined to get type hints on BaseConfig, [#1614](https://github.com/pydantic/pydantic/pull/1614) by [@PrettyWood](https://github.com/PrettyWood) +* Update datetime parser to support negative timestamps, [#1600](https://github.com/pydantic/pydantic/pull/1600) by [@mlbiche](https://github.com/mlbiche) +* Update mypy, remove `AnyType` alias for `Type[Any]`, [#1598](https://github.com/pydantic/pydantic/pull/1598) by [@samuelcolvin](https://github.com/samuelcolvin) +* Adjust handling of root validators so that errors are aggregated from _all_ failing root validators, instead of reporting on only the first root validator to fail, [#1586](https://github.com/pydantic/pydantic/pull/1586) by [@beezee](https://github.com/beezee) +* Make `__modify_schema__` on Enums apply to the enum schema rather than fields that use the enum, [#1581](https://github.com/pydantic/pydantic/pull/1581) by [@therefromhere](https://github.com/therefromhere) +* Fix behavior of `__all__` key when used in conjunction with index keys in advanced include/exclude of fields that are sequences, [#1579](https://github.com/pydantic/pydantic/pull/1579) by [@xspirus](https://github.com/xspirus) +* Subclass validators do not run when referencing a `List` field defined in a parent class when `each_item=True`. Added an example to the docs illustrating this, [#1566](https://github.com/pydantic/pydantic/pull/1566) by [@samueldeklund](https://github.com/samueldeklund) +* change `schema.field_class_to_schema` to support `frozenset` in schema, [#1557](https://github.com/pydantic/pydantic/pull/1557) by [@wangpeibao](https://github.com/wangpeibao) +* Call `__modify_schema__` only for the field schema, [#1552](https://github.com/pydantic/pydantic/pull/1552) by [@PrettyWood](https://github.com/PrettyWood) +* Move the assignment of `field.validate_always` in `fields.py` so the `always` parameter of validators work on inheritance, [#1545](https://github.com/pydantic/pydantic/pull/1545) by [@dcHHH](https://github.com/dcHHH) +* Added support for UUID instantiation through 16 byte strings such as `b'\x12\x34\x56\x78' * 4`. This was done to support `BINARY(16)` columns in sqlalchemy, [#1541](https://github.com/pydantic/pydantic/pull/1541) by [@shawnwall](https://github.com/shawnwall) +* Add a test assertion that `default_factory` can return a singleton, [#1523](https://github.com/pydantic/pydantic/pull/1523) by [@therefromhere](https://github.com/therefromhere) +* Add `NameEmail.__eq__` so duplicate `NameEmail` instances are evaluated as equal, [#1514](https://github.com/pydantic/pydantic/pull/1514) by [@stephen-bunn](https://github.com/stephen-bunn) +* Add datamodel-code-generator link in pydantic document site, [#1500](https://github.com/pydantic/pydantic/pull/1500) by [@koxudaxi](https://github.com/koxudaxi) +* Added a "Discussion of Pydantic" section to the documentation, with a link to "Pydantic Introduction" video by Alexander Hultnér, [#1499](https://github.com/pydantic/pydantic/pull/1499) by [@hultner](https://github.com/hultner) +* Avoid some side effects of `default_factory` by calling it only once + if possible and by not setting a default value in the schema, [#1491](https://github.com/pydantic/pydantic/pull/1491) by [@PrettyWood](https://github.com/PrettyWood) +* Added docs about dumping dataclasses to JSON, [#1487](https://github.com/pydantic/pydantic/pull/1487) by [@mikegrima](https://github.com/mikegrima) +* Make `BaseModel.__signature__` class-only, so getting `__signature__` from model instance will raise `AttributeError`, [#1466](https://github.com/pydantic/pydantic/pull/1466) by [@Bobronium](https://github.com/Bobronium) +* include `'format': 'password'` in the schema for secret types, [#1424](https://github.com/pydantic/pydantic/pull/1424) by [@atheuz](https://github.com/atheuz) +* Modify schema constraints on `ConstrainedFloat` so that `exclusiveMinimum` and + minimum are not included in the schema if they are equal to `-math.inf` and + `exclusiveMaximum` and `maximum` are not included if they are equal to `math.inf`, [#1417](https://github.com/pydantic/pydantic/pull/1417) by [@vdwees](https://github.com/vdwees) +* Squash internal `__root__` dicts in `.dict()` (and, by extension, in `.json()`), [#1414](https://github.com/pydantic/pydantic/pull/1414) by [@patrickkwang](https://github.com/patrickkwang) +* Move `const` validator to post-validators so it validates the parsed value, [#1410](https://github.com/pydantic/pydantic/pull/1410) by [@selimb](https://github.com/selimb) +* Fix model validation to handle nested literals, e.g. `Literal['foo', Literal['bar']]`, [#1364](https://github.com/pydantic/pydantic/pull/1364) by [@DBCerigo](https://github.com/DBCerigo) +* Remove `user_required = True` from `RedisDsn`, neither user nor password are required, [#1275](https://github.com/pydantic/pydantic/pull/1275) by [@samuelcolvin](https://github.com/samuelcolvin) +* Remove extra `allOf` from schema for fields with `Union` and custom `Field`, [#1209](https://github.com/pydantic/pydantic/pull/1209) by [@mostaphaRoudsari](https://github.com/mostaphaRoudsari) +* Updates OpenAPI schema generation to output all enums as separate models. + Instead of inlining the enum values in the model schema, models now use a `$ref` + property to point to the enum definition, [#1173](https://github.com/pydantic/pydantic/pull/1173) by [@calvinwyoung](https://github.com/calvinwyoung) + +## v1.5.1 (2020-04-23) + +* Signature generation with `extra: allow` never uses a field name, [#1418](https://github.com/pydantic/pydantic/pull/1418) by [@prettywood](https://github.com/prettywood) +* Avoid mutating `Field` default value, [#1412](https://github.com/pydantic/pydantic/pull/1412) by [@prettywood](https://github.com/prettywood) + +## v1.5 (2020-04-18) + +* Make includes/excludes arguments for `.dict()`, `._iter()`, ..., immutable, [#1404](https://github.com/pydantic/pydantic/pull/1404) by [@AlexECX](https://github.com/AlexECX) +* Always use a field's real name with includes/excludes in `model._iter()`, regardless of `by_alias`, [#1397](https://github.com/pydantic/pydantic/pull/1397) by [@AlexECX](https://github.com/AlexECX) +* Update constr regex example to include start and end lines, [#1396](https://github.com/pydantic/pydantic/pull/1396) by [@lmcnearney](https://github.com/lmcnearney) +* Confirm that shallow `model.copy()` does make a shallow copy of attributes, [#1383](https://github.com/pydantic/pydantic/pull/1383) by [@samuelcolvin](https://github.com/samuelcolvin) +* Renaming `model_name` argument of `main.create_model()` to `__model_name` to allow using `model_name` as a field name, [#1367](https://github.com/pydantic/pydantic/pull/1367) by [@kittipatv](https://github.com/kittipatv) +* Replace raising of exception to silent passing for non-Var attributes in mypy plugin, [#1345](https://github.com/pydantic/pydantic/pull/1345) by [@b0g3r](https://github.com/b0g3r) +* Remove `typing_extensions` dependency for Python 3.8, [#1342](https://github.com/pydantic/pydantic/pull/1342) by [@prettywood](https://github.com/prettywood) +* Make `SecretStr` and `SecretBytes` initialization idempotent, [#1330](https://github.com/pydantic/pydantic/pull/1330) by [@atheuz](https://github.com/atheuz) +* document making secret types dumpable using the json method, [#1328](https://github.com/pydantic/pydantic/pull/1328) by [@atheuz](https://github.com/atheuz) +* Move all testing and build to github actions, add windows and macos binaries, + thank you [@StephenBrown2](https://github.com/StephenBrown2) for much help, [#1326](https://github.com/pydantic/pydantic/pull/1326) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix card number length check in `PaymentCardNumber`, `PaymentCardBrand` now inherits from `str`, [#1317](https://github.com/pydantic/pydantic/pull/1317) by [@samuelcolvin](https://github.com/samuelcolvin) +* Have `BaseModel` inherit from `Representation` to make mypy happy when overriding `__str__`, [#1310](https://github.com/pydantic/pydantic/pull/1310) by [@FuegoFro](https://github.com/FuegoFro) +* Allow `None` as input to all optional list fields, [#1307](https://github.com/pydantic/pydantic/pull/1307) by [@prettywood](https://github.com/prettywood) +* Add `datetime` field to `default_factory` example, [#1301](https://github.com/pydantic/pydantic/pull/1301) by [@StephenBrown2](https://github.com/StephenBrown2) +* Allow subclasses of known types to be encoded with superclass encoder, [#1291](https://github.com/pydantic/pydantic/pull/1291) by [@StephenBrown2](https://github.com/StephenBrown2) +* Exclude exported fields from all elements of a list/tuple of submodels/dicts with `'__all__'`, [#1286](https://github.com/pydantic/pydantic/pull/1286) by [@masalim2](https://github.com/masalim2) +* Add pydantic.color.Color objects as available input for Color fields, [#1258](https://github.com/pydantic/pydantic/pull/1258) by [@leosussan](https://github.com/leosussan) +* In examples, type nullable fields as `Optional`, so that these are valid mypy annotations, [#1248](https://github.com/pydantic/pydantic/pull/1248) by [@kokes](https://github.com/kokes) +* Make `pattern_validator()` accept pre-compiled `Pattern` objects. Fix `str_validator()` return type to `str`, [#1237](https://github.com/pydantic/pydantic/pull/1237) by [@adamgreg](https://github.com/adamgreg) +* Document how to manage Generics and inheritance, [#1229](https://github.com/pydantic/pydantic/pull/1229) by [@esadruhn](https://github.com/esadruhn) +* `update_forward_refs()` method of BaseModel now copies `__dict__` of class module instead of modyfying it, [#1228](https://github.com/pydantic/pydantic/pull/1228) by [@paul-ilyin](https://github.com/paul-ilyin) +* Support instance methods and class methods with `@validate_arguments`, [#1222](https://github.com/pydantic/pydantic/pull/1222) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add `default_factory` argument to `Field` to create a dynamic default value by passing a zero-argument callable, [#1210](https://github.com/pydantic/pydantic/pull/1210) by [@prettywood](https://github.com/prettywood) +* add support for `NewType` of `List`, `Optional`, etc, [#1207](https://github.com/pydantic/pydantic/pull/1207) by [@Kazy](https://github.com/Kazy) +* fix mypy signature for `root_validator`, [#1192](https://github.com/pydantic/pydantic/pull/1192) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fixed parsing of nested 'custom root type' models, [#1190](https://github.com/pydantic/pydantic/pull/1190) by [@Shados](https://github.com/Shados) +* Add `validate_arguments` function decorator which checks the arguments to a function matches type annotations, [#1179](https://github.com/pydantic/pydantic/pull/1179) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add `__signature__` to models, [#1034](https://github.com/pydantic/pydantic/pull/1034) by [@Bobronium](https://github.com/Bobronium) +* Refactor `._iter()` method, 10x speed boost for `dict(model)`, [#1017](https://github.com/pydantic/pydantic/pull/1017) by [@Bobronium](https://github.com/Bobronium) + +## v1.4 (2020-01-24) + +* **Breaking Change:** alias precedence logic changed so aliases on a field always take priority over + an alias from `alias_generator` to avoid buggy/unexpected behaviour, + see [here](https://docs.pydantic.dev/usage/model_config/#alias-precedence) for details, [#1178](https://github.com/pydantic/pydantic/pull/1178) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add support for unicode and punycode in TLDs, [#1182](https://github.com/pydantic/pydantic/pull/1182) by [@jamescurtin](https://github.com/jamescurtin) +* Fix `cls` argument in validators during assignment, [#1172](https://github.com/pydantic/pydantic/pull/1172) by [@samuelcolvin](https://github.com/samuelcolvin) +* completing Luhn algorithm for `PaymentCardNumber`, [#1166](https://github.com/pydantic/pydantic/pull/1166) by [@cuencandres](https://github.com/cuencandres) +* add support for generics that implement `__get_validators__` like a custom data type, [#1159](https://github.com/pydantic/pydantic/pull/1159) by [@tiangolo](https://github.com/tiangolo) +* add support for infinite generators with `Iterable`, [#1152](https://github.com/pydantic/pydantic/pull/1152) by [@tiangolo](https://github.com/tiangolo) +* fix `url_regex` to accept schemas with `+`, `-` and `.` after the first character, [#1142](https://github.com/pydantic/pydantic/pull/1142) by [@samuelcolvin](https://github.com/samuelcolvin) +* move `version_info()` to `version.py`, suggest its use in issues, [#1138](https://github.com/pydantic/pydantic/pull/1138) by [@samuelcolvin](https://github.com/samuelcolvin) +* Improve pydantic import time by roughly 50% by deferring some module loading and regex compilation, [#1127](https://github.com/pydantic/pydantic/pull/1127) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix `EmailStr` and `NameEmail` to accept instances of themselves in cython, [#1126](https://github.com/pydantic/pydantic/pull/1126) by [@koxudaxi](https://github.com/koxudaxi) +* Pass model class to the `Config.schema_extra` callable, [#1125](https://github.com/pydantic/pydantic/pull/1125) by [@therefromhere](https://github.com/therefromhere) +* Fix regex for username and password in URLs, [#1115](https://github.com/pydantic/pydantic/pull/1115) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add support for nested generic models, [#1104](https://github.com/pydantic/pydantic/pull/1104) by [@dmontagu](https://github.com/dmontagu) +* add `__all__` to `__init__.py` to prevent "implicit reexport" errors from mypy, [#1072](https://github.com/pydantic/pydantic/pull/1072) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add support for using "dotenv" files with `BaseSettings`, [#1011](https://github.com/pydantic/pydantic/pull/1011) by [@acnebs](https://github.com/acnebs) + +## v1.3 (2019-12-21) + +* Change `schema` and `schema_model` to handle dataclasses by using their `__pydantic_model__` feature, [#792](https://github.com/pydantic/pydantic/pull/792) by [@aviramha](https://github.com/aviramha) +* Added option for `root_validator` to be skipped if values validation fails using keyword `skip_on_failure=True`, [#1049](https://github.com/pydantic/pydantic/pull/1049) by [@aviramha](https://github.com/aviramha) +* Allow `Config.schema_extra` to be a callable so that the generated schema can be post-processed, [#1054](https://github.com/pydantic/pydantic/pull/1054) by [@selimb](https://github.com/selimb) +* Update mypy to version 0.750, [#1057](https://github.com/pydantic/pydantic/pull/1057) by [@dmontagu](https://github.com/dmontagu) +* Trick Cython into allowing str subclassing, [#1061](https://github.com/pydantic/pydantic/pull/1061) by [@skewty](https://github.com/skewty) +* Prevent type attributes being added to schema unless the attribute `__schema_attributes__` is `True`, [#1064](https://github.com/pydantic/pydantic/pull/1064) by [@samuelcolvin](https://github.com/samuelcolvin) +* Change `BaseModel.parse_file` to use `Config.json_loads`, [#1067](https://github.com/pydantic/pydantic/pull/1067) by [@kierandarcy](https://github.com/kierandarcy) +* Fix for optional `Json` fields, [#1073](https://github.com/pydantic/pydantic/pull/1073) by [@volker48](https://github.com/volker48) +* Change the default number of threads used when compiling with cython to one, + allow override via the `CYTHON_NTHREADS` environment variable, [#1074](https://github.com/pydantic/pydantic/pull/1074) by [@samuelcolvin](https://github.com/samuelcolvin) +* Run FastAPI tests during Pydantic's CI tests, [#1075](https://github.com/pydantic/pydantic/pull/1075) by [@tiangolo](https://github.com/tiangolo) +* My mypy strictness constraints, and associated tweaks to type annotations, [#1077](https://github.com/pydantic/pydantic/pull/1077) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add `__eq__` to SecretStr and SecretBytes to allow "value equals", [#1079](https://github.com/pydantic/pydantic/pull/1079) by [@sbv-trueenergy](https://github.com/sbv-trueenergy) +* Fix schema generation for nested None case, [#1088](https://github.com/pydantic/pydantic/pull/1088) by [@lutostag](https://github.com/lutostag) +* Consistent checks for sequence like objects, [#1090](https://github.com/pydantic/pydantic/pull/1090) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix `Config` inheritance on `BaseSettings` when used with `env_prefix`, [#1091](https://github.com/pydantic/pydantic/pull/1091) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix for `__modify_schema__` when it conflicted with `field_class_to_schema*`, [#1102](https://github.com/pydantic/pydantic/pull/1102) by [@samuelcolvin](https://github.com/samuelcolvin) +* docs: Fix explanation of case sensitive environment variable names when populating `BaseSettings` subclass attributes, [#1105](https://github.com/pydantic/pydantic/pull/1105) by [@tribals](https://github.com/tribals) +* Rename django-rest-framework benchmark in documentation, [#1119](https://github.com/pydantic/pydantic/pull/1119) by [@frankie567](https://github.com/frankie567) + +## v1.2 (2019-11-28) + +* **Possible Breaking Change:** Add support for required `Optional` with `name: Optional[AnyType] = Field(...)` + and refactor `ModelField` creation to preserve `required` parameter value, [#1031](https://github.com/pydantic/pydantic/pull/1031) by [@tiangolo](https://github.com/tiangolo); + see [here](https://docs.pydantic.dev/usage/models/#required-optional-fields) for details +* Add benchmarks for `cattrs`, [#513](https://github.com/pydantic/pydantic/pull/513) by [@sebastianmika](https://github.com/sebastianmika) +* Add `exclude_none` option to `dict()` and friends, [#587](https://github.com/pydantic/pydantic/pull/587) by [@niknetniko](https://github.com/niknetniko) +* Add benchmarks for `valideer`, [#670](https://github.com/pydantic/pydantic/pull/670) by [@gsakkis](https://github.com/gsakkis) +* Add `parse_obj_as` and `parse_file_as` functions for ad-hoc parsing of data into arbitrary pydantic-compatible types, [#934](https://github.com/pydantic/pydantic/pull/934) by [@dmontagu](https://github.com/dmontagu) +* Add `allow_reuse` argument to validators, thus allowing validator reuse, [#940](https://github.com/pydantic/pydantic/pull/940) by [@dmontagu](https://github.com/dmontagu) +* Add support for mapping types for custom root models, [#958](https://github.com/pydantic/pydantic/pull/958) by [@dmontagu](https://github.com/dmontagu) +* Mypy plugin support for dataclasses, [#966](https://github.com/pydantic/pydantic/pull/966) by [@koxudaxi](https://github.com/koxudaxi) +* Add support for dataclasses default factory, [#968](https://github.com/pydantic/pydantic/pull/968) by [@ahirner](https://github.com/ahirner) +* Add a `ByteSize` type for converting byte string (`1GB`) to plain bytes, [#977](https://github.com/pydantic/pydantic/pull/977) by [@dgasmith](https://github.com/dgasmith) +* Fix mypy complaint about `@root_validator(pre=True)`, [#984](https://github.com/pydantic/pydantic/pull/984) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add manylinux binaries for Python 3.8 to pypi, also support manylinux2010, [#994](https://github.com/pydantic/pydantic/pull/994) by [@samuelcolvin](https://github.com/samuelcolvin) +* Adds ByteSize conversion to another unit, [#995](https://github.com/pydantic/pydantic/pull/995) by [@dgasmith](https://github.com/dgasmith) +* Fix `__str__` and `__repr__` inheritance for models, [#1022](https://github.com/pydantic/pydantic/pull/1022) by [@samuelcolvin](https://github.com/samuelcolvin) +* add testimonials section to docs, [#1025](https://github.com/pydantic/pydantic/pull/1025) by [@sullivancolin](https://github.com/sullivancolin) +* Add support for `typing.Literal` for Python 3.8, [#1026](https://github.com/pydantic/pydantic/pull/1026) by [@dmontagu](https://github.com/dmontagu) + +## v1.1.1 (2019-11-20) + +* Fix bug where use of complex fields on sub-models could cause fields to be incorrectly configured, [#1015](https://github.com/pydantic/pydantic/pull/1015) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.1 (2019-11-07) + +* Add a mypy plugin for type checking `BaseModel.__init__` and more, [#722](https://github.com/pydantic/pydantic/pull/722) by [@dmontagu](https://github.com/dmontagu) +* Change return type typehint for `GenericModel.__class_getitem__` to prevent PyCharm warnings, [#936](https://github.com/pydantic/pydantic/pull/936) by [@dmontagu](https://github.com/dmontagu) +* Fix usage of `Any` to allow `None`, also support `TypeVar` thus allowing use of un-parameterised collection types + e.g. `Dict` and `List`, [#962](https://github.com/pydantic/pydantic/pull/962) by [@samuelcolvin](https://github.com/samuelcolvin) +* Set `FieldInfo` on subfields to fix schema generation for complex nested types, [#965](https://github.com/pydantic/pydantic/pull/965) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.0 (2019-10-23) + +* **Breaking Change:** deprecate the `Model.fields` property, use `Model.__fields__` instead, [#883](https://github.com/pydantic/pydantic/pull/883) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change:** Change the precedence of aliases so child model aliases override parent aliases, + including using `alias_generator`, [#904](https://github.com/pydantic/pydantic/pull/904) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking change:** Rename `skip_defaults` to `exclude_unset`, and add ability to exclude actual defaults, [#915](https://github.com/pydantic/pydantic/pull/915) by [@dmontagu](https://github.com/dmontagu) +* Add `**kwargs` to `pydantic.main.ModelMetaclass.__new__` so `__init_subclass__` can take custom parameters on extended + `BaseModel` classes, [#867](https://github.com/pydantic/pydantic/pull/867) by [@retnikt](https://github.com/retnikt) +* Fix field of a type that has a default value, [#880](https://github.com/pydantic/pydantic/pull/880) by [@koxudaxi](https://github.com/koxudaxi) +* Use `FutureWarning` instead of `DeprecationWarning` when `alias` instead of `env` is used for settings models, [#881](https://github.com/pydantic/pydantic/pull/881) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix issue with `BaseSettings` inheritance and `alias` getting set to `None`, [#882](https://github.com/pydantic/pydantic/pull/882) by [@samuelcolvin](https://github.com/samuelcolvin) +* Modify `__repr__` and `__str__` methods to be consistent across all public classes, add `__pretty__` to support + python-devtools, [#884](https://github.com/pydantic/pydantic/pull/884) by [@samuelcolvin](https://github.com/samuelcolvin) +* deprecation warning for `case_insensitive` on `BaseSettings` config, [#885](https://github.com/pydantic/pydantic/pull/885) by [@samuelcolvin](https://github.com/samuelcolvin) +* For `BaseSettings` merge environment variables and in-code values recursively, as long as they create a valid object + when merged together, to allow splitting init arguments, [#888](https://github.com/pydantic/pydantic/pull/888) by [@idmitrievsky](https://github.com/idmitrievsky) +* change secret types example, [#890](https://github.com/pydantic/pydantic/pull/890) by [@ashears](https://github.com/ashears) +* Change the signature of `Model.construct()` to be more user-friendly, document `construct()` usage, [#898](https://github.com/pydantic/pydantic/pull/898) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add example for the `construct()` method, [#907](https://github.com/pydantic/pydantic/pull/907) by [@ashears](https://github.com/ashears) +* Improve use of `Field` constraints on complex types, raise an error if constraints are not enforceable, + also support tuples with an ellipsis `Tuple[X, ...]`, `Sequence` and `FrozenSet` in schema, [#909](https://github.com/pydantic/pydantic/pull/909) by [@samuelcolvin](https://github.com/samuelcolvin) +* update docs for bool missing valid value, [#911](https://github.com/pydantic/pydantic/pull/911) by [@trim21](https://github.com/trim21) +* Better `str`/`repr` logic for `ModelField`, [#912](https://github.com/pydantic/pydantic/pull/912) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix `ConstrainedList`, update schema generation to reflect `min_items` and `max_items` `Field()` arguments, [#917](https://github.com/pydantic/pydantic/pull/917) by [@samuelcolvin](https://github.com/samuelcolvin) +* Allow abstracts sets (eg. dict keys) in the `include` and `exclude` arguments of `dict()`, [#921](https://github.com/pydantic/pydantic/pull/921) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix JSON serialization errors on `ValidationError.json()` by using `pydantic_encoder`, [#922](https://github.com/pydantic/pydantic/pull/922) by [@samuelcolvin](https://github.com/samuelcolvin) +* Clarify usage of `remove_untouched`, improve error message for types with no validators, [#926](https://github.com/pydantic/pydantic/pull/926) by [@retnikt](https://github.com/retnikt) + +## v1.0b2 (2019-10-07) + +* Mark `StrictBool` typecheck as `bool` to allow for default values without mypy errors, [#690](https://github.com/pydantic/pydantic/pull/690) by [@dmontagu](https://github.com/dmontagu) +* Transfer the documentation build from sphinx to mkdocs, re-write much of the documentation, [#856](https://github.com/pydantic/pydantic/pull/856) by [@samuelcolvin](https://github.com/samuelcolvin) +* Add support for custom naming schemes for `GenericModel` subclasses, [#859](https://github.com/pydantic/pydantic/pull/859) by [@dmontagu](https://github.com/dmontagu) +* Add `if TYPE_CHECKING:` to the excluded lines for test coverage, [#874](https://github.com/pydantic/pydantic/pull/874) by [@dmontagu](https://github.com/dmontagu) +* Rename `allow_population_by_alias` to `allow_population_by_field_name`, remove unnecessary warning about it, [#875](https://github.com/pydantic/pydantic/pull/875) by [@samuelcolvin](https://github.com/samuelcolvin) + +## v1.0b1 (2019-10-01) + +* **Breaking Change:** rename `Schema` to `Field`, make it a function to placate mypy, [#577](https://github.com/pydantic/pydantic/pull/577) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change:** modify parsing behavior for `bool`, [#617](https://github.com/pydantic/pydantic/pull/617) by [@dmontagu](https://github.com/dmontagu) +* **Breaking Change:** `get_validators` is no longer recognised, use `__get_validators__`. + `Config.ignore_extra` and `Config.allow_extra` are no longer recognised, use `Config.extra`, [#720](https://github.com/pydantic/pydantic/pull/720) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change:** modify default config settings for `BaseSettings`; `case_insensitive` renamed to `case_sensitive`, + default changed to `case_sensitive = False`, `env_prefix` default changed to `''` - e.g. no prefix, [#721](https://github.com/pydantic/pydantic/pull/721) by [@dmontagu](https://github.com/dmontagu) +* **Breaking change:** Implement `root_validator` and rename root errors from `__obj__` to `__root__`, [#729](https://github.com/pydantic/pydantic/pull/729) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change:** alter the behaviour of `dict(model)` so that sub-models are nolonger + converted to dictionaries, [#733](https://github.com/pydantic/pydantic/pull/733) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking change:** Added `initvars` support to `post_init_post_parse`, [#748](https://github.com/pydantic/pydantic/pull/748) by [@Raphael-C-Almeida](https://github.com/Raphael-C-Almeida) +* **Breaking Change:** Make `BaseModel.json()` only serialize the `__root__` key for models with custom root, [#752](https://github.com/pydantic/pydantic/pull/752) by [@dmontagu](https://github.com/dmontagu) +* **Breaking Change:** complete rewrite of `URL` parsing logic, [#755](https://github.com/pydantic/pydantic/pull/755) by [@samuelcolvin](https://github.com/samuelcolvin) +* **Breaking Change:** preserve superclass annotations for field-determination when not provided in subclass, [#757](https://github.com/pydantic/pydantic/pull/757) by [@dmontagu](https://github.com/dmontagu) +* **Breaking Change:** `BaseSettings` now uses the special `env` settings to define which environment variables to + read, not aliases, [#847](https://github.com/pydantic/pydantic/pull/847) by [@samuelcolvin](https://github.com/samuelcolvin) +* add support for `assert` statements inside validators, [#653](https://github.com/pydantic/pydantic/pull/653) by [@abdusco](https://github.com/abdusco) +* Update documentation to specify the use of `pydantic.dataclasses.dataclass` and subclassing `pydantic.BaseModel`, [#710](https://github.com/pydantic/pydantic/pull/710) by [@maddosaurus](https://github.com/maddosaurus) +* Allow custom JSON decoding and encoding via `json_loads` and `json_dumps` `Config` properties, [#714](https://github.com/pydantic/pydantic/pull/714) by [@samuelcolvin](https://github.com/samuelcolvin) +* make all annotated fields occur in the order declared, [#715](https://github.com/pydantic/pydantic/pull/715) by [@dmontagu](https://github.com/dmontagu) +* use pytest to test `mypy` integration, [#735](https://github.com/pydantic/pydantic/pull/735) by [@dmontagu](https://github.com/dmontagu) +* add `__repr__` method to `ErrorWrapper`, [#738](https://github.com/pydantic/pydantic/pull/738) by [@samuelcolvin](https://github.com/samuelcolvin) +* Added support for `FrozenSet` members in dataclasses, and a better error when attempting to use types from the `typing` module that are not supported by Pydantic, [#745](https://github.com/pydantic/pydantic/pull/745) by [@djpetti](https://github.com/djpetti) +* add documentation for Pycharm Plugin, [#750](https://github.com/pydantic/pydantic/pull/750) by [@koxudaxi](https://github.com/koxudaxi) +* fix broken examples in the docs, [#753](https://github.com/pydantic/pydantic/pull/753) by [@dmontagu](https://github.com/dmontagu) +* moving typing related objects into `pydantic.typing`, [#761](https://github.com/pydantic/pydantic/pull/761) by [@samuelcolvin](https://github.com/samuelcolvin) +* Minor performance improvements to `ErrorWrapper`, `ValidationError` and datetime parsing, [#763](https://github.com/pydantic/pydantic/pull/763) by [@samuelcolvin](https://github.com/samuelcolvin) +* Improvements to `datetime`/`date`/`time`/`timedelta` types: more descriptive errors, + change errors to `value_error` not `type_error`, support bytes, [#766](https://github.com/pydantic/pydantic/pull/766) by [@samuelcolvin](https://github.com/samuelcolvin) +* fix error messages for `Literal` types with multiple allowed values, [#770](https://github.com/pydantic/pydantic/pull/770) by [@dmontagu](https://github.com/dmontagu) +* Improved auto-generated `title` field in JSON schema by converting underscore to space, [#772](https://github.com/pydantic/pydantic/pull/772) by [@skewty](https://github.com/skewty) +* support `mypy --no-implicit-reexport` for dataclasses, also respect `--no-implicit-reexport` in pydantic itself, [#783](https://github.com/pydantic/pydantic/pull/783) by [@samuelcolvin](https://github.com/samuelcolvin) +* add the `PaymentCardNumber` type, [#790](https://github.com/pydantic/pydantic/pull/790) by [@matin](https://github.com/matin) +* Fix const validations for lists, [#794](https://github.com/pydantic/pydantic/pull/794) by [@hmvp](https://github.com/hmvp) +* Set `additionalProperties` to false in schema for models with extra fields disallowed, [#796](https://github.com/pydantic/pydantic/pull/796) by [@Code0x58](https://github.com/Code0x58) +* `EmailStr` validation method now returns local part case-sensitive per RFC 5321, [#798](https://github.com/pydantic/pydantic/pull/798) by [@henriklindgren](https://github.com/henriklindgren) +* Added ability to validate strictness to `ConstrainedFloat`, `ConstrainedInt` and `ConstrainedStr` and added + `StrictFloat` and `StrictInt` classes, [#799](https://github.com/pydantic/pydantic/pull/799) by [@DerRidda](https://github.com/DerRidda) +* Improve handling of `None` and `Optional`, replace `whole` with `each_item` (inverse meaning, default `False`) + on validators, [#803](https://github.com/pydantic/pydantic/pull/803) by [@samuelcolvin](https://github.com/samuelcolvin) +* add support for `Type[T]` type hints, [#807](https://github.com/pydantic/pydantic/pull/807) by [@timonbimon](https://github.com/timonbimon) +* Performance improvements from removing `change_exceptions`, change how pydantic error are constructed, [#819](https://github.com/pydantic/pydantic/pull/819) by [@samuelcolvin](https://github.com/samuelcolvin) +* Fix the error message arising when a `BaseModel`-type model field causes a `ValidationError` during parsing, [#820](https://github.com/pydantic/pydantic/pull/820) by [@dmontagu](https://github.com/dmontagu) +* allow `getter_dict` on `Config`, modify `GetterDict` to be more like a `Mapping` object and thus easier to work with, [#821](https://github.com/pydantic/pydantic/pull/821) by [@samuelcolvin](https://github.com/samuelcolvin) +* Only check `TypeVar` param on base `GenericModel` class, [#842](https://github.com/pydantic/pydantic/pull/842) by [@zpencerq](https://github.com/zpencerq) +* rename `Model._schema_cache` -> `Model.__schema_cache__`, `Model._json_encoder` -> `Model.__json_encoder__`, + `Model._custom_root_type` -> `Model.__custom_root_type__`, [#851](https://github.com/pydantic/pydantic/pull/851) by [@samuelcolvin](https://github.com/samuelcolvin) + + +... see [here](https://docs.pydantic.dev/changelog/#v0322-2019-08-17) for earlier changes. diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/RECORD new file mode 100644 index 00000000..79559575 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/RECORD @@ -0,0 +1,196 @@ +pydantic-2.5.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pydantic-2.5.0.dist-info/METADATA,sha256=ue6gfzl4oP6G5Qi-Ymny4KNpXWNhAjzZIoQxTiM326k,174564 +pydantic-2.5.0.dist-info/RECORD,, +pydantic-2.5.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic-2.5.0.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87 +pydantic-2.5.0.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129 +pydantic/__init__.py,sha256=0jUaDGNpcQr0AHpu-gdgH6_gK7i5raGe4btfuiKbs2w,12401 +pydantic/__pycache__/__init__.cpython-312.pyc,, +pydantic/__pycache__/_migration.cpython-312.pyc,, +pydantic/__pycache__/alias_generators.cpython-312.pyc,, +pydantic/__pycache__/annotated_handlers.cpython-312.pyc,, +pydantic/__pycache__/class_validators.cpython-312.pyc,, +pydantic/__pycache__/color.cpython-312.pyc,, +pydantic/__pycache__/config.cpython-312.pyc,, +pydantic/__pycache__/dataclasses.cpython-312.pyc,, +pydantic/__pycache__/datetime_parse.cpython-312.pyc,, +pydantic/__pycache__/decorator.cpython-312.pyc,, +pydantic/__pycache__/env_settings.cpython-312.pyc,, +pydantic/__pycache__/error_wrappers.cpython-312.pyc,, +pydantic/__pycache__/errors.cpython-312.pyc,, +pydantic/__pycache__/fields.cpython-312.pyc,, +pydantic/__pycache__/functional_serializers.cpython-312.pyc,, +pydantic/__pycache__/functional_validators.cpython-312.pyc,, +pydantic/__pycache__/generics.cpython-312.pyc,, +pydantic/__pycache__/json.cpython-312.pyc,, +pydantic/__pycache__/json_schema.cpython-312.pyc,, +pydantic/__pycache__/main.cpython-312.pyc,, +pydantic/__pycache__/mypy.cpython-312.pyc,, +pydantic/__pycache__/networks.cpython-312.pyc,, +pydantic/__pycache__/parse.cpython-312.pyc,, +pydantic/__pycache__/root_model.cpython-312.pyc,, +pydantic/__pycache__/schema.cpython-312.pyc,, +pydantic/__pycache__/tools.cpython-312.pyc,, +pydantic/__pycache__/type_adapter.cpython-312.pyc,, +pydantic/__pycache__/types.cpython-312.pyc,, +pydantic/__pycache__/typing.cpython-312.pyc,, +pydantic/__pycache__/utils.cpython-312.pyc,, +pydantic/__pycache__/validate_call_decorator.cpython-312.pyc,, +pydantic/__pycache__/validators.cpython-312.pyc,, +pydantic/__pycache__/version.cpython-312.pyc,, +pydantic/__pycache__/warnings.cpython-312.pyc,, +pydantic/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/_internal/__pycache__/__init__.cpython-312.pyc,, +pydantic/_internal/__pycache__/_config.cpython-312.pyc,, +pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc,, +pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc,, +pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc,, +pydantic/_internal/__pycache__/_decorators.cpython-312.pyc,, +pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc,, +pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc,, +pydantic/_internal/__pycache__/_fields.cpython-312.pyc,, +pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc,, +pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc,, +pydantic/_internal/__pycache__/_generics.cpython-312.pyc,, +pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc,, +pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc,, +pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc,, +pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc,, +pydantic/_internal/__pycache__/_repr.cpython-312.pyc,, +pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc,, +pydantic/_internal/__pycache__/_std_types_schema.cpython-312.pyc,, +pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc,, +pydantic/_internal/__pycache__/_utils.cpython-312.pyc,, +pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc,, +pydantic/_internal/__pycache__/_validators.cpython-312.pyc,, +pydantic/_internal/_config.py,sha256=fHqmP2A3BJeBZkryRUQrt0t9tGdRg3cUbOb-r0Q4-vw,11408 +pydantic/_internal/_core_metadata.py,sha256=Da-e0-DXK__dJvog0e8CZLQ4r_k9RpldG6KQTGrYlHg,3521 +pydantic/_internal/_core_utils.py,sha256=OAkGU1PxigjfUDdbdXm0xIdLTDzUR7g0U_CY_tgS6Lc,25009 +pydantic/_internal/_dataclasses.py,sha256=v8WjfAlFdyu767lk50iyxBtpdCfERngdippr4v0XVnM,10636 +pydantic/_internal/_decorators.py,sha256=Sy6HuCGgme69ttWkS2EqRtT9VZTjAK2_33L2l9JOTNE,30856 +pydantic/_internal/_decorators_v1.py,sha256=_m9TskhZh9yPUn7Jmy3KbKa3UDREQWyMm5NXyOJM3R8,6266 +pydantic/_internal/_discriminated_union.py,sha256=QRh8P2_i0oKk6j4lW4Y9z8EV8AXFhWOT8mAszsOsrZ0,26598 +pydantic/_internal/_fields.py,sha256=sPwEKQnGXpTwkXLqwwPy-YrBvjr35Maux1xBtP_AQDw,12638 +pydantic/_internal/_forward_ref.py,sha256=5n3Y7-3AKLn8_FS3Yc7KutLiPUhyXmAtkEZOaFnonwM,611 +pydantic/_internal/_generate_schema.py,sha256=VQ5S73Dhh_MRW9z43rBdzMIH1rnlfh4aAgcIDGech3Y,96517 +pydantic/_internal/_generics.py,sha256=i9voXYIspptcC1-qXCNynhq0ijpQ1AwgkOXL6bDs3SM,22344 +pydantic/_internal/_internal_dataclass.py,sha256=NswLpapJY_61NFHBAXYpgFdxMmIX_yE9ttx_pQt_Vp8,207 +pydantic/_internal/_known_annotated_metadata.py,sha256=DokRRZNcFgyUoeAOzgB3Jp1nD5b39HxwdnE9druaKgc,16415 +pydantic/_internal/_mock_val_ser.py,sha256=5DqrtofFw4wUmZWNky6zaFwyCAbjTTD9XQc8FK2JzKc,5180 +pydantic/_internal/_model_construction.py,sha256=iWmIyLKRsVs6cUWw-FMlitbgZroBgU3ZYwyvfg67bG4,27169 +pydantic/_internal/_repr.py,sha256=APDlOwrPu07hTWTf1PgvWU2jelo80BHM8oAciS1VmP4,4485 +pydantic/_internal/_schema_generation_shared.py,sha256=eRwZ85Gj0FfabYlvM97I5997vhY4Mk3AYQJljK5B3to,4855 +pydantic/_internal/_std_types_schema.py,sha256=6Q5kGGe_7GL3aDUh5PkwH1JAaNWetAEzuWZ2MktQSfw,29085 +pydantic/_internal/_typing_extra.py,sha256=WkD7d6o_arCYzNjYsDQi8F-mwv2R-XHL2QPmzuTITxo,16863 +pydantic/_internal/_utils.py,sha256=afJfxw4kZmpnN62DbmtQYiY7nUy07W-KhVa4i4u5ugw,11714 +pydantic/_internal/_validate_call.py,sha256=2Gaum1PDs36T_CdnfT-2tGyq6x8usRAjojo1Fb3dywc,5755 +pydantic/_internal/_validators.py,sha256=GbyE9vUkMafP89hRj8Zdm2TXSpA6ynMZz8qPKqUhdlI,10054 +pydantic/_migration.py,sha256=j6TbRpJofjAX8lr-k2nVnQcBR9RD2B91I7Ulcw_ZzEo,11913 +pydantic/alias_generators.py,sha256=95F9x9P1bzzL7Z3y5F2BvEF9SMUEiT-r69SWlJao_3E,1141 +pydantic/annotated_handlers.py,sha256=iyOdMvz2-G-pe6HJ1a1EpRYn3EnktNyppmlI0YeM-Ss,4346 +pydantic/class_validators.py,sha256=iQz1Tw8FBliqEapmzB7iLkbwkJAeAx5314Vksb_Kj0g,147 +pydantic/color.py,sha256=Pq4DAe1HgmbhKlrZ5kbal23npquKd9c0RPwPPCS_OYM,21493 +pydantic/config.py,sha256=pvcDw99rpfZTcN7cKU7-hIxHUmXjhEAwxNvGzbbux1w,27545 +pydantic/dataclasses.py,sha256=WXSTlIxUPtTJjQK--WFnHcpsubeQ8G6N-I-z_FgCCII,12469 +pydantic/datetime_parse.py,sha256=5lJpo3-iBTAA9YmMuDLglP-5f2k8etayAXjEi6rfEN0,149 +pydantic/decorator.py,sha256=Qqx1UU19tpRVp05a2NIK5OdpLXN_a84HZPMjt_5BxdE,144 +pydantic/deprecated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/deprecated/__pycache__/__init__.cpython-312.pyc,, +pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc,, +pydantic/deprecated/__pycache__/config.cpython-312.pyc,, +pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc,, +pydantic/deprecated/__pycache__/decorator.cpython-312.pyc,, +pydantic/deprecated/__pycache__/json.cpython-312.pyc,, +pydantic/deprecated/__pycache__/parse.cpython-312.pyc,, +pydantic/deprecated/__pycache__/tools.cpython-312.pyc,, +pydantic/deprecated/class_validators.py,sha256=PWpcCzfDJsdgeFY2Xx3fg0OPpVnktEIL6I1C9fZXamM,9878 +pydantic/deprecated/config.py,sha256=zgaFWxmg5k6cWUs7ir_OGYS26MQJxRiblp6HPmCy0u4,2612 +pydantic/deprecated/copy_internals.py,sha256=SoUj1MevXt3fnloqNg5wivSUHSDPnuSj_YydzkEMzu0,7595 +pydantic/deprecated/decorator.py,sha256=rYviEY5ZM77OrpdBPaaitrnoFjh4ENCT_oBzvQASWjs,10903 +pydantic/deprecated/json.py,sha256=1hcwvq33cxrwIvUA6vm_rpb0qMdzxMQGiroo0jJHYtU,4465 +pydantic/deprecated/parse.py,sha256=GYT-CVRam_p13rH1bROnUlSKZf4NanvXt_KhTwkawPM,2513 +pydantic/deprecated/tools.py,sha256=2VRvcQIaJbFywkRvhFITjdkeujfunmMHgjjlioUNJp0,3278 +pydantic/env_settings.py,sha256=quxt8c9TioRg-u74gTW-GrK6r5mFXmn-J5H8FAC9Prc,147 +pydantic/error_wrappers.py,sha256=u9Dz8RgawIw8-rx7G7WGZoRtGptHXyXhHxiN9PbQ58g,149 +pydantic/errors.py,sha256=nRcIyey2FItANFNWCk6X1SyMElBw0izXYIZKL8r-d3A,4632 +pydantic/fields.py,sha256=KltRUjerVOA_f_I7MbixGfkhvuiLebgTU9D871dyx-o,46237 +pydantic/functional_serializers.py,sha256=ubcOeapLyEmvq4ZyZe0pWfHNji39Wm1BRXWXJTr177c,10780 +pydantic/functional_validators.py,sha256=XeaZmbfwF1QLNKTATNzMJGf63zkVosY1Ez2LvsMt9M4,22285 +pydantic/generics.py,sha256=T1UIBvpgur_28EIcR9Dc_Wo2r9yntzqdcR-NbnOLXB8,143 +pydantic/json.py,sha256=qk9fHVGWKNrvE-v2WxWLEm66t81JKttbySd9zjy0dnc,139 +pydantic/json_schema.py,sha256=yq80n7ybR3mkmCyyUh56FPxyu0kcHqWxg5oliHYYUL4,100969 +pydantic/main.py,sha256=GEyRlK-_mM2ANImQNpyIDUnqTe4FsSO5QJXqQqsFoHU,63149 +pydantic/mypy.py,sha256=SkQKSoJziHwuuOAAst6cKX50I92rlyknXbrB5ty0ya8,51836 +pydantic/networks.py,sha256=Qd_XZ1_9J1Svtc_Yqb_EOmzwEywNcULq9qI41udU4UA,20543 +pydantic/parse.py,sha256=BNo_W_gp1xR7kohYdHjF2m_5UNYFQxUt487-NR0RiK8,140 +pydantic/plugin/__init__.py,sha256=ig-mCaKrXm_5Dg8W-nbqIg9Agk-OH6DYAmxo3RFvl_8,6115 +pydantic/plugin/__pycache__/__init__.cpython-312.pyc,, +pydantic/plugin/__pycache__/_loader.cpython-312.pyc,, +pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc,, +pydantic/plugin/_loader.py,sha256=wW8GWTi1m14yNKg4XG9lf_BktsoBTyjO3w-andi7Hig,1972 +pydantic/plugin/_schema_validator.py,sha256=3eVp5-4IsIHEQrsCsh34oPf2bNyMJTVh3nGAH7IRC1M,5228 +pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/root_model.py,sha256=lDuFoQw_FERrwb-Ezsi6MSWXfPV9KRTnDbdbtvJfWk8,4949 +pydantic/schema.py,sha256=EkbomWuaAdv7C3V8h6xxoT4uJKy3Mwvkg064tOUbvxg,141 +pydantic/tools.py,sha256=YB4vzOx4g7reKUM_s5oTXIGxC5LGBnGsXdVICSRuh7g,140 +pydantic/type_adapter.py,sha256=VYxlODzVNkVshWwmsTu-2H3vTvMH0Bk0T-BEhDvBWkI,17788 +pydantic/types.py,sha256=vxRwhnSTxMAjk9wYYt7fE0Eewmvf6YsP7FgEdfabgPM,86235 +pydantic/typing.py,sha256=sPkx0hi_RX7qSV3BB0zzHd8ZuAKbRRI37XJI4av_HzQ,137 +pydantic/utils.py,sha256=twRV5SqiguiCrOA9GvrKvOG-TThfWYb7mEXDVXFZp2s,140 +pydantic/v1/__init__.py,sha256=iTu8CwWWvn6zM_zYJtqhie24PImW25zokitz_06kDYw,2771 +pydantic/v1/__pycache__/__init__.cpython-312.pyc,, +pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc,, +pydantic/v1/__pycache__/annotated_types.cpython-312.pyc,, +pydantic/v1/__pycache__/class_validators.cpython-312.pyc,, +pydantic/v1/__pycache__/color.cpython-312.pyc,, +pydantic/v1/__pycache__/config.cpython-312.pyc,, +pydantic/v1/__pycache__/dataclasses.cpython-312.pyc,, +pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc,, +pydantic/v1/__pycache__/decorator.cpython-312.pyc,, +pydantic/v1/__pycache__/env_settings.cpython-312.pyc,, +pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc,, +pydantic/v1/__pycache__/errors.cpython-312.pyc,, +pydantic/v1/__pycache__/fields.cpython-312.pyc,, +pydantic/v1/__pycache__/generics.cpython-312.pyc,, +pydantic/v1/__pycache__/json.cpython-312.pyc,, +pydantic/v1/__pycache__/main.cpython-312.pyc,, +pydantic/v1/__pycache__/mypy.cpython-312.pyc,, +pydantic/v1/__pycache__/networks.cpython-312.pyc,, +pydantic/v1/__pycache__/parse.cpython-312.pyc,, +pydantic/v1/__pycache__/schema.cpython-312.pyc,, +pydantic/v1/__pycache__/tools.cpython-312.pyc,, +pydantic/v1/__pycache__/types.cpython-312.pyc,, +pydantic/v1/__pycache__/typing.cpython-312.pyc,, +pydantic/v1/__pycache__/utils.cpython-312.pyc,, +pydantic/v1/__pycache__/validators.cpython-312.pyc,, +pydantic/v1/__pycache__/version.cpython-312.pyc,, +pydantic/v1/_hypothesis_plugin.py,sha256=gILcyAEfZ3u9YfKxtDxkReLpakjMou1VWC3FEcXmJgQ,14844 +pydantic/v1/annotated_types.py,sha256=dJTDUyPj4QJj4rDcNkt9xDUMGEkAnuWzDeGE2q7Wxrc,3124 +pydantic/v1/class_validators.py,sha256=0BZx0Ft19cREVHEOaA6wf_E3A0bTL4wQIGzeOinVatg,14595 +pydantic/v1/color.py,sha256=cGzck7kSD5beBkOMhda4bfTICput6dMx8GGpEU5SK5Y,16811 +pydantic/v1/config.py,sha256=h5ceeZ9HzDjUv0IZNYQoza0aNGFVo22iszY-6s0a3eM,6477 +pydantic/v1/dataclasses.py,sha256=roiVI64yCN68aMRxHEw615qgrcdEwpHAHfTEz_HlAtQ,17515 +pydantic/v1/datetime_parse.py,sha256=DhGfkbG4Vs5Oyxq3u8jM-7gFrbuUKsn-4aG2DJDJbHw,7714 +pydantic/v1/decorator.py,sha256=wzuIuKKHVjaiE97YBctCU0Vho0VRlUO-aVu1IUEczFE,10263 +pydantic/v1/env_settings.py,sha256=4PWxPYeK5jt59JJ4QGb90qU8pfC7qgGX44UESTmXdpE,14039 +pydantic/v1/error_wrappers.py,sha256=NvfemFFYx9EFLXBGeJ07MKT2MJQAJFFlx_bIoVpqgVI,5142 +pydantic/v1/errors.py,sha256=f93z30S4s5bJEl8JXh-zFCAtLDCko9ze2hKTkOimaa8,17693 +pydantic/v1/fields.py,sha256=fxTn7A17AXAHuDdz8HzFSjb8qfWhRoruwc2VOzRpUdM,50488 +pydantic/v1/generics.py,sha256=n5TTgh3EHkG1Xw3eY9A143bUN11_4m57Db5u49hkGJ8,17805 +pydantic/v1/json.py,sha256=B0gJ2WmPqw-6fsvPmgu-rwhhOy4E0JpbbYjC8HR01Ho,3346 +pydantic/v1/main.py,sha256=kC5_bcJc4zoLhRUVvNq67ACmGmRtQFvyRHDub6cw5ik,44378 +pydantic/v1/mypy.py,sha256=G8yQLLt6CodoTvGl84MP3ZpdInBtc0QoaLJ7iArHXNU,38745 +pydantic/v1/networks.py,sha256=TeV9FvCYg4ALk8j7dU1q6Ntze7yaUrCHQFEDJDnq1NI,22059 +pydantic/v1/parse.py,sha256=rrVhaWLK8t03rT3oxvC6uRLuTF5iZ2NKGvGqs4iQEM0,1810 +pydantic/v1/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pydantic/v1/schema.py,sha256=ZqIQQpjxohG0hP7Zz5W401fpm4mYNu_Crmvr5HlgvMA,47615 +pydantic/v1/tools.py,sha256=ELC66w6UaU_HzAGfJBSIP47Aq9ZGkGiWPMLkkTs6VrI,2826 +pydantic/v1/types.py,sha256=S1doibLP6gg6TVZU9TwNfL2E10mFhZwCzd9WZK8Kilo,35380 +pydantic/v1/typing.py,sha256=5_C_fiUvWiAzW3MBJaHeuy2s3Hi52rFMxTfNPHv9_os,18996 +pydantic/v1/utils.py,sha256=5w7Q3N_Fqg5H9__JQDaumw9N3EFdlc7galEsCGxEDN0,25809 +pydantic/v1/validators.py,sha256=T-t9y9L_68El9p4PYkEVGEjpetNV6luav8Iwu9iTLkM,21887 +pydantic/v1/version.py,sha256=yUT25-EekWoBCsQwsA0kQTvIKOBUST7feqZT-TrbyX4,1039 +pydantic/validate_call_decorator.py,sha256=G9qjiaBNCZ5VsSWKIE2r0lZc3u1X3Q7K3MvYOCUUyyY,1780 +pydantic/validators.py,sha256=3oPhHojp9UD3PdEZpMYMkxeLGUAabRm__zera8_T92w,145 +pydantic/version.py,sha256=8Ec2ESNIInfUUuEbHJ6ht4UNTtQYrlD7Wd_9SHZiVvY,2333 +pydantic/warnings.py,sha256=EMmscArzAer1q2XWdD5u4z3yNmzr9LehqpDTqP9CSVE,2004 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/REQUESTED similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/REQUESTED rename to Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/REQUESTED diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/WHEEL similarity index 67% rename from Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/WHEEL index 12228d41..ba1a8af2 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/WHEEL @@ -1,4 +1,4 @@ Wheel-Version: 1.0 -Generator: hatchling 1.27.0 +Generator: hatchling 1.18.0 Root-Is-Purelib: true Tag: py3-none-any diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/licenses/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/licenses/LICENSE rename to Backend/venv/lib/python3.12/site-packages/pydantic-2.5.0.dist-info/licenses/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__init__.py b/Backend/venv/lib/python3.12/site-packages/pydantic/__init__.py index 01212849..4d5fef38 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/__init__.py @@ -1,14 +1,9 @@ -from importlib import import_module -from typing import TYPE_CHECKING -from warnings import warn +import typing from ._migration import getattr_migration -from .version import VERSION, _ensure_pydantic_core_version +from .version import VERSION -_ensure_pydantic_core_version() -del _ensure_pydantic_core_version - -if TYPE_CHECKING: +if typing.TYPE_CHECKING: # import of virtually everything is supported via `__getattr__` below, # but we need them here for type checking and IDE support import pydantic_core @@ -21,11 +16,11 @@ if TYPE_CHECKING: ) from . import dataclasses - from .aliases import AliasChoices, AliasGenerator, AliasPath + from ._internal._generate_schema import GenerateSchema as GenerateSchema from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler - from .config import ConfigDict, with_config + from .config import ConfigDict from .errors import * - from .fields import Field, PrivateAttr, computed_field + from .fields import AliasChoices, AliasPath, Field, PrivateAttr, computed_field from .functional_serializers import ( PlainSerializer, SerializeAsAny, @@ -37,10 +32,8 @@ if TYPE_CHECKING: AfterValidator, BeforeValidator, InstanceOf, - ModelWrapValidatorHandler, PlainValidator, SkipValidation, - ValidateAs, WrapValidator, field_validator, model_validator, @@ -51,16 +44,7 @@ if TYPE_CHECKING: from .type_adapter import TypeAdapter from .types import * from .validate_call_decorator import validate_call - from .warnings import ( - PydanticDeprecatedSince20, - PydanticDeprecatedSince26, - PydanticDeprecatedSince29, - PydanticDeprecatedSince210, - PydanticDeprecatedSince211, - PydanticDeprecatedSince212, - PydanticDeprecationWarning, - PydanticExperimentalWarning, - ) + from .warnings import PydanticDeprecatedSince20, PydanticDeprecationWarning # this encourages pycharm to import `ValidationError` from here, not pydantic_core ValidationError = pydantic_core.ValidationError @@ -81,9 +65,7 @@ __all__ = ( 'PlainValidator', 'WrapValidator', 'SkipValidation', - 'ValidateAs', 'InstanceOf', - 'ModelWrapValidatorHandler', # JSON Schema 'WithJsonSchema', # deprecated V1 functional validators, these are imported via `__getattr__` below @@ -97,7 +79,6 @@ __all__ = ( 'WrapSerializer', # config 'ConfigDict', - 'with_config', # deprecated V1 config, these are imported via `__getattr__` below 'BaseConfig', 'Extra', @@ -110,15 +91,12 @@ __all__ = ( 'PydanticImportError', 'PydanticUndefinedAnnotation', 'PydanticInvalidForJsonSchema', - 'PydanticForbiddenQualifier', # fields + 'AliasPath', + 'AliasChoices', 'Field', 'computed_field', 'PrivateAttr', - # alias - 'AliasChoices', - 'AliasGenerator', - 'AliasPath', # main 'BaseModel', 'create_model', @@ -127,9 +105,6 @@ __all__ = ( 'AnyHttpUrl', 'FileUrl', 'HttpUrl', - 'FtpUrl', - 'WebsocketUrl', - 'AnyWebsocketUrl', 'UrlConstraints', 'EmailStr', 'NameEmail', @@ -142,11 +117,8 @@ __all__ = ( 'RedisDsn', 'MongoDsn', 'KafkaDsn', - 'NatsDsn', 'MySQLDsn', 'MariaDBDsn', - 'ClickHouseDsn', - 'SnowflakeDsn', 'validate_email', # root_model 'RootModel', @@ -181,17 +153,12 @@ __all__ = ( 'UUID3', 'UUID4', 'UUID5', - 'UUID6', - 'UUID7', - 'UUID8', 'FilePath', 'DirectoryPath', 'NewPath', 'Json', - 'Secret', 'SecretStr', 'SecretBytes', - 'SocketPath', 'StrictBool', 'StrictBytes', 'StrictInt', @@ -217,7 +184,6 @@ __all__ = ( 'Tag', 'Discriminator', 'JsonValue', - 'FailFast', # type_adapter 'TypeAdapter', # version @@ -225,16 +191,12 @@ __all__ = ( 'VERSION', # warnings 'PydanticDeprecatedSince20', - 'PydanticDeprecatedSince26', - 'PydanticDeprecatedSince29', - 'PydanticDeprecatedSince210', - 'PydanticDeprecatedSince211', - 'PydanticDeprecatedSince212', 'PydanticDeprecationWarning', - 'PydanticExperimentalWarning', # annotated handlers 'GetCoreSchemaHandler', 'GetJsonSchemaHandler', + # generate schema from ._internal + 'GenerateSchema', # pydantic_core 'ValidationError', 'ValidationInfo', @@ -242,162 +204,138 @@ __all__ = ( 'ValidatorFunctionWrapHandler', 'FieldSerializationInfo', 'SerializerFunctionWrapHandler', - 'OnErrorOmit', ) # A mapping of {: (package, )} defining dynamic imports _dynamic_imports: 'dict[str, tuple[str, str]]' = { - 'dataclasses': (__spec__.parent, '__module__'), + 'dataclasses': (__package__, '__module__'), # functional validators - 'field_validator': (__spec__.parent, '.functional_validators'), - 'model_validator': (__spec__.parent, '.functional_validators'), - 'AfterValidator': (__spec__.parent, '.functional_validators'), - 'BeforeValidator': (__spec__.parent, '.functional_validators'), - 'PlainValidator': (__spec__.parent, '.functional_validators'), - 'WrapValidator': (__spec__.parent, '.functional_validators'), - 'SkipValidation': (__spec__.parent, '.functional_validators'), - 'InstanceOf': (__spec__.parent, '.functional_validators'), - 'ValidateAs': (__spec__.parent, '.functional_validators'), - 'ModelWrapValidatorHandler': (__spec__.parent, '.functional_validators'), + 'field_validator': (__package__, '.functional_validators'), + 'model_validator': (__package__, '.functional_validators'), + 'AfterValidator': (__package__, '.functional_validators'), + 'BeforeValidator': (__package__, '.functional_validators'), + 'PlainValidator': (__package__, '.functional_validators'), + 'WrapValidator': (__package__, '.functional_validators'), + 'SkipValidation': (__package__, '.functional_validators'), + 'InstanceOf': (__package__, '.functional_validators'), # JSON Schema - 'WithJsonSchema': (__spec__.parent, '.json_schema'), + 'WithJsonSchema': (__package__, '.json_schema'), # functional serializers - 'field_serializer': (__spec__.parent, '.functional_serializers'), - 'model_serializer': (__spec__.parent, '.functional_serializers'), - 'PlainSerializer': (__spec__.parent, '.functional_serializers'), - 'SerializeAsAny': (__spec__.parent, '.functional_serializers'), - 'WrapSerializer': (__spec__.parent, '.functional_serializers'), + 'field_serializer': (__package__, '.functional_serializers'), + 'model_serializer': (__package__, '.functional_serializers'), + 'PlainSerializer': (__package__, '.functional_serializers'), + 'SerializeAsAny': (__package__, '.functional_serializers'), + 'WrapSerializer': (__package__, '.functional_serializers'), # config - 'ConfigDict': (__spec__.parent, '.config'), - 'with_config': (__spec__.parent, '.config'), + 'ConfigDict': (__package__, '.config'), # validate call - 'validate_call': (__spec__.parent, '.validate_call_decorator'), + 'validate_call': (__package__, '.validate_call_decorator'), # errors - 'PydanticErrorCodes': (__spec__.parent, '.errors'), - 'PydanticUserError': (__spec__.parent, '.errors'), - 'PydanticSchemaGenerationError': (__spec__.parent, '.errors'), - 'PydanticImportError': (__spec__.parent, '.errors'), - 'PydanticUndefinedAnnotation': (__spec__.parent, '.errors'), - 'PydanticInvalidForJsonSchema': (__spec__.parent, '.errors'), - 'PydanticForbiddenQualifier': (__spec__.parent, '.errors'), + 'PydanticErrorCodes': (__package__, '.errors'), + 'PydanticUserError': (__package__, '.errors'), + 'PydanticSchemaGenerationError': (__package__, '.errors'), + 'PydanticImportError': (__package__, '.errors'), + 'PydanticUndefinedAnnotation': (__package__, '.errors'), + 'PydanticInvalidForJsonSchema': (__package__, '.errors'), # fields - 'Field': (__spec__.parent, '.fields'), - 'computed_field': (__spec__.parent, '.fields'), - 'PrivateAttr': (__spec__.parent, '.fields'), - # alias - 'AliasChoices': (__spec__.parent, '.aliases'), - 'AliasGenerator': (__spec__.parent, '.aliases'), - 'AliasPath': (__spec__.parent, '.aliases'), + 'AliasPath': (__package__, '.fields'), + 'AliasChoices': (__package__, '.fields'), + 'Field': (__package__, '.fields'), + 'computed_field': (__package__, '.fields'), + 'PrivateAttr': (__package__, '.fields'), # main - 'BaseModel': (__spec__.parent, '.main'), - 'create_model': (__spec__.parent, '.main'), + 'BaseModel': (__package__, '.main'), + 'create_model': (__package__, '.main'), # network - 'AnyUrl': (__spec__.parent, '.networks'), - 'AnyHttpUrl': (__spec__.parent, '.networks'), - 'FileUrl': (__spec__.parent, '.networks'), - 'HttpUrl': (__spec__.parent, '.networks'), - 'FtpUrl': (__spec__.parent, '.networks'), - 'WebsocketUrl': (__spec__.parent, '.networks'), - 'AnyWebsocketUrl': (__spec__.parent, '.networks'), - 'UrlConstraints': (__spec__.parent, '.networks'), - 'EmailStr': (__spec__.parent, '.networks'), - 'NameEmail': (__spec__.parent, '.networks'), - 'IPvAnyAddress': (__spec__.parent, '.networks'), - 'IPvAnyInterface': (__spec__.parent, '.networks'), - 'IPvAnyNetwork': (__spec__.parent, '.networks'), - 'PostgresDsn': (__spec__.parent, '.networks'), - 'CockroachDsn': (__spec__.parent, '.networks'), - 'AmqpDsn': (__spec__.parent, '.networks'), - 'RedisDsn': (__spec__.parent, '.networks'), - 'MongoDsn': (__spec__.parent, '.networks'), - 'KafkaDsn': (__spec__.parent, '.networks'), - 'NatsDsn': (__spec__.parent, '.networks'), - 'MySQLDsn': (__spec__.parent, '.networks'), - 'MariaDBDsn': (__spec__.parent, '.networks'), - 'ClickHouseDsn': (__spec__.parent, '.networks'), - 'SnowflakeDsn': (__spec__.parent, '.networks'), - 'validate_email': (__spec__.parent, '.networks'), + 'AnyUrl': (__package__, '.networks'), + 'AnyHttpUrl': (__package__, '.networks'), + 'FileUrl': (__package__, '.networks'), + 'HttpUrl': (__package__, '.networks'), + 'UrlConstraints': (__package__, '.networks'), + 'EmailStr': (__package__, '.networks'), + 'NameEmail': (__package__, '.networks'), + 'IPvAnyAddress': (__package__, '.networks'), + 'IPvAnyInterface': (__package__, '.networks'), + 'IPvAnyNetwork': (__package__, '.networks'), + 'PostgresDsn': (__package__, '.networks'), + 'CockroachDsn': (__package__, '.networks'), + 'AmqpDsn': (__package__, '.networks'), + 'RedisDsn': (__package__, '.networks'), + 'MongoDsn': (__package__, '.networks'), + 'KafkaDsn': (__package__, '.networks'), + 'MySQLDsn': (__package__, '.networks'), + 'MariaDBDsn': (__package__, '.networks'), + 'validate_email': (__package__, '.networks'), # root_model - 'RootModel': (__spec__.parent, '.root_model'), + 'RootModel': (__package__, '.root_model'), # types - 'Strict': (__spec__.parent, '.types'), - 'StrictStr': (__spec__.parent, '.types'), - 'conbytes': (__spec__.parent, '.types'), - 'conlist': (__spec__.parent, '.types'), - 'conset': (__spec__.parent, '.types'), - 'confrozenset': (__spec__.parent, '.types'), - 'constr': (__spec__.parent, '.types'), - 'StringConstraints': (__spec__.parent, '.types'), - 'ImportString': (__spec__.parent, '.types'), - 'conint': (__spec__.parent, '.types'), - 'PositiveInt': (__spec__.parent, '.types'), - 'NegativeInt': (__spec__.parent, '.types'), - 'NonNegativeInt': (__spec__.parent, '.types'), - 'NonPositiveInt': (__spec__.parent, '.types'), - 'confloat': (__spec__.parent, '.types'), - 'PositiveFloat': (__spec__.parent, '.types'), - 'NegativeFloat': (__spec__.parent, '.types'), - 'NonNegativeFloat': (__spec__.parent, '.types'), - 'NonPositiveFloat': (__spec__.parent, '.types'), - 'FiniteFloat': (__spec__.parent, '.types'), - 'condecimal': (__spec__.parent, '.types'), - 'condate': (__spec__.parent, '.types'), - 'UUID1': (__spec__.parent, '.types'), - 'UUID3': (__spec__.parent, '.types'), - 'UUID4': (__spec__.parent, '.types'), - 'UUID5': (__spec__.parent, '.types'), - 'UUID6': (__spec__.parent, '.types'), - 'UUID7': (__spec__.parent, '.types'), - 'UUID8': (__spec__.parent, '.types'), - 'FilePath': (__spec__.parent, '.types'), - 'DirectoryPath': (__spec__.parent, '.types'), - 'NewPath': (__spec__.parent, '.types'), - 'Json': (__spec__.parent, '.types'), - 'Secret': (__spec__.parent, '.types'), - 'SecretStr': (__spec__.parent, '.types'), - 'SecretBytes': (__spec__.parent, '.types'), - 'StrictBool': (__spec__.parent, '.types'), - 'StrictBytes': (__spec__.parent, '.types'), - 'StrictInt': (__spec__.parent, '.types'), - 'StrictFloat': (__spec__.parent, '.types'), - 'PaymentCardNumber': (__spec__.parent, '.types'), - 'ByteSize': (__spec__.parent, '.types'), - 'PastDate': (__spec__.parent, '.types'), - 'SocketPath': (__spec__.parent, '.types'), - 'FutureDate': (__spec__.parent, '.types'), - 'PastDatetime': (__spec__.parent, '.types'), - 'FutureDatetime': (__spec__.parent, '.types'), - 'AwareDatetime': (__spec__.parent, '.types'), - 'NaiveDatetime': (__spec__.parent, '.types'), - 'AllowInfNan': (__spec__.parent, '.types'), - 'EncoderProtocol': (__spec__.parent, '.types'), - 'EncodedBytes': (__spec__.parent, '.types'), - 'EncodedStr': (__spec__.parent, '.types'), - 'Base64Encoder': (__spec__.parent, '.types'), - 'Base64Bytes': (__spec__.parent, '.types'), - 'Base64Str': (__spec__.parent, '.types'), - 'Base64UrlBytes': (__spec__.parent, '.types'), - 'Base64UrlStr': (__spec__.parent, '.types'), - 'GetPydanticSchema': (__spec__.parent, '.types'), - 'Tag': (__spec__.parent, '.types'), - 'Discriminator': (__spec__.parent, '.types'), - 'JsonValue': (__spec__.parent, '.types'), - 'OnErrorOmit': (__spec__.parent, '.types'), - 'FailFast': (__spec__.parent, '.types'), + 'Strict': (__package__, '.types'), + 'StrictStr': (__package__, '.types'), + 'conbytes': (__package__, '.types'), + 'conlist': (__package__, '.types'), + 'conset': (__package__, '.types'), + 'confrozenset': (__package__, '.types'), + 'constr': (__package__, '.types'), + 'StringConstraints': (__package__, '.types'), + 'ImportString': (__package__, '.types'), + 'conint': (__package__, '.types'), + 'PositiveInt': (__package__, '.types'), + 'NegativeInt': (__package__, '.types'), + 'NonNegativeInt': (__package__, '.types'), + 'NonPositiveInt': (__package__, '.types'), + 'confloat': (__package__, '.types'), + 'PositiveFloat': (__package__, '.types'), + 'NegativeFloat': (__package__, '.types'), + 'NonNegativeFloat': (__package__, '.types'), + 'NonPositiveFloat': (__package__, '.types'), + 'FiniteFloat': (__package__, '.types'), + 'condecimal': (__package__, '.types'), + 'condate': (__package__, '.types'), + 'UUID1': (__package__, '.types'), + 'UUID3': (__package__, '.types'), + 'UUID4': (__package__, '.types'), + 'UUID5': (__package__, '.types'), + 'FilePath': (__package__, '.types'), + 'DirectoryPath': (__package__, '.types'), + 'NewPath': (__package__, '.types'), + 'Json': (__package__, '.types'), + 'SecretStr': (__package__, '.types'), + 'SecretBytes': (__package__, '.types'), + 'StrictBool': (__package__, '.types'), + 'StrictBytes': (__package__, '.types'), + 'StrictInt': (__package__, '.types'), + 'StrictFloat': (__package__, '.types'), + 'PaymentCardNumber': (__package__, '.types'), + 'ByteSize': (__package__, '.types'), + 'PastDate': (__package__, '.types'), + 'FutureDate': (__package__, '.types'), + 'PastDatetime': (__package__, '.types'), + 'FutureDatetime': (__package__, '.types'), + 'AwareDatetime': (__package__, '.types'), + 'NaiveDatetime': (__package__, '.types'), + 'AllowInfNan': (__package__, '.types'), + 'EncoderProtocol': (__package__, '.types'), + 'EncodedBytes': (__package__, '.types'), + 'EncodedStr': (__package__, '.types'), + 'Base64Encoder': (__package__, '.types'), + 'Base64Bytes': (__package__, '.types'), + 'Base64Str': (__package__, '.types'), + 'Base64UrlBytes': (__package__, '.types'), + 'Base64UrlStr': (__package__, '.types'), + 'GetPydanticSchema': (__package__, '.types'), + 'Tag': (__package__, '.types'), + 'Discriminator': (__package__, '.types'), + 'JsonValue': (__package__, '.types'), # type_adapter - 'TypeAdapter': (__spec__.parent, '.type_adapter'), + 'TypeAdapter': (__package__, '.type_adapter'), # warnings - 'PydanticDeprecatedSince20': (__spec__.parent, '.warnings'), - 'PydanticDeprecatedSince26': (__spec__.parent, '.warnings'), - 'PydanticDeprecatedSince29': (__spec__.parent, '.warnings'), - 'PydanticDeprecatedSince210': (__spec__.parent, '.warnings'), - 'PydanticDeprecatedSince211': (__spec__.parent, '.warnings'), - 'PydanticDeprecatedSince212': (__spec__.parent, '.warnings'), - 'PydanticDeprecationWarning': (__spec__.parent, '.warnings'), - 'PydanticExperimentalWarning': (__spec__.parent, '.warnings'), + 'PydanticDeprecatedSince20': (__package__, '.warnings'), + 'PydanticDeprecationWarning': (__package__, '.warnings'), # annotated handlers - 'GetCoreSchemaHandler': (__spec__.parent, '.annotated_handlers'), - 'GetJsonSchemaHandler': (__spec__.parent, '.annotated_handlers'), + 'GetCoreSchemaHandler': (__package__, '.annotated_handlers'), + 'GetJsonSchemaHandler': (__package__, '.annotated_handlers'), + # generate schema from ._internal + 'GenerateSchema': (__package__, '._internal._generate_schema'), # pydantic_core stuff 'ValidationError': ('pydantic_core', '.'), 'ValidationInfo': ('pydantic_core', '.core_schema'), @@ -406,51 +344,34 @@ _dynamic_imports: 'dict[str, tuple[str, str]]' = { 'FieldSerializationInfo': ('pydantic_core', '.core_schema'), 'SerializerFunctionWrapHandler': ('pydantic_core', '.core_schema'), # deprecated, mostly not included in __all__ - 'root_validator': (__spec__.parent, '.deprecated.class_validators'), - 'validator': (__spec__.parent, '.deprecated.class_validators'), - 'BaseConfig': (__spec__.parent, '.deprecated.config'), - 'Extra': (__spec__.parent, '.deprecated.config'), - 'parse_obj_as': (__spec__.parent, '.deprecated.tools'), - 'schema_of': (__spec__.parent, '.deprecated.tools'), - 'schema_json_of': (__spec__.parent, '.deprecated.tools'), - # deprecated dynamic imports + 'root_validator': (__package__, '.deprecated.class_validators'), + 'validator': (__package__, '.deprecated.class_validators'), + 'BaseConfig': (__package__, '.deprecated.config'), + 'Extra': (__package__, '.deprecated.config'), + 'parse_obj_as': (__package__, '.deprecated.tools'), + 'schema_of': (__package__, '.deprecated.tools'), + 'schema_json_of': (__package__, '.deprecated.tools'), 'FieldValidationInfo': ('pydantic_core', '.core_schema'), - 'GenerateSchema': (__spec__.parent, '._internal._generate_schema'), } -_deprecated_dynamic_imports = {'FieldValidationInfo', 'GenerateSchema'} _getattr_migration = getattr_migration(__name__) def __getattr__(attr_name: str) -> object: - if attr_name in _deprecated_dynamic_imports: - from pydantic.warnings import PydanticDeprecatedSince20 - - warn( - f'Importing {attr_name} from `pydantic` is deprecated. This feature is either no longer supported, or is not public.', - PydanticDeprecatedSince20, - stacklevel=2, - ) - dynamic_attr = _dynamic_imports.get(attr_name) if dynamic_attr is None: return _getattr_migration(attr_name) package, module_name = dynamic_attr + from importlib import import_module + if module_name == '__module__': - result = import_module(f'.{attr_name}', package=package) - globals()[attr_name] = result - return result + return import_module(f'.{attr_name}', package=package) else: module = import_module(module_name, package=package) - result = getattr(module, attr_name) - g = globals() - for k, (_, v_module_name) in _dynamic_imports.items(): - if v_module_name == module_name and k not in _deprecated_dynamic_imports: - g[k] = getattr(module, k) - return result + return getattr(module, attr_name) -def __dir__() -> list[str]: +def __dir__() -> 'list[str]': return list(__all__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc index f59e3682..0d7bb614 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc index e4501286..ef7a3fa3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc index 531a738e..289d2c5c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/aliases.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/aliases.cpython-312.pyc deleted file mode 100644 index ed2f6f10..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/aliases.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc index 3aabc0c8..b08445f8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc index b0c53c81..4d6ad5c2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/color.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/color.cpython-312.pyc index 57cd9af4..b447c3dc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/color.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/color.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/config.cpython-312.pyc index c9ec8751..e8720b1a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc index 4d4e4e72..d607b3f3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc index 93c7436b..5eeb30b6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc index 3f492584..5a441f4a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc index d2deb79c..2c016d2d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc index 6a1231f1..baa2834c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/errors.cpython-312.pyc index 233e55fe..cd1c71d0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/fields.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/fields.cpython-312.pyc index 6d7073bf..9fc64881 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/fields.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/fields.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc index 7ee678ac..7aad34c2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc index 2b5d710a..4c377cea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/generics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/generics.cpython-312.pyc index 6376508c..e4f759a0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/generics.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/generics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json.cpython-312.pyc index 281b2fd7..779a9125 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc index 8e6f1b22..8588a79a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/main.cpython-312.pyc index fbf1cce2..fb559ac2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc index f1018bda..1d302925 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/networks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/networks.cpython-312.pyc index 3d276b57..cdab3004 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/networks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/networks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/parse.cpython-312.pyc index 23a6c33a..a0ce7843 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc index 0e2b7e13..d735a0d5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/schema.cpython-312.pyc index 87ee2f05..350505bc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/tools.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/tools.cpython-312.pyc index 7abf614a..63c52010 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/tools.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/tools.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc index 1c90ca53..0b348b0a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/types.cpython-312.pyc index 55952dd9..0ee887c0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/typing.cpython-312.pyc index 7dc58308..c32aa46a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/utils.cpython-312.pyc index 07f46742..067d8a03 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc index f32b9c7b..6d8ae376 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validators.cpython-312.pyc index a2c3d52b..3cb44f61 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/version.cpython-312.pyc index cbd912ea..79d7747d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc index dfa213cd..26a327bc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc index 30045315..0cb9b434 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc index 15eae418..46fb7902 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc index 04a4196a..9937f4ba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc index f4fc37fb..5f208af7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc index f0195d18..0795046b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc index f260efbb..62d04e0c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc index 41f18bf6..dff87daf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc index abf6779d..3ae7ea00 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc deleted file mode 100644 index 6dbeb6b2..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc index eb64b126..9d98a33f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc index 1f0d8e8f..745a6e26 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc index 6eab4f99..5c87a95a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc index c9ab2871..c7b73ea5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_git.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_git.cpython-312.pyc deleted file mode 100644 index d17fa102..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_git.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc deleted file mode 100644 index 25ab6fd8..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc index 710cf96b..e9001b19 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc index d5555769..96cee7ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc index d0720359..6e12c149 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc index 4020a0b2..429c3079 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc deleted file mode 100644 index b6c138af..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc index 89a47c85..21261879 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc deleted file mode 100644 index 33225beb..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc index 16f921cf..0b4c8618 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_serializers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_serializers.cpython-312.pyc deleted file mode 100644 index d16067ba..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_serializers.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_signature.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_signature.cpython-312.pyc deleted file mode 100644 index 79d7e90a..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_signature.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_std_types_schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_std_types_schema.cpython-312.pyc new file mode 100644 index 00000000..c5fd197f Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_std_types_schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc index c8342192..be85f59f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc index 1544965c..3591ad45 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc index 501e4c66..cb736e12 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc index 0a62e7d7..598625f2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_config.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_config.py index 43c85685..95b5f07e 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_config.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_config.py @@ -2,26 +2,30 @@ from __future__ import annotations as _annotations import warnings from contextlib import contextmanager -from re import Pattern from typing import ( TYPE_CHECKING, Any, Callable, - Literal, cast, ) from pydantic_core import core_schema -from typing_extensions import Self +from typing_extensions import ( + Literal, + Self, +) -from ..aliases import AliasGenerator from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable from ..errors import PydanticUserError -from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210 +from ..warnings import PydanticDeprecatedSince20 + +if not TYPE_CHECKING: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 if TYPE_CHECKING: from .._internal._schema_generation_shared import GenerateSchema - from ..fields import ComputedFieldInfo, FieldInfo DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.' @@ -51,9 +55,7 @@ class ConfigWrapper: # whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names # to construct error `loc`s, default `True` loc_by_alias: bool - alias_generator: Callable[[str], str] | AliasGenerator | None - model_title_generator: Callable[[type], str] | None - field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None + alias_generator: Callable[[str], str] | None ignored_types: tuple[type, ...] allow_inf_nan: bool json_schema_extra: JsonDict | JsonSchemaExtraCallable | None @@ -64,15 +66,11 @@ class ConfigWrapper: # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' revalidate_instances: Literal['always', 'never', 'subclass-instances'] ser_json_timedelta: Literal['iso8601', 'float'] - ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] - val_temporal_unit: Literal['seconds', 'milliseconds', 'infer'] - ser_json_bytes: Literal['utf8', 'base64', 'hex'] - val_json_bytes: Literal['utf8', 'base64', 'hex'] - ser_json_inf_nan: Literal['null', 'constants', 'strings'] + ser_json_bytes: Literal['utf8', 'base64'] # whether to validate default values during validation, default False validate_default: bool validate_return: bool - protected_namespaces: tuple[str | Pattern[str], ...] + protected_namespaces: tuple[str, ...] hide_input_in_errors: bool defer_build: bool plugin_settings: dict[str, object] | None @@ -82,12 +80,6 @@ class ConfigWrapper: coerce_numbers_to_str: bool regex_engine: Literal['rust-regex', 'python-re'] validation_error_cause: bool - use_attribute_docstrings: bool - cache_strings: bool | Literal['all', 'keys', 'none'] - validate_by_alias: bool - validate_by_name: bool - serialize_by_alias: bool - url_preserve_empty_path: bool def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True): if check: @@ -96,13 +88,7 @@ class ConfigWrapper: self.config_dict = cast(ConfigDict, config) @classmethod - def for_model( - cls, - bases: tuple[type[Any], ...], - namespace: dict[str, Any], - raw_annotations: dict[str, Any], - kwargs: dict[str, Any], - ) -> Self: + def for_model(cls, bases: tuple[type[Any], ...], namespace: dict[str, Any], kwargs: dict[str, Any]) -> Self: """Build a new `ConfigWrapper` instance for a `BaseModel`. The config wrapper built based on (in descending order of priority): @@ -113,7 +99,6 @@ class ConfigWrapper: Args: bases: A tuple of base classes. namespace: The namespace of the class being created. - raw_annotations: The (non-evaluated) annotations of the model. kwargs: The kwargs passed to the class being created. Returns: @@ -128,12 +113,6 @@ class ConfigWrapper: config_class_from_namespace = namespace.get('Config') config_dict_from_namespace = namespace.get('model_config') - if raw_annotations.get('model_config') and config_dict_from_namespace is None: - raise PydanticUserError( - '`model_config` cannot be used as a model field name. Use `model_config` for model configuration.', - code='model-config-invalid-field-name', - ) - if config_class_from_namespace and config_dict_from_namespace: raise PydanticUserError('"Config" and "model_config" cannot be used together', code='config-both') @@ -159,80 +138,48 @@ class ConfigWrapper: except KeyError: raise AttributeError(f'Config has no attribute {name!r}') from None - def core_config(self, title: str | None) -> core_schema.CoreConfig: - """Create a pydantic-core config. + def core_config(self, obj: Any) -> core_schema.CoreConfig: + """Create a pydantic-core config, `obj` is just used to populate `title` if not set in config. + + Pass `obj=None` if you do not want to attempt to infer the `title`. We don't use getattr here since we don't want to populate with defaults. Args: - title: The title to use if not set in config. + obj: An object used to populate `title` if not set in config. Returns: A `CoreConfig` object created from config. """ - config = self.config_dict - if config.get('schema_generator') is not None: - warnings.warn( - 'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.', - PydanticDeprecatedSince210, - stacklevel=2, + def dict_not_none(**kwargs: Any) -> Any: + return {k: v for k, v in kwargs.items() if v is not None} + + core_config = core_schema.CoreConfig( + **dict_not_none( + title=self.config_dict.get('title') or (obj and obj.__name__), + extra_fields_behavior=self.config_dict.get('extra'), + allow_inf_nan=self.config_dict.get('allow_inf_nan'), + populate_by_name=self.config_dict.get('populate_by_name'), + str_strip_whitespace=self.config_dict.get('str_strip_whitespace'), + str_to_lower=self.config_dict.get('str_to_lower'), + str_to_upper=self.config_dict.get('str_to_upper'), + strict=self.config_dict.get('strict'), + ser_json_timedelta=self.config_dict.get('ser_json_timedelta'), + ser_json_bytes=self.config_dict.get('ser_json_bytes'), + from_attributes=self.config_dict.get('from_attributes'), + loc_by_alias=self.config_dict.get('loc_by_alias'), + revalidate_instances=self.config_dict.get('revalidate_instances'), + validate_default=self.config_dict.get('validate_default'), + str_max_length=self.config_dict.get('str_max_length'), + str_min_length=self.config_dict.get('str_min_length'), + hide_input_in_errors=self.config_dict.get('hide_input_in_errors'), + coerce_numbers_to_str=self.config_dict.get('coerce_numbers_to_str'), + regex_engine=self.config_dict.get('regex_engine'), + validation_error_cause=self.config_dict.get('validation_error_cause'), ) - - if (populate_by_name := config.get('populate_by_name')) is not None: - # We include this patch for backwards compatibility purposes, but this config setting will be deprecated in v3.0, and likely removed in v4.0. - # Thus, the above warning and this patch can be removed then as well. - if config.get('validate_by_name') is None: - config['validate_by_alias'] = True - config['validate_by_name'] = populate_by_name - - # We dynamically patch validate_by_name to be True if validate_by_alias is set to False - # and validate_by_name is not explicitly set. - if config.get('validate_by_alias') is False and config.get('validate_by_name') is None: - config['validate_by_name'] = True - - if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)): - raise PydanticUserError( - 'At least one of `validate_by_alias` or `validate_by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) - - return core_schema.CoreConfig( - **{ # pyright: ignore[reportArgumentType] - k: v - for k, v in ( - ('title', config.get('title') or title or None), - ('extra_fields_behavior', config.get('extra')), - ('allow_inf_nan', config.get('allow_inf_nan')), - ('str_strip_whitespace', config.get('str_strip_whitespace')), - ('str_to_lower', config.get('str_to_lower')), - ('str_to_upper', config.get('str_to_upper')), - ('strict', config.get('strict')), - ('ser_json_timedelta', config.get('ser_json_timedelta')), - ('ser_json_temporal', config.get('ser_json_temporal')), - ('val_temporal_unit', config.get('val_temporal_unit')), - ('ser_json_bytes', config.get('ser_json_bytes')), - ('val_json_bytes', config.get('val_json_bytes')), - ('ser_json_inf_nan', config.get('ser_json_inf_nan')), - ('from_attributes', config.get('from_attributes')), - ('loc_by_alias', config.get('loc_by_alias')), - ('revalidate_instances', config.get('revalidate_instances')), - ('validate_default', config.get('validate_default')), - ('str_max_length', config.get('str_max_length')), - ('str_min_length', config.get('str_min_length')), - ('hide_input_in_errors', config.get('hide_input_in_errors')), - ('coerce_numbers_to_str', config.get('coerce_numbers_to_str')), - ('regex_engine', config.get('regex_engine')), - ('validation_error_cause', config.get('validation_error_cause')), - ('cache_strings', config.get('cache_strings')), - ('validate_by_alias', config.get('validate_by_alias')), - ('validate_by_name', config.get('validate_by_name')), - ('serialize_by_alias', config.get('serialize_by_alias')), - ('url_preserve_empty_path', config.get('url_preserve_empty_path')), - ) - if v is not None - } ) + return core_config def __repr__(self): c = ', '.join(f'{k}={v!r}' for k, v in self.config_dict.items()) @@ -282,38 +229,26 @@ config_defaults = ConfigDict( from_attributes=False, loc_by_alias=True, alias_generator=None, - model_title_generator=None, - field_title_generator=None, ignored_types=(), allow_inf_nan=True, json_schema_extra=None, strict=False, revalidate_instances='never', ser_json_timedelta='iso8601', - ser_json_temporal='iso8601', - val_temporal_unit='infer', ser_json_bytes='utf8', - val_json_bytes='utf8', - ser_json_inf_nan='null', validate_default=False, validate_return=False, - protected_namespaces=('model_validate', 'model_dump'), + protected_namespaces=('model_',), hide_input_in_errors=False, json_encoders=None, defer_build=False, - schema_generator=None, plugin_settings=None, + schema_generator=None, json_schema_serialization_defaults_required=False, json_schema_mode_override=None, coerce_numbers_to_str=False, regex_engine='rust-regex', validation_error_cause=False, - use_attribute_docstrings=False, - cache_strings=True, - validate_by_alias=True, - validate_by_name=False, - serialize_by_alias=False, - url_preserve_empty_path=False, ) @@ -330,7 +265,7 @@ def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> Co return ConfigDict() if not isinstance(config, dict): - warnings.warn(DEPRECATION_MESSAGE, PydanticDeprecatedSince20, stacklevel=4) + warnings.warn(DEPRECATION_MESSAGE, DeprecationWarning) config = {k: getattr(config, k) for k in dir(config) if not k.startswith('__')} config_dict = cast(ConfigDict, config) @@ -354,7 +289,7 @@ V2_REMOVED_KEYS = { 'post_init_call', } V2_RENAMED_KEYS = { - 'allow_population_by_field_name': 'validate_by_name', + 'allow_population_by_field_name': 'populate_by_name', 'anystr_lower': 'str_to_lower', 'anystr_strip_whitespace': 'str_strip_whitespace', 'anystr_upper': 'str_to_upper', diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_metadata.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_metadata.py index 9f2510c0..296d49f5 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_metadata.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_metadata.py @@ -1,97 +1,92 @@ from __future__ import annotations as _annotations -from typing import TYPE_CHECKING, Any, TypedDict, cast -from warnings import warn +import typing +from typing import Any -if TYPE_CHECKING: - from ..config import JsonDict, JsonSchemaExtraCallable +import typing_extensions + +if typing.TYPE_CHECKING: + from ._schema_generation_shared import ( + CoreSchemaOrField as CoreSchemaOrField, + ) from ._schema_generation_shared import ( GetJsonSchemaFunction, ) -class CoreMetadata(TypedDict, total=False): +class CoreMetadata(typing_extensions.TypedDict, total=False): """A `TypedDict` for holding the metadata dict of the schema. Attributes: - pydantic_js_functions: List of JSON schema functions that resolve refs during application. - pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application. + pydantic_js_functions: List of JSON schema functions. pydantic_js_prefer_positional_arguments: Whether JSON schema generator will prefer positional over keyword arguments for an 'arguments' schema. - custom validation function. Only applies to before, plain, and wrap validators. - pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type. - pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable. - pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union. - pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union - when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time - of the annotation application. - - TODO: Perhaps we should move this structure to pydantic-core. At the moment, though, - it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API. - - TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during - the core schema generation process. It's inevitable that we need to store some json schema related information - on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related - issues is quite difficult when JSON schema information is disguised via dynamically defined functions. """ pydantic_js_functions: list[GetJsonSchemaFunction] pydantic_js_annotation_functions: list[GetJsonSchemaFunction] - pydantic_js_prefer_positional_arguments: bool - pydantic_js_updates: JsonDict - pydantic_js_extra: JsonDict | JsonSchemaExtraCallable - pydantic_internal_union_tag_key: str - pydantic_internal_union_discriminator: str + + # If `pydantic_js_prefer_positional_arguments` is True, the JSON schema generator will + # prefer positional over keyword arguments for an 'arguments' schema. + pydantic_js_prefer_positional_arguments: bool | None + + pydantic_typed_dict_cls: type[Any] | None # TODO: Consider moving this into the pydantic-core TypedDictSchema -def update_core_metadata( - core_metadata: Any, - /, - *, - pydantic_js_functions: list[GetJsonSchemaFunction] | None = None, - pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None, - pydantic_js_updates: JsonDict | None = None, - pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None, -) -> None: - from ..json_schema import PydanticJsonSchemaWarning +class CoreMetadataHandler: + """Because the metadata field in pydantic_core is of type `Any`, we can't assume much about its contents. - """Update CoreMetadata instance in place. When we make modifications in this function, they - take effect on the `core_metadata` reference passed in as the first (and only) positional argument. - - First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility. - We do this here, instead of before / after each call to this function so that this typing hack - can be easily removed if/when we move `CoreMetadata` to `pydantic-core`. - - For parameter descriptions, see `CoreMetadata` above. + This class is used to interact with the metadata field on a CoreSchema object in a consistent + way throughout pydantic. """ - core_metadata = cast(CoreMetadata, core_metadata) - if pydantic_js_functions: - core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions) + __slots__ = ('_schema',) - if pydantic_js_annotation_functions: - core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions) + def __init__(self, schema: CoreSchemaOrField): + self._schema = schema - if pydantic_js_updates: - if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None: - core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates} - else: - core_metadata['pydantic_js_updates'] = pydantic_js_updates + metadata = schema.get('metadata') + if metadata is None: + schema['metadata'] = CoreMetadata() + elif not isinstance(metadata, dict): + raise TypeError(f'CoreSchema metadata should be a dict; got {metadata!r}.') - if pydantic_js_extra is not None: - existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra') - if existing_pydantic_js_extra is None: - core_metadata['pydantic_js_extra'] = pydantic_js_extra - if isinstance(existing_pydantic_js_extra, dict): - if isinstance(pydantic_js_extra, dict): - core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra} - if callable(pydantic_js_extra): - warn( - 'Composing `dict` and `callable` type `json_schema_extra` is not supported.' - 'The `callable` type is being ignored.' - "If you'd like support for this behavior, please open an issue on pydantic.", - PydanticJsonSchemaWarning, - ) - if callable(existing_pydantic_js_extra): - # if ever there's a case of a callable, we'll just keep the last json schema extra spec - core_metadata['pydantic_js_extra'] = pydantic_js_extra + @property + def metadata(self) -> CoreMetadata: + """Retrieves the metadata dict from the schema, initializing it to a dict if it is None + and raises an error if it is not a dict. + """ + metadata = self._schema.get('metadata') + if metadata is None: + self._schema['metadata'] = metadata = CoreMetadata() + if not isinstance(metadata, dict): + raise TypeError(f'CoreSchema metadata should be a dict; got {metadata!r}.') + return metadata + + +def build_metadata_dict( + *, # force keyword arguments to make it easier to modify this signature in a backwards-compatible way + js_functions: list[GetJsonSchemaFunction] | None = None, + js_annotation_functions: list[GetJsonSchemaFunction] | None = None, + js_prefer_positional_arguments: bool | None = None, + typed_dict_cls: type[Any] | None = None, + initial_metadata: Any | None = None, +) -> Any: + """Builds a dict to use as the metadata field of a CoreSchema object in a manner that is consistent + with the CoreMetadataHandler class. + """ + if initial_metadata is not None and not isinstance(initial_metadata, dict): + raise TypeError(f'CoreSchema metadata should be a dict; got {initial_metadata!r}.') + + metadata = CoreMetadata( + pydantic_js_functions=js_functions or [], + pydantic_js_annotation_functions=js_annotation_functions or [], + pydantic_js_prefer_positional_arguments=js_prefer_positional_arguments, + pydantic_typed_dict_cls=typed_dict_cls, + ) + metadata = {k: v for k, v in metadata.items() if v is not None} + + if initial_metadata is not None: + metadata = {**initial_metadata, **metadata} + + return metadata diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_utils.py index caa51e8c..551dafec 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_utils.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_core_utils.py @@ -1,19 +1,23 @@ from __future__ import annotations -import inspect -from collections.abc import Mapping, Sequence -from typing import TYPE_CHECKING, Any, Union +import os +from collections import defaultdict +from typing import ( + Any, + Callable, + Hashable, + TypeVar, + Union, + cast, +) from pydantic_core import CoreSchema, core_schema -from typing_extensions import TypeGuard, get_args, get_origin -from typing_inspection import typing_objects +from pydantic_core import validate_core_schema as _validate_core_schema +from typing_extensions import TypeAliasType, TypeGuard, get_args, get_origin from . import _repr from ._typing_extra import is_generic_alias -if TYPE_CHECKING: - from rich.console import Console - AnyFunctionSchema = Union[ core_schema.AfterValidatorFunctionSchema, core_schema.BeforeValidatorFunctionSchema, @@ -35,7 +39,23 @@ CoreSchemaOrField = Union[core_schema.CoreSchema, CoreSchemaField] _CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'} _FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'} -_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'set', 'frozenset'} +_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'tuple-variable', 'set', 'frozenset'} + +_DEFINITIONS_CACHE_METADATA_KEY = 'pydantic.definitions_cache' + +NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY = 'pydantic.internal.needs_apply_discriminated_union' +"""Used to mark a schema that has a discriminated union that needs to be checked for validity at the end of +schema building because one of it's members refers to a definition that was not yet defined when the union +was first encountered. +""" +TAGGED_UNION_TAG_KEY = 'pydantic.internal.tagged_union_tag' +""" +Used in a `Tag` schema to specify the tag used for a discriminated union. +""" +HAS_INVALID_SCHEMAS_METADATA_KEY = 'pydantic.internal.invalid' +"""Used to mark a schema that is invalid because it refers to a definition that was not yet defined when the +schema was first encountered. +""" def is_core_schema( @@ -58,11 +78,13 @@ def is_function_with_inner_schema( def is_list_like_schema_with_items_schema( schema: CoreSchema, -) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]: +) -> TypeGuard[ + core_schema.ListSchema | core_schema.TupleVariableSchema | core_schema.SetSchema | core_schema.FrozenSetSchema +]: return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES -def get_type_ref(type_: Any, args_override: tuple[type[Any], ...] | None = None) -> str: +def get_type_ref(type_: type[Any], args_override: tuple[type[Any], ...] | None = None) -> str: """Produces the ref to be used for this type by pydantic_core's core schemas. This `args_override` argument was added for the purpose of creating valid recursive references @@ -77,7 +99,7 @@ def get_type_ref(type_: Any, args_override: tuple[type[Any], ...] | None = None) args = generic_metadata['args'] or args module_name = getattr(origin, '__module__', '') - if typing_objects.is_typealiastype(origin): + if isinstance(origin, TypeAliasType): type_ref = f'{module_name}.{origin.__name__}:{id(origin)}' else: try: @@ -107,68 +129,457 @@ def get_ref(s: core_schema.CoreSchema) -> None | str: return s.get('ref', None) -def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover - """A utility function to remove irrelevant information from a core schema.""" - if isinstance(obj, Mapping): - new_dct = {} - for k, v in obj.items(): - if k == 'metadata' and strip_metadata: - new_metadata = {} +def collect_definitions(schema: core_schema.CoreSchema) -> dict[str, core_schema.CoreSchema]: + defs: dict[str, CoreSchema] = {} - for meta_k, meta_v in v.items(): - if meta_k in ('pydantic_js_functions', 'pydantic_js_annotation_functions'): - new_metadata['js_metadata'] = '' - else: - new_metadata[meta_k] = _clean_schema_for_pretty_print(meta_v, strip_metadata=strip_metadata) + def _record_valid_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreSchema: + ref = get_ref(s) + if ref: + defs[ref] = s + return recurse(s, _record_valid_refs) - if list(new_metadata.keys()) == ['js_metadata']: - new_metadata = {''} + walk_core_schema(schema, _record_valid_refs) - new_dct[k] = new_metadata - # Remove some defaults: - elif k in ('custom_init', 'root_model') and not v: - continue + return defs + + +def define_expected_missing_refs( + schema: core_schema.CoreSchema, allowed_missing_refs: set[str] +) -> core_schema.CoreSchema | None: + if not allowed_missing_refs: + # in this case, there are no missing refs to potentially substitute, so there's no need to walk the schema + # this is a common case (will be hit for all non-generic models), so it's worth optimizing for + return None + + refs = collect_definitions(schema).keys() + + expected_missing_refs = allowed_missing_refs.difference(refs) + if expected_missing_refs: + definitions: list[core_schema.CoreSchema] = [ + # TODO: Replace this with a (new) CoreSchema that, if present at any level, makes validation fail + # Issue: https://github.com/pydantic/pydantic-core/issues/619 + core_schema.none_schema(ref=ref, metadata={HAS_INVALID_SCHEMAS_METADATA_KEY: True}) + for ref in expected_missing_refs + ] + return core_schema.definitions_schema(schema, definitions) + return None + + +def collect_invalid_schemas(schema: core_schema.CoreSchema) -> bool: + invalid = False + + def _is_schema_valid(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreSchema: + nonlocal invalid + if 'metadata' in s: + metadata = s['metadata'] + if HAS_INVALID_SCHEMAS_METADATA_KEY in metadata: + invalid = metadata[HAS_INVALID_SCHEMAS_METADATA_KEY] + return s + return recurse(s, _is_schema_valid) + + walk_core_schema(schema, _is_schema_valid) + return invalid + + +T = TypeVar('T') + + +Recurse = Callable[[core_schema.CoreSchema, 'Walk'], core_schema.CoreSchema] +Walk = Callable[[core_schema.CoreSchema, Recurse], core_schema.CoreSchema] + +# TODO: Should we move _WalkCoreSchema into pydantic_core proper? +# Issue: https://github.com/pydantic/pydantic-core/issues/615 + + +class _WalkCoreSchema: + def __init__(self): + self._schema_type_to_method = self._build_schema_type_to_method() + + def _build_schema_type_to_method(self) -> dict[core_schema.CoreSchemaType, Recurse]: + mapping: dict[core_schema.CoreSchemaType, Recurse] = {} + key: core_schema.CoreSchemaType + for key in get_args(core_schema.CoreSchemaType): + method_name = f"handle_{key.replace('-', '_')}_schema" + mapping[key] = getattr(self, method_name, self._handle_other_schemas) + return mapping + + def walk(self, schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: + return f(schema, self._walk) + + def _walk(self, schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: + schema = self._schema_type_to_method[schema['type']](schema.copy(), f) + ser_schema: core_schema.SerSchema | None = schema.get('serialization') # type: ignore + if ser_schema: + schema['serialization'] = self._handle_ser_schemas(ser_schema, f) + return schema + + def _handle_other_schemas(self, schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: + sub_schema = schema.get('schema', None) + if sub_schema is not None: + schema['schema'] = self.walk(sub_schema, f) # type: ignore + return schema + + def _handle_ser_schemas(self, ser_schema: core_schema.SerSchema, f: Walk) -> core_schema.SerSchema: + schema: core_schema.CoreSchema | None = ser_schema.get('schema', None) + if schema is not None: + ser_schema['schema'] = self.walk(schema, f) # type: ignore + return_schema: core_schema.CoreSchema | None = ser_schema.get('return_schema', None) + if return_schema is not None: + ser_schema['return_schema'] = self.walk(return_schema, f) # type: ignore + return ser_schema + + def handle_definitions_schema(self, schema: core_schema.DefinitionsSchema, f: Walk) -> core_schema.CoreSchema: + new_definitions: list[core_schema.CoreSchema] = [] + for definition in schema['definitions']: + updated_definition = self.walk(definition, f) + if 'ref' in updated_definition: + # If the updated definition schema doesn't have a 'ref', it shouldn't go in the definitions + # This is most likely to happen due to replacing something with a definition reference, in + # which case it should certainly not go in the definitions list + new_definitions.append(updated_definition) + new_inner_schema = self.walk(schema['schema'], f) + + if not new_definitions and len(schema) == 3: + # This means we'd be returning a "trivial" definitions schema that just wrapped the inner schema + return new_inner_schema + + new_schema = schema.copy() + new_schema['schema'] = new_inner_schema + new_schema['definitions'] = new_definitions + return new_schema + + def handle_list_schema(self, schema: core_schema.ListSchema, f: Walk) -> core_schema.CoreSchema: + items_schema = schema.get('items_schema') + if items_schema is not None: + schema['items_schema'] = self.walk(items_schema, f) + return schema + + def handle_set_schema(self, schema: core_schema.SetSchema, f: Walk) -> core_schema.CoreSchema: + items_schema = schema.get('items_schema') + if items_schema is not None: + schema['items_schema'] = self.walk(items_schema, f) + return schema + + def handle_frozenset_schema(self, schema: core_schema.FrozenSetSchema, f: Walk) -> core_schema.CoreSchema: + items_schema = schema.get('items_schema') + if items_schema is not None: + schema['items_schema'] = self.walk(items_schema, f) + return schema + + def handle_generator_schema(self, schema: core_schema.GeneratorSchema, f: Walk) -> core_schema.CoreSchema: + items_schema = schema.get('items_schema') + if items_schema is not None: + schema['items_schema'] = self.walk(items_schema, f) + return schema + + def handle_tuple_variable_schema( + self, schema: core_schema.TupleVariableSchema | core_schema.TuplePositionalSchema, f: Walk + ) -> core_schema.CoreSchema: + schema = cast(core_schema.TupleVariableSchema, schema) + items_schema = schema.get('items_schema') + if items_schema is not None: + schema['items_schema'] = self.walk(items_schema, f) + return schema + + def handle_tuple_positional_schema( + self, schema: core_schema.TupleVariableSchema | core_schema.TuplePositionalSchema, f: Walk + ) -> core_schema.CoreSchema: + schema = cast(core_schema.TuplePositionalSchema, schema) + schema['items_schema'] = [self.walk(v, f) for v in schema['items_schema']] + extras_schema = schema.get('extras_schema') + if extras_schema is not None: + schema['extras_schema'] = self.walk(extras_schema, f) + return schema + + def handle_dict_schema(self, schema: core_schema.DictSchema, f: Walk) -> core_schema.CoreSchema: + keys_schema = schema.get('keys_schema') + if keys_schema is not None: + schema['keys_schema'] = self.walk(keys_schema, f) + values_schema = schema.get('values_schema') + if values_schema: + schema['values_schema'] = self.walk(values_schema, f) + return schema + + def handle_function_schema(self, schema: AnyFunctionSchema, f: Walk) -> core_schema.CoreSchema: + if not is_function_with_inner_schema(schema): + return schema + schema['schema'] = self.walk(schema['schema'], f) + return schema + + def handle_union_schema(self, schema: core_schema.UnionSchema, f: Walk) -> core_schema.CoreSchema: + new_choices: list[CoreSchema | tuple[CoreSchema, str]] = [] + for v in schema['choices']: + if isinstance(v, tuple): + new_choices.append((self.walk(v[0], f), v[1])) else: - new_dct[k] = _clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) + new_choices.append(self.walk(v, f)) + schema['choices'] = new_choices + return schema - return new_dct - elif isinstance(obj, Sequence) and not isinstance(obj, str): - return [_clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) for v in obj] - else: - return obj + def handle_tagged_union_schema(self, schema: core_schema.TaggedUnionSchema, f: Walk) -> core_schema.CoreSchema: + new_choices: dict[Hashable, core_schema.CoreSchema] = {} + for k, v in schema['choices'].items(): + new_choices[k] = v if isinstance(v, (str, int)) else self.walk(v, f) + schema['choices'] = new_choices + return schema + + def handle_chain_schema(self, schema: core_schema.ChainSchema, f: Walk) -> core_schema.CoreSchema: + schema['steps'] = [self.walk(v, f) for v in schema['steps']] + return schema + + def handle_lax_or_strict_schema(self, schema: core_schema.LaxOrStrictSchema, f: Walk) -> core_schema.CoreSchema: + schema['lax_schema'] = self.walk(schema['lax_schema'], f) + schema['strict_schema'] = self.walk(schema['strict_schema'], f) + return schema + + def handle_json_or_python_schema(self, schema: core_schema.JsonOrPythonSchema, f: Walk) -> core_schema.CoreSchema: + schema['json_schema'] = self.walk(schema['json_schema'], f) + schema['python_schema'] = self.walk(schema['python_schema'], f) + return schema + + def handle_model_fields_schema(self, schema: core_schema.ModelFieldsSchema, f: Walk) -> core_schema.CoreSchema: + extras_schema = schema.get('extras_schema') + if extras_schema is not None: + schema['extras_schema'] = self.walk(extras_schema, f) + replaced_fields: dict[str, core_schema.ModelField] = {} + replaced_computed_fields: list[core_schema.ComputedField] = [] + for computed_field in schema.get('computed_fields', ()): + replaced_field = computed_field.copy() + replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) + replaced_computed_fields.append(replaced_field) + if replaced_computed_fields: + schema['computed_fields'] = replaced_computed_fields + for k, v in schema['fields'].items(): + replaced_field = v.copy() + replaced_field['schema'] = self.walk(v['schema'], f) + replaced_fields[k] = replaced_field + schema['fields'] = replaced_fields + return schema + + def handle_typed_dict_schema(self, schema: core_schema.TypedDictSchema, f: Walk) -> core_schema.CoreSchema: + extras_schema = schema.get('extras_schema') + if extras_schema is not None: + schema['extras_schema'] = self.walk(extras_schema, f) + replaced_computed_fields: list[core_schema.ComputedField] = [] + for computed_field in schema.get('computed_fields', ()): + replaced_field = computed_field.copy() + replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) + replaced_computed_fields.append(replaced_field) + if replaced_computed_fields: + schema['computed_fields'] = replaced_computed_fields + replaced_fields: dict[str, core_schema.TypedDictField] = {} + for k, v in schema['fields'].items(): + replaced_field = v.copy() + replaced_field['schema'] = self.walk(v['schema'], f) + replaced_fields[k] = replaced_field + schema['fields'] = replaced_fields + return schema + + def handle_dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema, f: Walk) -> core_schema.CoreSchema: + replaced_fields: list[core_schema.DataclassField] = [] + replaced_computed_fields: list[core_schema.ComputedField] = [] + for computed_field in schema.get('computed_fields', ()): + replaced_field = computed_field.copy() + replaced_field['return_schema'] = self.walk(computed_field['return_schema'], f) + replaced_computed_fields.append(replaced_field) + if replaced_computed_fields: + schema['computed_fields'] = replaced_computed_fields + for field in schema['fields']: + replaced_field = field.copy() + replaced_field['schema'] = self.walk(field['schema'], f) + replaced_fields.append(replaced_field) + schema['fields'] = replaced_fields + return schema + + def handle_arguments_schema(self, schema: core_schema.ArgumentsSchema, f: Walk) -> core_schema.CoreSchema: + replaced_arguments_schema: list[core_schema.ArgumentsParameter] = [] + for param in schema['arguments_schema']: + replaced_param = param.copy() + replaced_param['schema'] = self.walk(param['schema'], f) + replaced_arguments_schema.append(replaced_param) + schema['arguments_schema'] = replaced_arguments_schema + if 'var_args_schema' in schema: + schema['var_args_schema'] = self.walk(schema['var_args_schema'], f) + if 'var_kwargs_schema' in schema: + schema['var_kwargs_schema'] = self.walk(schema['var_kwargs_schema'], f) + return schema + + def handle_call_schema(self, schema: core_schema.CallSchema, f: Walk) -> core_schema.CoreSchema: + schema['arguments_schema'] = self.walk(schema['arguments_schema'], f) + if 'return_schema' in schema: + schema['return_schema'] = self.walk(schema['return_schema'], f) + return schema + + +_dispatch = _WalkCoreSchema().walk + + +def walk_core_schema(schema: core_schema.CoreSchema, f: Walk) -> core_schema.CoreSchema: + """Recursively traverse a CoreSchema. + + Args: + schema (core_schema.CoreSchema): The CoreSchema to process, it will not be modified. + f (Walk): A function to apply. This function takes two arguments: + 1. The current CoreSchema that is being processed + (not the same one you passed into this function, one level down). + 2. The "next" `f` to call. This lets you for example use `f=functools.partial(some_method, some_context)` + to pass data down the recursive calls without using globals or other mutable state. + + Returns: + core_schema.CoreSchema: A processed CoreSchema. + """ + return f(schema.copy(), _dispatch) + + +def simplify_schema_references(schema: core_schema.CoreSchema) -> core_schema.CoreSchema: # noqa: C901 + definitions: dict[str, core_schema.CoreSchema] = {} + ref_counts: dict[str, int] = defaultdict(int) + involved_in_recursion: dict[str, bool] = {} + current_recursion_ref_count: dict[str, int] = defaultdict(int) + + def collect_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreSchema: + if s['type'] == 'definitions': + for definition in s['definitions']: + ref = get_ref(definition) + assert ref is not None + if ref not in definitions: + definitions[ref] = definition + recurse(definition, collect_refs) + return recurse(s['schema'], collect_refs) + else: + ref = get_ref(s) + if ref is not None: + new = recurse(s, collect_refs) + new_ref = get_ref(new) + if new_ref: + definitions[new_ref] = new + return core_schema.definition_reference_schema(schema_ref=ref) + else: + return recurse(s, collect_refs) + + schema = walk_core_schema(schema, collect_refs) + + def count_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreSchema: + if s['type'] != 'definition-ref': + return recurse(s, count_refs) + ref = s['schema_ref'] + ref_counts[ref] += 1 + + if ref_counts[ref] >= 2: + # If this model is involved in a recursion this should be detected + # on its second encounter, we can safely stop the walk here. + if current_recursion_ref_count[ref] != 0: + involved_in_recursion[ref] = True + return s + + current_recursion_ref_count[ref] += 1 + recurse(definitions[ref], count_refs) + current_recursion_ref_count[ref] -= 1 + return s + + schema = walk_core_schema(schema, count_refs) + + assert all(c == 0 for c in current_recursion_ref_count.values()), 'this is a bug! please report it' + + def can_be_inlined(s: core_schema.DefinitionReferenceSchema, ref: str) -> bool: + if ref_counts[ref] > 1: + return False + if involved_in_recursion.get(ref, False): + return False + if 'serialization' in s: + return False + if 'metadata' in s: + metadata = s['metadata'] + for k in ( + 'pydantic_js_functions', + 'pydantic_js_annotation_functions', + 'pydantic.internal.union_discriminator', + ): + if k in metadata: + # we need to keep this as a ref + return False + return True + + def inline_refs(s: core_schema.CoreSchema, recurse: Recurse) -> core_schema.CoreSchema: + if s['type'] == 'definition-ref': + ref = s['schema_ref'] + # Check if the reference is only used once, not involved in recursion and does not have + # any extra keys (like 'serialization') + if can_be_inlined(s, ref): + # Inline the reference by replacing the reference with the actual schema + new = definitions.pop(ref) + ref_counts[ref] -= 1 # because we just replaced it! + # put all other keys that were on the def-ref schema into the inlined version + # in particular this is needed for `serialization` + if 'serialization' in s: + new['serialization'] = s['serialization'] + s = recurse(new, inline_refs) + return s + else: + return recurse(s, inline_refs) + else: + return recurse(s, inline_refs) + + schema = walk_core_schema(schema, inline_refs) + + def_values = [v for v in definitions.values() if ref_counts[v['ref']] > 0] # type: ignore + + if def_values: + schema = core_schema.definitions_schema(schema=schema, definitions=def_values) + return schema + + +def _strip_metadata(schema: CoreSchema) -> CoreSchema: + def strip_metadata(s: CoreSchema, recurse: Recurse) -> CoreSchema: + s = s.copy() + s.pop('metadata', None) + if s['type'] == 'model-fields': + s = s.copy() + s['fields'] = {k: v.copy() for k, v in s['fields'].items()} + for field_name, field_schema in s['fields'].items(): + field_schema.pop('metadata', None) + s['fields'][field_name] = field_schema + computed_fields = s.get('computed_fields', None) + if computed_fields: + s['computed_fields'] = [cf.copy() for cf in computed_fields] + for cf in computed_fields: + cf.pop('metadata', None) + else: + s.pop('computed_fields', None) + elif s['type'] == 'model': + # remove some defaults + if s.get('custom_init', True) is False: + s.pop('custom_init') + if s.get('root_model', True) is False: + s.pop('root_model') + if {'title'}.issuperset(s.get('config', {}).keys()): + s.pop('config', None) + + return recurse(s, strip_metadata) + + return walk_core_schema(schema, strip_metadata) def pretty_print_core_schema( - val: Any, - *, - console: Console | None = None, - max_depth: int | None = None, - strip_metadata: bool = True, -) -> None: # pragma: no cover - """Pretty-print a core schema using the `rich` library. + schema: CoreSchema, + include_metadata: bool = False, +) -> None: + """Pretty print a CoreSchema using rich. + This is intended for debugging purposes. Args: - val: The core schema to print, or a Pydantic model/dataclass/type adapter - (in which case the cached core schema is fetched and printed). - console: A rich console to use when printing. Defaults to the global rich console instance. - max_depth: The number of nesting levels which may be printed. - strip_metadata: Whether to strip metadata in the output. If `True` any known core metadata - attributes will be stripped (but custom attributes are kept). Defaults to `True`. + schema: The CoreSchema to print. + include_metadata: Whether to include metadata in the output. Defaults to `False`. """ - # lazy import: - from rich.pretty import pprint + from rich import print # type: ignore # install it manually in your dev env - # circ. imports: - from pydantic import BaseModel, TypeAdapter - from pydantic.dataclasses import is_pydantic_dataclass + if not include_metadata: + schema = _strip_metadata(schema) - if (inspect.isclass(val) and issubclass(val, BaseModel)) or is_pydantic_dataclass(val): - val = val.__pydantic_core_schema__ - if isinstance(val, TypeAdapter): - val = val.core_schema - cleaned_schema = _clean_schema_for_pretty_print(val, strip_metadata=strip_metadata) - - pprint(cleaned_schema, console=console, max_depth=max_depth) + return print(schema) -pps = pretty_print_core_schema +def validate_core_schema(schema: CoreSchema) -> CoreSchema: + if 'PYDANTIC_SKIP_VALIDATING_CORE_SCHEMAS' in os.environ: + return schema + return _validate_core_schema(schema) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py index 869286b2..2bc43e96 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py @@ -1,43 +1,48 @@ """Private logic for creating pydantic dataclasses.""" - from __future__ import annotations as _annotations -import copy import dataclasses -import sys +import inspect +import typing import warnings -from collections.abc import Generator -from contextlib import contextmanager -from functools import partial -from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast +from functools import partial, wraps +from inspect import Parameter, Signature +from typing import Any, Callable, ClassVar from pydantic_core import ( ArgsKwargs, + PydanticUndefined, SchemaSerializer, SchemaValidator, core_schema, ) -from typing_extensions import TypeAlias, TypeIs +from typing_extensions import TypeGuard from ..errors import PydanticUndefinedAnnotation from ..fields import FieldInfo -from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator +from ..plugin._schema_validator import create_schema_validator from ..warnings import PydanticDeprecatedSince20 -from . import _config, _decorators +from . import _config, _decorators, _typing_extra +from ._config import ConfigWrapper from ._fields import collect_dataclass_fields -from ._generate_schema import GenerateSchema, InvalidSchemaError +from ._generate_schema import GenerateSchema, generate_pydantic_signature from ._generics import get_standard_typevars_map from ._mock_val_ser import set_dataclass_mocks -from ._namespace_utils import NsResolver -from ._signature import generate_pydantic_signature -from ._utils import LazyClassAttribute - -if TYPE_CHECKING: - from _typeshed import DataclassInstance as StandardDataclass +from ._schema_generation_shared import CallbackGetCoreSchemaHandler +from ._utils import is_valid_identifier +if typing.TYPE_CHECKING: from ..config import ConfigDict - class PydanticDataclass(StandardDataclass, Protocol): + class StandardDataclass(typing.Protocol): + __dataclass_fields__: ClassVar[dict[str, Any]] + __dataclass_params__: ClassVar[Any] # in reality `dataclasses._DataclassParams` + __post_init__: ClassVar[Callable[..., None]] + + def __init__(self, *args: object, **kwargs: object) -> None: + pass + + class PydanticDataclass(StandardDataclass, typing.Protocol): """A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass. Attributes: @@ -56,28 +61,23 @@ if TYPE_CHECKING: __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] __pydantic_fields__: ClassVar[dict[str, FieldInfo]] __pydantic_serializer__: ClassVar[SchemaSerializer] - __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] + __pydantic_validator__: ClassVar[SchemaValidator] - @classmethod - def __pydantic_fields_complete__(cls) -> bool: ... +else: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 -def set_dataclass_fields( - cls: type[StandardDataclass], - config_wrapper: _config.ConfigWrapper, - ns_resolver: NsResolver | None = None, -) -> None: +def set_dataclass_fields(cls: type[StandardDataclass], types_namespace: dict[str, Any] | None = None) -> None: """Collect and set `cls.__pydantic_fields__`. Args: cls: The class. - config_wrapper: The config wrapper instance. - ns_resolver: Namespace resolver to use when getting dataclass annotations. + types_namespace: The types namespace, defaults to `None`. """ typevars_map = get_standard_typevars_map(cls) - fields = collect_dataclass_fields( - cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper - ) + fields = collect_dataclass_fields(cls, types_namespace, typevars_map=typevars_map) cls.__pydantic_fields__ = fields # type: ignore @@ -87,8 +87,7 @@ def complete_dataclass( config_wrapper: _config.ConfigWrapper, *, raise_errors: bool = True, - ns_resolver: NsResolver | None = None, - _force_build: bool = False, + types_namespace: dict[str, Any] | None, ) -> bool: """Finish building a pydantic dataclass. @@ -100,10 +99,7 @@ def complete_dataclass( cls: The class. config_wrapper: The config wrapper instance. raise_errors: Whether to raise errors, defaults to `True`. - ns_resolver: The namespace resolver instance to use when collecting dataclass fields - and during schema building. - _force_build: Whether to force building the dataclass, no matter if - [`defer_build`][pydantic.config.ConfigDict.defer_build] is set. + types_namespace: The types namespace. Returns: `True` if building a pydantic dataclass is successfully completed, `False` otherwise. @@ -111,10 +107,27 @@ def complete_dataclass( Raises: PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations. """ - original_init = cls.__init__ + if hasattr(cls, '__post_init_post_parse__'): + warnings.warn( + 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', DeprecationWarning + ) - # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied, - # and so that the mock validator is used if building was deferred: + if types_namespace is None: + types_namespace = _typing_extra.get_cls_types_namespace(cls) + + set_dataclass_fields(cls, types_namespace) + + typevars_map = get_standard_typevars_map(cls) + gen_schema = GenerateSchema( + config_wrapper, + types_namespace, + typevars_map, + ) + + # This needs to be called before we change the __init__ + sig = generate_dataclass_signature(cls, cls.__pydantic_fields__, config_wrapper) # type: ignore + + # dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied. def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None: __tracebackhide__ = True s = __dataclass_self__ @@ -124,77 +137,136 @@ def complete_dataclass( cls.__init__ = __init__ # type: ignore cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore - - set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) - - if not _force_build and config_wrapper.defer_build: - set_dataclass_mocks(cls) - return False - - if hasattr(cls, '__post_init_post_parse__'): - warnings.warn( - 'Support for `__post_init_post_parse__` has been dropped, the method will not be called', - PydanticDeprecatedSince20, - ) - - typevars_map = get_standard_typevars_map(cls) - gen_schema = GenerateSchema( - config_wrapper, - ns_resolver=ns_resolver, - typevars_map=typevars_map, - ) - - # set __signature__ attr only for the class, but not for its instances - # (because instances can define `__call__`, and `inspect.signature` shouldn't - # use the `__signature__` attribute and instead generate from `__call__`). - cls.__signature__ = LazyClassAttribute( - '__signature__', - partial( - generate_pydantic_signature, - # It's important that we reference the `original_init` here, - # as it is the one synthesized by the stdlib `dataclass` module: - init=original_init, - fields=cls.__pydantic_fields__, # type: ignore - validate_by_name=config_wrapper.validate_by_name, - extra=config_wrapper.extra, - is_dataclass=True, - ), - ) - + cls.__signature__ = sig # type: ignore + get_core_schema = getattr(cls, '__get_pydantic_core_schema__', None) try: - schema = gen_schema.generate_schema(cls) + if get_core_schema: + schema = get_core_schema( + cls, + CallbackGetCoreSchemaHandler( + partial(gen_schema.generate_schema, from_dunder_get_core_schema=False), + gen_schema, + ref_mode='unpack', + ), + ) + else: + schema = gen_schema.generate_schema(cls, from_dunder_get_core_schema=False) except PydanticUndefinedAnnotation as e: if raise_errors: raise - set_dataclass_mocks(cls, f'`{e.name}`') + set_dataclass_mocks(cls, cls.__name__, f'`{e.name}`') return False - core_config = config_wrapper.core_config(title=cls.__name__) + core_config = config_wrapper.core_config(cls) try: schema = gen_schema.clean_schema(schema) - except InvalidSchemaError: - set_dataclass_mocks(cls) + except gen_schema.CollectedInvalid: + set_dataclass_mocks(cls, cls.__name__, 'all referenced types') return False # We are about to set all the remaining required properties expected for this cast; # __pydantic_decorators__ and __pydantic_fields__ should already be set - cls = cast('type[PydanticDataclass]', cls) + cls = typing.cast('type[PydanticDataclass]', cls) + # debug(schema) cls.__pydantic_core_schema__ = schema - cls.__pydantic_validator__ = create_schema_validator( + cls.__pydantic_validator__ = validator = create_schema_validator( schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings ) cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) - cls.__pydantic_complete__ = True + + if config_wrapper.validate_assignment: + + @wraps(cls.__setattr__) + def validated_setattr(instance: Any, __field: str, __value: str) -> None: + validator.validate_assignment(instance, __field, __value) + + cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore + return True -def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: - """Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass. +def process_param_defaults(param: Parameter) -> Parameter: + """Custom processing where the parameter default is of type FieldInfo - Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses - of a dataclass that are themselves not dataclasses. + Args: + param (Parameter): The parameter + + Returns: + Parameter: The custom processed parameter + """ + param_default = param.default + if isinstance(param_default, FieldInfo): + annotation = param.annotation + # Replace the annotation if appropriate + # inspect does "clever" things to show annotations as strings because we have + # `from __future__ import annotations` in main, we don't want that + if annotation == 'Any': + annotation = Any + + # Replace the field name with the alias if present + name = param.name + alias = param_default.alias + validation_alias = param_default.validation_alias + if validation_alias is None and isinstance(alias, str) and is_valid_identifier(alias): + name = alias + elif isinstance(validation_alias, str) and is_valid_identifier(validation_alias): + name = validation_alias + + # Replace the field default + default = param_default.default + if default is PydanticUndefined: + if param_default.default_factory is PydanticUndefined: + default = inspect.Signature.empty + else: + # this is used by dataclasses to indicate a factory exists: + default = dataclasses._HAS_DEFAULT_FACTORY # type: ignore + return param.replace(annotation=annotation, name=name, default=default) + return param + + +def generate_dataclass_signature( + cls: type[StandardDataclass], fields: dict[str, FieldInfo], config_wrapper: ConfigWrapper +) -> Signature: + """Generate signature for a pydantic dataclass. + + Args: + cls: The dataclass. + fields: The model fields. + config_wrapper: The config wrapper instance. + + Returns: + The dataclass signature. + """ + return generate_pydantic_signature( + init=cls.__init__, fields=fields, config_wrapper=config_wrapper, post_process_parameter=process_param_defaults + ) + + +def is_builtin_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: + """Returns True if a class is a stdlib dataclass and *not* a pydantic dataclass. + + We check that + - `_cls` is a dataclass + - `_cls` does not inherit from a processed pydantic dataclass (and thus have a `__pydantic_validator__`) + - `_cls` does not have any annotations that are not dataclass fields + e.g. + ```py + import dataclasses + + import pydantic.dataclasses + + @dataclasses.dataclass + class A: + x: int + + @pydantic.dataclasses.dataclass + class B(A): + y: int + ``` + In this case, when we first check `B`, we make an extra check and look at the annotations ('y'), + which won't be a superset of all the dataclass fields (only the stdlib fields i.e. 'x') Args: cls: The class. @@ -202,114 +274,8 @@ def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]: Returns: `True` if the class is a stdlib dataclass, `False` otherwise. """ - return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__') - - -def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]: - field_args: dict[str, Any] = {'default': pydantic_field} - - # Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple: - if sys.version_info >= (3, 14) and pydantic_field.description is not None: - field_args['doc'] = pydantic_field.description - - # Needed as the stdlib dataclass module processes kw_only in a specific way during class construction: - if sys.version_info >= (3, 10) and pydantic_field.kw_only: - field_args['kw_only'] = True - - # Needed as the stdlib dataclass modules generates `__repr__()` during class construction: - if pydantic_field.repr is not True: - field_args['repr'] = pydantic_field.repr - - return dataclasses.field(**field_args) - - -DcFields: TypeAlias = dict[str, dataclasses.Field[Any]] - - -@contextmanager -def patch_base_fields(cls: type[Any]) -> Generator[None]: - """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used. - - When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where - the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply - the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass, - the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the - dataclass construction. To do so, we temporarily patch the fields dictionary of the affected - bases. - - For instance, with the following example: - - ```python {test="skip" lint="skip"} - import dataclasses as stdlib_dc - - import pydantic - import pydantic.dataclasses as pydantic_dc - - @stdlib_dc.dataclass - class A: - a: int = pydantic.Field(repr=False) - - # Notice that the `repr` attribute of the dataclass field is `True`: - A.__dataclass_fields__['a'] - #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...) - - @pydantic_dc.dataclass - class B(A): - b: int = pydantic.Field(repr=False) - ``` - - When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes - and reuse them directly. When this context manager is active, `A` will be temporarily patched to be - equivalent to: - - ```python {test="skip" lint="skip"} - @stdlib_dc.dataclass - class A: - a: int = stdlib_dc.field(default=Field(repr=False), repr=False) - ``` - - !!! note - This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic - dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields - directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`). - - !!! note - This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions. - The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a - safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway. - """ - # A list of two-tuples, the first element being a reference to the - # dataclass fields dictionary, the second element being a mapping between - # the field names that were modified, and their original `Field`: - original_fields_list: list[tuple[DcFields, DcFields]] = [] - - for base in cls.__mro__[1:]: - dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) - dc_fields_with_pydantic_field_defaults = { - field_name: field - for field_name, field in dc_fields.items() - if isinstance(field.default, FieldInfo) - # Only do the patching if one of the affected attributes is set: - and (field.default.description is not None or field.default.kw_only or field.default.repr is not True) - } - if dc_fields_with_pydantic_field_defaults: - original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults)) - for field_name, field in dc_fields_with_pydantic_field_defaults.items(): - default = cast(FieldInfo, field.default) - # `dataclasses.Field` isn't documented as working with `copy.copy()`. - # It is a class with `__slots__`, so should work (and we hope for the best): - new_dc_field = copy.copy(field) - # For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant - # for the class under construction and handled in `as_dataclass_field()`. - if sys.version_info >= (3, 10) and default.kw_only: - new_dc_field.kw_only = True - if default.repr is not True: - new_dc_field.repr = default.repr - dc_fields[field_name] = new_dc_field - - try: - yield - finally: - for fields, original_fields in original_fields_list: - for field_name, original_field in original_fields.items(): - fields[field_name] = original_field + return ( + dataclasses.is_dataclass(_cls) + and not hasattr(_cls, '__pydantic_validator__') + and set(_cls.__dataclass_fields__).issuperset(set(getattr(_cls, '__annotations__', {}))) + ) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators.py index 2a43bbb6..2f811a22 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators.py @@ -1,31 +1,30 @@ """Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators.""" - from __future__ import annotations as _annotations -import sys -import types from collections import deque -from collections.abc import Iterable from dataclasses import dataclass, field -from functools import cached_property, partial, partialmethod +from functools import partial, partialmethod from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor, signature from itertools import islice -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Literal, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Iterable, TypeVar, Union -from pydantic_core import PydanticUndefined, PydanticUndefinedType, core_schema -from typing_extensions import TypeAlias, is_typeddict +from pydantic_core import PydanticUndefined, core_schema +from typing_extensions import Literal, TypeAlias, is_typeddict from ..errors import PydanticUserError from ._core_utils import get_type_ref from ._internal_dataclass import slots_true -from ._namespace_utils import GlobalsNamespace, MappingNamespace from ._typing_extra import get_function_type_hints -from ._utils import can_be_positional if TYPE_CHECKING: from ..fields import ComputedFieldInfo from ..functional_validators import FieldValidatorModes - from ._config import ConfigWrapper + +try: + from functools import cached_property # type: ignore +except ImportError: + # python 3.7 + cached_property = None @dataclass(**slots_true) @@ -62,9 +61,6 @@ class FieldValidatorDecoratorInfo: fields: A tuple of field names the validator should be called on. mode: The proposed validator mode. check_fields: Whether to check that the fields actually exist on the model. - json_schema_input_type: The input type of the function. This is only used to generate - the appropriate JSON Schema (in validation mode) and can only specified - when `mode` is either `'before'`, `'plain'` or `'wrap'`. """ decorator_repr: ClassVar[str] = '@field_validator' @@ -72,7 +68,6 @@ class FieldValidatorDecoratorInfo: fields: tuple[str, ...] mode: FieldValidatorModes check_fields: bool | None - json_schema_input_type: Any @dataclass(**slots_true) @@ -137,7 +132,7 @@ class ModelValidatorDecoratorInfo: while building the pydantic-core schema. Attributes: - decorator_repr: A class variable representing the decorator string, '@model_validator'. + decorator_repr: A class variable representing the decorator string, '@model_serializer'. mode: The proposed serializer mode. """ @@ -188,28 +183,22 @@ class PydanticDescriptorProxy(Generic[ReturnType]): def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]: self.wrapped = getattr(self.wrapped, name)(func) - if isinstance(self.wrapped, property): - # update ComputedFieldInfo.wrapped_property - from ..fields import ComputedFieldInfo - - if isinstance(self.decorator_info, ComputedFieldInfo): - self.decorator_info.wrapped_property = self.wrapped return self def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]: try: - return self.wrapped.__get__(obj, obj_type) # pyright: ignore[reportReturnType] + return self.wrapped.__get__(obj, obj_type) except AttributeError: # not a descriptor, e.g. a partial object return self.wrapped # type: ignore[return-value] def __set_name__(self, instance: Any, name: str) -> None: if hasattr(self.wrapped, '__set_name__'): - self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess] + self.wrapped.__set_name__(instance, name) - def __getattr__(self, name: str, /) -> Any: + def __getattr__(self, __name: str) -> Any: """Forward checks for __isabstractmethod__ and such.""" - return getattr(self.wrapped, name) + return getattr(self.wrapped, __name) DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo) @@ -511,20 +500,13 @@ class DecoratorInfos: # so then we don't need to re-process the type, which means we can discard our descriptor wrappers # and replace them with the thing they are wrapping (see the other setattr call below) # which allows validator class methods to also function as regular class methods - model_dc.__pydantic_decorators__ = res + setattr(model_dc, '__pydantic_decorators__', res) for name, value in to_replace: setattr(model_dc, name, value) return res - def update_from_config(self, config_wrapper: ConfigWrapper) -> None: - """Update the decorator infos from the configuration of the class they are attached to.""" - for name, computed_field_dec in self.computed_fields.items(): - computed_field_dec.info._update_from_config(config_wrapper, name) - -def inspect_validator( - validator: Callable[..., Any], *, mode: FieldValidatorModes, type: Literal['field', 'model'] -) -> bool: +def inspect_validator(validator: Callable[..., Any], mode: FieldValidatorModes) -> bool: """Look at a field or model validator function and determine whether it takes an info argument. An error is raised if the function has an invalid signature. @@ -532,18 +514,18 @@ def inspect_validator( Args: validator: The validator function to inspect. mode: The proposed validator mode. - type: The type of validator, either 'field' or 'model'. Returns: Whether the validator takes an info argument. """ try: - sig = _signature_no_eval(validator) - except (ValueError, TypeError): - # `inspect.signature` might not be able to infer a signature, e.g. with C objects. - # In this case, we assume no info argument is present: + sig = signature(validator) + except ValueError: + # builtins and some C extensions don't have signatures + # assume that they don't take an info argument and only take a single argument + # e.g. `str.strip` or `datetime.datetime` return False - n_positional = count_positional_required_params(sig) + n_positional = count_positional_params(sig) if mode == 'wrap': if n_positional == 3: return True @@ -557,12 +539,14 @@ def inspect_validator( return False raise PydanticUserError( - f'Unrecognized {type} validator function signature for {validator} with `mode={mode}`: {sig}', + f'Unrecognized field_validator function signature for {validator} with `mode={mode}`:{sig}', code='validator-signature', ) -def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> tuple[bool, bool]: +def inspect_field_serializer( + serializer: Callable[..., Any], mode: Literal['plain', 'wrap'], computed_field: bool = False +) -> tuple[bool, bool]: """Look at a field serializer function and determine if it is a field serializer, and whether it takes an info argument. @@ -571,21 +555,18 @@ def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plai Args: serializer: The serializer function to inspect. mode: The serializer mode, either 'plain' or 'wrap'. + computed_field: When serializer is applied on computed_field. It doesn't require + info signature. Returns: Tuple of (is_field_serializer, info_arg). """ - try: - sig = _signature_no_eval(serializer) - except (ValueError, TypeError): - # `inspect.signature` might not be able to infer a signature, e.g. with C objects. - # In this case, we assume no info argument is present and this is not a method: - return (False, False) + sig = signature(serializer) first = next(iter(sig.parameters.values()), None) is_field_serializer = first is not None and first.name == 'self' - n_positional = count_positional_required_params(sig) + n_positional = count_positional_params(sig) if is_field_serializer: # -1 to correct for self parameter info_arg = _serializer_info_arg(mode, n_positional - 1) @@ -597,8 +578,13 @@ def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plai f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', code='field-serializer-signature', ) + if info_arg and computed_field: + raise PydanticUserError( + 'field_serializer on computed_field does not use info signature', code='field-serializer-signature' + ) - return is_field_serializer, info_arg + else: + return is_field_serializer, info_arg def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool: @@ -613,13 +599,8 @@ def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal[' Returns: info_arg """ - try: - sig = _signature_no_eval(serializer) - except (ValueError, TypeError): - # `inspect.signature` might not be able to infer a signature, e.g. with C objects. - # In this case, we assume no info argument is present: - return False - info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) + sig = signature(serializer) + info_arg = _serializer_info_arg(mode, count_positional_params(sig)) if info_arg is None: raise PydanticUserError( f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}', @@ -646,8 +627,8 @@ def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plai '`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method' ) - sig = _signature_no_eval(serializer) - info_arg = _serializer_info_arg(mode, count_positional_required_params(sig)) + sig = signature(serializer) + info_arg = _serializer_info_arg(mode, count_positional_params(sig)) if info_arg is None: raise PydanticUserError( f'Unrecognized model_serializer function signature for {serializer} with `mode={mode}`:{sig}', @@ -660,18 +641,18 @@ def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plai def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None: if mode == 'plain': if n_positional == 1: - # (input_value: Any, /) -> Any + # (__input_value: Any) -> Any return False elif n_positional == 2: - # (model: Any, input_value: Any, /) -> Any + # (__model: Any, __input_value: Any) -> Any return True else: assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'" if n_positional == 2: - # (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any + # (__input_value: Any, __serializer: SerializerFunctionWrapHandler) -> Any return False elif n_positional == 3: - # (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any + # (__input_value: Any, __serializer: SerializerFunctionWrapHandler, __info: SerializationInfo) -> Any return True return None @@ -694,7 +675,7 @@ def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool: Returns: `True` if the function is an instance method, `False` otherwise. """ - sig = _signature_no_eval(unwrap_wrapped_function(function)) + sig = signature(unwrap_wrapped_function(function)) first = next(iter(sig.parameters.values()), None) if first and first.name == 'self': return True @@ -718,7 +699,7 @@ def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool: - sig = _signature_no_eval(unwrap_wrapped_function(function)) + sig = signature(unwrap_wrapped_function(function)) first = next(iter(sig.parameters.values()), None) if first and first.name == 'cls': return True @@ -732,25 +713,34 @@ def unwrap_wrapped_function( unwrap_class_static_method: bool = True, ) -> Any: """Recursively unwraps a wrapped function until the underlying function is reached. - This handles property, functools.partial, functools.partialmethod, staticmethod, and classmethod. + This handles property, functools.partial, functools.partialmethod, staticmethod and classmethod. Args: func: The function to unwrap. - unwrap_partial: If True (default), unwrap partial and partialmethod decorators. + unwrap_partial: If True (default), unwrap partial and partialmethod decorators, otherwise don't. + decorators. unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod decorators. If False, only unwrap partial and partialmethod decorators. Returns: The underlying function of the wrapped function. """ - # Define the types we want to check against as a single tuple. - unwrap_types = ( - (property, cached_property) - + ((partial, partialmethod) if unwrap_partial else ()) - + ((staticmethod, classmethod) if unwrap_class_static_method else ()) - ) + all: set[Any] = {property} - while isinstance(func, unwrap_types): + if unwrap_partial: + all.update({partial, partialmethod}) + + try: + from functools import cached_property # type: ignore + except ImportError: + cached_property = type('', (), {}) + else: + all.add(cached_property) + + if unwrap_class_static_method: + all.update({staticmethod, classmethod}) + + while isinstance(func, tuple(all)): if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)): func = func.__func__ elif isinstance(func, (partial, partialmethod)): @@ -765,72 +755,38 @@ def unwrap_wrapped_function( return func -_function_like = ( - partial, - partialmethod, - types.FunctionType, - types.BuiltinFunctionType, - types.MethodType, - types.WrapperDescriptorType, - types.MethodWrapperType, - types.MemberDescriptorType, -) +def get_function_return_type( + func: Any, explicit_return_type: Any, types_namespace: dict[str, Any] | None = None +) -> Any: + """Get the function return type. - -def get_callable_return_type( - callable_obj: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, -) -> Any | PydanticUndefinedType: - """Get the callable return type. + It gets the return type from the type annotation if `explicit_return_type` is `None`. + Otherwise, it returns `explicit_return_type`. Args: - callable_obj: The callable to analyze. - globalns: The globals namespace to use during type annotation evaluation. - localns: The locals namespace to use during type annotation evaluation. + func: The function to get its return type. + explicit_return_type: The explicit return type. + types_namespace: The types namespace, defaults to `None`. Returns: The function return type. """ - if isinstance(callable_obj, type): - # types are callables, and we assume the return type - # is the type itself (e.g. `int()` results in an instance of `int`). - return callable_obj - - if not isinstance(callable_obj, _function_like): - call_func = getattr(type(callable_obj), '__call__', None) # noqa: B004 - if call_func is not None: - callable_obj = call_func - - hints = get_function_type_hints( - unwrap_wrapped_function(callable_obj), - include_keys={'return'}, - globalns=globalns, - localns=localns, - ) - return hints.get('return', PydanticUndefined) + if explicit_return_type is PydanticUndefined: + # try to get it from the type annotation + hints = get_function_type_hints( + unwrap_wrapped_function(func), include_keys={'return'}, types_namespace=types_namespace + ) + return hints.get('return', PydanticUndefined) + else: + return explicit_return_type -def count_positional_required_params(sig: Signature) -> int: - """Get the number of positional (required) arguments of a signature. +def count_positional_params(sig: Signature) -> int: + return sum(1 for param in sig.parameters.values() if can_be_positional(param)) - This function should only be used to inspect signatures of validation and serialization functions. - The first argument (the value being serialized or validated) is counted as a required argument - even if a default value exists. - Returns: - The number of positional arguments of a signature. - """ - parameters = list(sig.parameters.values()) - return sum( - 1 - for param in parameters - if can_be_positional(param) - # First argument is the value being validated/serialized, and can have a default value - # (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg - # for instance) should be required, and thus without any default value. - and (param.default is Parameter.empty or param is parameters[0]) - ) +def can_be_positional(param: Parameter) -> bool: + return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) def ensure_property(f: Any) -> Any: @@ -846,13 +802,3 @@ def ensure_property(f: Any) -> Any: return f else: return property(f) - - -def _signature_no_eval(f: Callable[..., Any]) -> Signature: - """Get the signature of a callable without evaluating any annotations.""" - if sys.version_info >= (3, 14): - from annotationlib import Format - - return signature(f, annotation_format=Format.FORWARDREF) - else: - return signature(f) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators_v1.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators_v1.py index 34273779..4f81e6d4 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators_v1.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_decorators_v1.py @@ -1,45 +1,49 @@ """Logic for V1 validators, e.g. `@validator` and `@root_validator`.""" - from __future__ import annotations as _annotations from inspect import Parameter, signature -from typing import Any, Union, cast +from typing import Any, Dict, Tuple, Union, cast from pydantic_core import core_schema from typing_extensions import Protocol from ..errors import PydanticUserError -from ._utils import can_be_positional +from ._decorators import can_be_positional class V1OnlyValueValidator(Protocol): """A simple validator, supported for V1 validators and V2 validators.""" - def __call__(self, __value: Any) -> Any: ... + def __call__(self, __value: Any) -> Any: + ... class V1ValidatorWithValues(Protocol): """A validator with `values` argument, supported for V1 validators and V2 validators.""" - def __call__(self, __value: Any, values: dict[str, Any]) -> Any: ... + def __call__(self, __value: Any, values: dict[str, Any]) -> Any: + ... class V1ValidatorWithValuesKwOnly(Protocol): """A validator with keyword only `values` argument, supported for V1 validators and V2 validators.""" - def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: ... + def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: + ... class V1ValidatorWithKwargs(Protocol): """A validator with `kwargs` argument, supported for V1 validators and V2 validators.""" - def __call__(self, __value: Any, **kwargs: Any) -> Any: ... + def __call__(self, __value: Any, **kwargs: Any) -> Any: + ... class V1ValidatorWithValuesAndKwargs(Protocol): """A validator with `values` and `kwargs` arguments, supported for V1 validators and V2 validators.""" - def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... + def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: + ... V1Validator = Union[ @@ -105,21 +109,23 @@ def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithI return wrapper2 -RootValidatorValues = dict[str, Any] +RootValidatorValues = Dict[str, Any] # technically tuple[model_dict, model_extra, fields_set] | tuple[dataclass_dict, init_vars] -RootValidatorFieldsTuple = tuple[Any, ...] +RootValidatorFieldsTuple = Tuple[Any, ...] class V1RootValidatorFunction(Protocol): """A simple root validator, supported for V1 validators and V2 validators.""" - def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: ... + def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: + ... class V2CoreBeforeRootValidator(Protocol): """V2 validator with mode='before'.""" - def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: ... + def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: + ... class V2CoreAfterRootValidator(Protocol): @@ -127,7 +133,8 @@ class V2CoreAfterRootValidator(Protocol): def __call__( self, __fields_tuple: RootValidatorFieldsTuple, __info: core_schema.ValidationInfo - ) -> RootValidatorFieldsTuple: ... + ) -> RootValidatorFieldsTuple: + ... def make_v1_generic_root_validator( diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py index 5dd6fdaf..e3806b8f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py @@ -1,19 +1,22 @@ from __future__ import annotations as _annotations -from collections.abc import Hashable, Sequence -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Hashable, Sequence from pydantic_core import CoreSchema, core_schema from ..errors import PydanticUserError from . import _core_utils from ._core_utils import ( + NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY, CoreSchemaField, + collect_definitions, + simplify_schema_references, ) if TYPE_CHECKING: from ..types import Discriminator - from ._core_metadata import CoreMetadata + +CORE_SCHEMA_METADATA_DISCRIMINATOR_PLACEHOLDER_KEY = 'pydantic.internal.union_discriminator' class MissingDefinitionForUnionRef(Exception): @@ -26,9 +29,35 @@ class MissingDefinitionForUnionRef(Exception): super().__init__(f'Missing definition for ref {self.ref!r}') -def set_discriminator_in_metadata(schema: CoreSchema, discriminator: Any) -> None: - metadata = cast('CoreMetadata', schema.setdefault('metadata', {})) - metadata['pydantic_internal_union_discriminator'] = discriminator +def set_discriminator(schema: CoreSchema, discriminator: Any) -> None: + schema.setdefault('metadata', {}) + metadata = schema.get('metadata') + assert metadata is not None + metadata[CORE_SCHEMA_METADATA_DISCRIMINATOR_PLACEHOLDER_KEY] = discriminator + + +def apply_discriminators(schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + definitions: dict[str, CoreSchema] | None = None + + def inner(s: core_schema.CoreSchema, recurse: _core_utils.Recurse) -> core_schema.CoreSchema: + nonlocal definitions + if 'metadata' in s: + if s['metadata'].get(NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY, True) is False: + return s + + s = recurse(s, inner) + if s['type'] == 'tagged-union': + return s + + metadata = s.get('metadata', {}) + discriminator = metadata.get(CORE_SCHEMA_METADATA_DISCRIMINATOR_PLACEHOLDER_KEY, None) + if discriminator is not None: + if definitions is None: + definitions = collect_definitions(schema) + s = apply_discriminator(s, discriminator, definitions) + return s + + return simplify_schema_references(_core_utils.walk_core_schema(schema, inner)) def apply_discriminator( @@ -134,7 +163,7 @@ class _ApplyInferredDiscriminator: # in the output TaggedUnionSchema that will replace the union from the input schema self._tagged_union_choices: dict[Hashable, core_schema.CoreSchema] = {} - # `_used` is changed to True after applying the discriminator to prevent accidental reuse + # `_used` is changed to True after applying the discriminator to prevent accidental re-use self._used = False def apply(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: @@ -160,11 +189,16 @@ class _ApplyInferredDiscriminator: - If discriminator fields have different aliases. - If discriminator field not of type `Literal`. """ + self.definitions.update(collect_definitions(schema)) assert not self._used schema = self._apply_to_root(schema) if self._should_be_nullable and not self._is_nullable: schema = core_schema.nullable_schema(schema) self._used = True + new_defs = collect_definitions(schema) + missing_defs = self.definitions.keys() - new_defs.keys() + if missing_defs: + schema = core_schema.definitions_schema(schema, [self.definitions[ref] for ref in missing_defs]) return schema def _apply_to_root(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: @@ -234,10 +268,6 @@ class _ApplyInferredDiscriminator: * Validating that each allowed discriminator value maps to a unique choice * Updating the _tagged_union_choices mapping that will ultimately be used to build the TaggedUnionSchema. """ - if choice['type'] == 'definition-ref': - if choice['schema_ref'] not in self.definitions: - raise MissingDefinitionForUnionRef(choice['schema_ref']) - if choice['type'] == 'none': self._should_be_nullable = True elif choice['type'] == 'definitions': @@ -249,6 +279,10 @@ class _ApplyInferredDiscriminator: # Reverse the choices list before extending the stack so that they get handled in the order they occur choices_schemas = [v[0] if isinstance(v, tuple) else v for v in choice['choices'][::-1]] self._choices_to_handle.extend(choices_schemas) + elif choice['type'] == 'definition-ref': + if choice['schema_ref'] not in self.definitions: + raise MissingDefinitionForUnionRef(choice['schema_ref']) + self._handle_choice(self.definitions[choice['schema_ref']]) elif choice['type'] not in { 'model', 'typed-dict', @@ -256,16 +290,12 @@ class _ApplyInferredDiscriminator: 'lax-or-strict', 'dataclass', 'dataclass-args', - 'definition-ref', } and not _core_utils.is_function_with_inner_schema(choice): # We should eventually handle 'definition-ref' as well - err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.' - if choice['type'] == 'list': - err_str += ( - ' If you are making use of a list of union types, make sure the discriminator is applied to the ' - 'union type and not the list (e.g. `list[Annotated[ | , Field(discriminator=...)]]`).' - ) - raise TypeError(err_str) + raise TypeError( + f'{choice["type"]!r} is not a valid discriminated union variant;' + ' should be a `BaseModel` or `dataclass`' + ) else: if choice['type'] == 'tagged-union' and self._is_discriminator_shared(choice): # In this case, this inner tagged-union is compatible with the outer tagged-union, @@ -299,10 +329,13 @@ class _ApplyInferredDiscriminator: """ if choice['type'] == 'definitions': return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name) - + elif choice['type'] == 'function-plain': + raise TypeError( + f'{choice["type"]!r} is not a valid discriminated union variant;' + ' should be a `BaseModel` or `dataclass`' + ) elif _core_utils.is_function_with_inner_schema(choice): return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name) - elif choice['type'] == 'lax-or-strict': return sorted( set( @@ -353,13 +386,10 @@ class _ApplyInferredDiscriminator: raise MissingDefinitionForUnionRef(schema_ref) return self._infer_discriminator_values_for_choice(self.definitions[schema_ref], source_name=source_name) else: - err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.' - if choice['type'] == 'list': - err_str += ( - ' If you are making use of a list of union types, make sure the discriminator is applied to the ' - 'union type and not the list (e.g. `list[Annotated[ | , Field(discriminator=...)]]`).' - ) - raise TypeError(err_str) + raise TypeError( + f'{choice["type"]!r} is not a valid discriminated union variant;' + ' should be a `BaseModel` or `dataclass`' + ) def _infer_discriminator_values_for_typed_dict_choice( self, choice: core_schema.TypedDictSchema, source_name: str | None = None diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_docs_extraction.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_docs_extraction.py deleted file mode 100644 index 6df77bf6..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_docs_extraction.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Utilities related to attribute docstring extraction.""" - -from __future__ import annotations - -import ast -import inspect -import sys -import textwrap -from typing import Any - - -class DocstringVisitor(ast.NodeVisitor): - def __init__(self) -> None: - super().__init__() - - self.target: str | None = None - self.attrs: dict[str, str] = {} - self.previous_node_type: type[ast.AST] | None = None - - def visit(self, node: ast.AST) -> Any: - node_result = super().visit(node) - self.previous_node_type = type(node) - return node_result - - def visit_AnnAssign(self, node: ast.AnnAssign) -> Any: - if isinstance(node.target, ast.Name): - self.target = node.target.id - - def visit_Expr(self, node: ast.Expr) -> Any: - if ( - isinstance(node.value, ast.Constant) - and isinstance(node.value.value, str) - and self.previous_node_type is ast.AnnAssign - ): - docstring = inspect.cleandoc(node.value.value) - if self.target: - self.attrs[self.target] = docstring - self.target = None - - -def _dedent_source_lines(source: list[str]) -> str: - # Required for nested class definitions, e.g. in a function block - dedent_source = textwrap.dedent(''.join(source)) - if dedent_source.startswith((' ', '\t')): - # We are in the case where there's a dedented (usually multiline) string - # at a lower indentation level than the class itself. We wrap our class - # in a function as a workaround. - dedent_source = f'def dedent_workaround():\n{dedent_source}' - return dedent_source - - -def _extract_source_from_frame(cls: type[Any]) -> list[str] | None: - frame = inspect.currentframe() - - while frame: - if inspect.getmodule(frame) is inspect.getmodule(cls): - lnum = frame.f_lineno - try: - lines, _ = inspect.findsource(frame) - except OSError: # pragma: no cover - # Source can't be retrieved (maybe because running in an interactive terminal), - # we don't want to error here. - pass - else: - block_lines = inspect.getblock(lines[lnum - 1 :]) - dedent_source = _dedent_source_lines(block_lines) - try: - block_tree = ast.parse(dedent_source) - except SyntaxError: - pass - else: - stmt = block_tree.body[0] - if isinstance(stmt, ast.FunctionDef) and stmt.name == 'dedent_workaround': - # `_dedent_source_lines` wrapped the class around the workaround function - stmt = stmt.body[0] - if isinstance(stmt, ast.ClassDef) and stmt.name == cls.__name__: - return block_lines - - frame = frame.f_back - - -def extract_docstrings_from_cls(cls: type[Any], use_inspect: bool = False) -> dict[str, str]: - """Map model attributes and their corresponding docstring. - - Args: - cls: The class of the Pydantic model to inspect. - use_inspect: Whether to skip usage of frames to find the object and use - the `inspect` module instead. - - Returns: - A mapping containing attribute names and their corresponding docstring. - """ - if use_inspect or sys.version_info >= (3, 13): - # On Python < 3.13, `inspect.getsourcelines()` might not work as expected - # if two classes have the same name in the same source file. - # On Python 3.13+, it will use the new `__firstlineno__` class attribute, - # making it way more robust. - try: - source, _ = inspect.getsourcelines(cls) - except OSError: # pragma: no cover - return {} - else: - # TODO remove this implementation when we drop support for Python 3.12: - source = _extract_source_from_frame(cls) - - if not source: - return {} - - dedent_source = _dedent_source_lines(source) - - visitor = DocstringVisitor() - visitor.visit(ast.parse(dedent_source)) - return visitor.attrs diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py index aad2ac94..7a3410e3 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py @@ -1,40 +1,58 @@ """Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`.""" - from __future__ import annotations as _annotations import dataclasses +import sys import warnings -from collections.abc import Mapping -from functools import cache -from inspect import Parameter, ismethoddescriptor, signature -from re import Pattern -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from copy import copy +from functools import lru_cache +from typing import TYPE_CHECKING, Any from pydantic_core import PydanticUndefined -from typing_extensions import TypeIs -from typing_inspection.introspection import AnnotationSource -from pydantic import PydanticDeprecatedSince211 -from pydantic.errors import PydanticUserError - -from ..aliases import AliasGenerator -from . import _generics, _typing_extra +from . import _typing_extra from ._config import ConfigWrapper -from ._docs_extraction import extract_docstrings_from_cls -from ._import_utils import import_cached_base_model, import_cached_field_info -from ._namespace_utils import NsResolver from ._repr import Representation -from ._utils import can_be_positional, get_first_not_none +from ._typing_extra import get_cls_type_hints_lenient, get_type_hints, is_classvar, is_finalvar if TYPE_CHECKING: from annotated_types import BaseMetadata from ..fields import FieldInfo from ..main import BaseModel - from ._dataclasses import PydanticDataclass, StandardDataclass + from ._dataclasses import StandardDataclass from ._decorators import DecoratorInfos +def get_type_hints_infer_globalns( + obj: Any, + localns: dict[str, Any] | None = None, + include_extras: bool = False, +) -> dict[str, Any]: + """Gets type hints for an object by inferring the global namespace. + + It uses the `typing.get_type_hints`, The only thing that we do here is fetching + global namespace from `obj.__module__` if it is not `None`. + + Args: + obj: The object to get its type hints. + localns: The local namespaces. + include_extras: Whether to recursively include annotation metadata. + + Returns: + The object type hints. + """ + module_name = getattr(obj, '__module__', None) + globalns: dict[str, Any] | None = None + if module_name: + try: + globalns = sys.modules[module_name].__dict__ + except KeyError: + # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 + pass + return get_type_hints(obj, globalns=globalns, localns=localns, include_extras=include_extras) + + class PydanticMetadata(Representation): """Base class for annotation markers like `Strict`.""" @@ -53,7 +71,7 @@ def pydantic_general_metadata(**metadata: Any) -> BaseMetadata: return _general_metadata_cls()(metadata) # type: ignore -@cache +@lru_cache(maxsize=None) def _general_metadata_cls() -> type[BaseMetadata]: """Do it this way to avoid importing `annotated_types` at import time.""" from annotated_types import BaseMetadata @@ -67,176 +85,29 @@ def _general_metadata_cls() -> type[BaseMetadata]: return _PydanticGeneralMetadata # type: ignore -def _check_protected_namespaces( - protected_namespaces: tuple[str | Pattern[str], ...], - ann_name: str, - bases: tuple[type[Any], ...], - cls_name: str, -) -> None: - BaseModel = import_cached_base_model() - - for protected_namespace in protected_namespaces: - ns_violation = False - if isinstance(protected_namespace, Pattern): - ns_violation = protected_namespace.match(ann_name) is not None - elif isinstance(protected_namespace, str): - ns_violation = ann_name.startswith(protected_namespace) - - if ns_violation: - for b in bases: - if hasattr(b, ann_name): - if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})): - raise ValueError( - f'Field {ann_name!r} conflicts with member {getattr(b, ann_name)}' - f' of protected namespace {protected_namespace!r}.' - ) - else: - valid_namespaces: list[str] = [] - for pn in protected_namespaces: - if isinstance(pn, Pattern): - if not pn.match(ann_name): - valid_namespaces.append(f're.compile({pn.pattern!r})') - else: - if not ann_name.startswith(pn): - valid_namespaces.append(f"'{pn}'") - - valid_namespaces_str = f'({", ".join(valid_namespaces)}{",)" if len(valid_namespaces) == 1 else ")"}' - - warnings.warn( - f'Field {ann_name!r} in {cls_name!r} conflicts with protected namespace {protected_namespace!r}.\n\n' - f"You may be able to solve this by setting the 'protected_namespaces' configuration to {valid_namespaces_str}.", - UserWarning, - stacklevel=5, - ) - - -def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], use_inspect: bool = False) -> None: - fields_docs = extract_docstrings_from_cls(cls, use_inspect=use_inspect) - for ann_name, field_info in fields.items(): - if field_info.description is None and ann_name in fields_docs: - field_info.description = fields_docs[ann_name] - - -def _apply_field_title_generator_to_field_info( - title_generator: Callable[[str, FieldInfo], str], - field_name: str, - field_info: FieldInfo, -): - if field_info.title is None: - title = title_generator(field_name, field_info) - if not isinstance(title, str): - raise TypeError(f'field_title_generator {title_generator} must return str, not {title.__class__}') - - field_info.title = title - - -def _apply_alias_generator_to_field_info( - alias_generator: Callable[[str], str] | AliasGenerator, field_name: str, field_info: FieldInfo -): - """Apply an alias generator to aliases on a `FieldInfo` instance if appropriate. - - Args: - alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance. - field_name: The name of the field from which to generate the alias. - field_info: The `FieldInfo` instance to which the alias generator is (maybe) applied. - """ - # Apply an alias_generator if - # 1. An alias is not specified - # 2. An alias is specified, but the priority is <= 1 - if ( - field_info.alias_priority is None - or field_info.alias_priority <= 1 - or field_info.alias is None - or field_info.validation_alias is None - or field_info.serialization_alias is None - ): - alias, validation_alias, serialization_alias = None, None, None - - if isinstance(alias_generator, AliasGenerator): - alias, validation_alias, serialization_alias = alias_generator.generate_aliases(field_name) - elif callable(alias_generator): - alias = alias_generator(field_name) - if not isinstance(alias, str): - raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}') - - # if priority is not set, we set to 1 - # which supports the case where the alias_generator from a child class is used - # to generate an alias for a field in a parent class - if field_info.alias_priority is None or field_info.alias_priority <= 1: - field_info.alias_priority = 1 - - # if the priority is 1, then we set the aliases to the generated alias - if field_info.alias_priority == 1: - field_info.serialization_alias = get_first_not_none(serialization_alias, alias) - field_info.validation_alias = get_first_not_none(validation_alias, alias) - field_info.alias = alias - - # if any of the aliases are not set, then we set them to the corresponding generated alias - if field_info.alias is None: - field_info.alias = alias - if field_info.serialization_alias is None: - field_info.serialization_alias = get_first_not_none(serialization_alias, alias) - if field_info.validation_alias is None: - field_info.validation_alias = get_first_not_none(validation_alias, alias) - - -def update_field_from_config(config_wrapper: ConfigWrapper, field_name: str, field_info: FieldInfo) -> None: - """Update the `FieldInfo` instance from the configuration set on the model it belongs to. - - This will apply the title and alias generators from the configuration. - - Args: - config_wrapper: The configuration from the model. - field_name: The field name the `FieldInfo` instance is attached to. - field_info: The `FieldInfo` instance to update. - """ - field_title_generator = field_info.field_title_generator or config_wrapper.field_title_generator - if field_title_generator is not None: - _apply_field_title_generator_to_field_info(field_title_generator, field_name, field_info) - if config_wrapper.alias_generator is not None: - _apply_alias_generator_to_field_info(config_wrapper.alias_generator, field_name, field_info) - - -_deprecated_method_names = {'dict', 'json', 'copy', '_iter', '_copy_and_set_values', '_calculate_keys'} - -_deprecated_classmethod_names = { - 'parse_obj', - 'parse_raw', - 'parse_file', - 'from_orm', - 'construct', - 'schema', - 'schema_json', - 'validate', - 'update_forward_refs', - '_get_value', -} - - def collect_model_fields( # noqa: C901 cls: type[BaseModel], + bases: tuple[type[Any], ...], config_wrapper: ConfigWrapper, - ns_resolver: NsResolver | None, + types_namespace: dict[str, Any] | None, *, - typevars_map: Mapping[TypeVar, Any] | None = None, + typevars_map: dict[Any, Any] | None = None, ) -> tuple[dict[str, FieldInfo], set[str]]: - """Collect the fields and class variables names of a nascent Pydantic model. + """Collect the fields of a nascent pydantic model. - The fields collection process is *lenient*, meaning it won't error if string annotations - fail to evaluate. If this happens, the original annotation (and assigned value, if any) - is stored on the created `FieldInfo` instance. + Also collect the names of any ClassVars present in the type hints. - The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model), - and will make use of these stored attributes. + The returned value is a tuple of two items: the fields dict, and the set of ClassVar names. Args: cls: BaseModel or dataclass. + bases: Parents of the class, generally `cls.__bases__`. config_wrapper: The config wrapper instance. - ns_resolver: Namespace resolver to use when getting model annotations. + types_namespace: Optional extra namespace to look for types in. typevars_map: A dictionary mapping type variables to their concrete types. Returns: - A two-tuple containing model fields and class variables names. + A tuple contains fields and class variables. Raises: NameError: @@ -244,58 +115,49 @@ def collect_model_fields( # noqa: C901 - If there is a field other than `root` in `RootModel`. - If a field shadows an attribute in the parent model. """ - FieldInfo_ = import_cached_field_info() - BaseModel_ = import_cached_base_model() + from ..fields import FieldInfo - bases = cls.__bases__ - parent_fields_lookup: dict[str, FieldInfo] = {} - for base in reversed(bases): - if model_fields := getattr(base, '__pydantic_fields__', None): - parent_fields_lookup.update(model_fields) - - type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver) + type_hints = get_cls_type_hints_lenient(cls, types_namespace) # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older # annotations is only used for finding fields in parent classes - annotations = _typing_extra.safe_get_annotations(cls) - + annotations = cls.__dict__.get('__annotations__', {}) fields: dict[str, FieldInfo] = {} class_vars: set[str] = set() - for ann_name, (ann_type, evaluated) in type_hints.items(): + for ann_name, ann_type in type_hints.items(): if ann_name == 'model_config': # We never want to treat `model_config` as a field # Note: we may need to change this logic if/when we introduce a `BareModel` class with no # protected namespaces (where `model_config` might be allowed as a field name) continue + for protected_namespace in config_wrapper.protected_namespaces: + if ann_name.startswith(protected_namespace): + for b in bases: + if hasattr(b, ann_name): + from ..main import BaseModel - _check_protected_namespaces( - protected_namespaces=config_wrapper.protected_namespaces, - ann_name=ann_name, - bases=bases, - cls_name=cls.__name__, - ) - - if _typing_extra.is_classvar_annotation(ann_type): + if not (issubclass(b, BaseModel) and ann_name in b.model_fields): + raise NameError( + f'Field "{ann_name}" conflicts with member {getattr(b, ann_name)}' + f' of protected namespace "{protected_namespace}".' + ) + else: + valid_namespaces = tuple( + x for x in config_wrapper.protected_namespaces if not ann_name.startswith(x) + ) + warnings.warn( + f'Field "{ann_name}" has conflict with protected namespace "{protected_namespace}".' + '\n\nYou may be able to resolve this warning by setting' + f" `model_config['protected_namespaces'] = {valid_namespaces}`.", + UserWarning, + ) + if is_classvar(ann_type): + class_vars.add(ann_name) + continue + if _is_finalvar_with_default_val(ann_type, getattr(cls, ann_name, PydanticUndefined)): class_vars.add(ann_name) continue - - assigned_value = getattr(cls, ann_name, PydanticUndefined) - if assigned_value is not PydanticUndefined and ( - # One of the deprecated instance methods was used as a field name (e.g. `dict()`): - any(getattr(BaseModel_, depr_name, None) is assigned_value for depr_name in _deprecated_method_names) - # One of the deprecated class methods was used as a field name (e.g. `schema()`): - or ( - hasattr(assigned_value, '__func__') - and any( - getattr(getattr(BaseModel_, depr_name, None), '__func__', None) is assigned_value.__func__ # pyright: ignore[reportAttributeAccessIssue] - for depr_name in _deprecated_classmethod_names - ) - ) - ): - # Then `assigned_value` would be the method, even though no default was specified: - assigned_value = PydanticUndefined - if not is_valid_field_name(ann_name): continue if cls.__pydantic_root_model__ and ann_name != 'root': @@ -304,7 +166,7 @@ def collect_model_fields( # noqa: C901 ) # when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get - # "... shadows an attribute" warnings + # "... shadows an attribute" errors generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') for base in bases: dataclass_fields = { @@ -312,74 +174,42 @@ def collect_model_fields( # noqa: C901 } if hasattr(base, ann_name): if base is generic_origin: - # Don't warn when "shadowing" of attributes in parametrized generics + # Don't error when "shadowing" of attributes in parametrized generics continue if ann_name in dataclass_fields: - # Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set + # Don't error when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set # on the class instance. continue - - if ann_name not in annotations: - # Don't warn when a field exists in a parent class but has not been defined in the current class - continue - warnings.warn( - f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent ' - f'"{base.__qualname__}"', + f'Field name "{ann_name}" shadows an attribute in parent "{base.__qualname__}"; ', UserWarning, - stacklevel=4, ) - if assigned_value is PydanticUndefined: # no assignment, just a plain annotation - if ann_name in annotations or ann_name not in parent_fields_lookup: - # field is either: - # - present in the current model's annotations (and *not* from parent classes) - # - not found on any base classes; this seems to be caused by fields bot getting - # generated due to models not being fully defined while initializing recursive models. - # Nothing stops us from just creating a `FieldInfo` for this type hint, so we do this. - field_info = FieldInfo_.from_annotation(ann_type, _source=AnnotationSource.CLASS) - if not evaluated: - field_info._complete = False - # Store the original annotation that should be used to rebuild - # the field info later: - field_info._original_annotation = ann_type + try: + default = getattr(cls, ann_name, PydanticUndefined) + if default is PydanticUndefined: + raise AttributeError + except AttributeError: + if ann_name in annotations: + field_info = FieldInfo.from_annotation(ann_type) else: - # The field was present on one of the (possibly multiple) base classes - # copy the field to make sure typevar substitutions don't cause issues with the base classes - field_info = parent_fields_lookup[ann_name]._copy() - - else: # An assigned value is present (either the default value, or a `Field()` function) - if isinstance(assigned_value, FieldInfo_) and ismethoddescriptor(assigned_value.default): - # `assigned_value` was fetched using `getattr`, which triggers a call to `__get__` - # for descriptors, so we do the same if the `= field(default=...)` form is used. - # Note that we only do this for method descriptors for now, we might want to - # extend this to any descriptor in the future (by simply checking for - # `hasattr(assigned_value.default, '__get__')`). - default = assigned_value.default.__get__(None, cls) - assigned_value.default = default - assigned_value._attributes_set['default'] = default - - field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS) - # Store the original annotation and assignment value that should be used to rebuild the field info later. - # Note that the assignment is always stored as the annotation might contain a type var that is later - # parameterized with an unknown forward reference (and we'll need it to rebuild the field info): - field_info._original_assignment = assigned_value - if not evaluated: - field_info._complete = False - field_info._original_annotation = ann_type - elif 'final' in field_info._qualifiers and not field_info.is_required(): - warnings.warn( - f'Annotation {ann_name!r} is marked as final and has a default value. Pydantic treats {ann_name!r} as a ' - 'class variable, but it will be considered as a normal field in V3 to be aligned with dataclasses. If you ' - f'still want {ann_name!r} to be considered as a class variable, annotate it as: `ClassVar[] = .`', - category=PydanticDeprecatedSince211, - # Incorrect when `create_model` is used, but the chance that final with a default is used is low in that case: - stacklevel=4, - ) - class_vars.add(ann_name) - continue - + # if field has no default value and is not in __annotations__ this means that it is + # defined in a base class and we can take it from there + model_fields_lookup: dict[str, FieldInfo] = {} + for x in cls.__bases__[::-1]: + model_fields_lookup.update(getattr(x, 'model_fields', {})) + if ann_name in model_fields_lookup: + # The field was present on one of the (possibly multiple) base classes + # copy the field to make sure typevar substitutions don't cause issues with the base classes + field_info = copy(model_fields_lookup[ann_name]) + else: + # The field was not found on any base classes; this seems to be caused by fields not getting + # generated thanks to models not being fully defined while initializing recursive models. + # Nothing stops us from just creating a new FieldInfo for this type hint, so we do this. + field_info = FieldInfo.from_annotation(ann_type) + else: + field_info = FieldInfo.from_annotated_attribute(ann_type, default) # attributes which are fields are removed from the class namespace: # 1. To match the behaviour of annotation-only fields # 2. To avoid false positives in the NameError check above @@ -392,244 +222,85 @@ def collect_model_fields( # noqa: C901 # to make sure the decorators have already been built for this exact class decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__'] if ann_name in decorators.computed_fields: - raise TypeError( - f'Field {ann_name!r} of class {cls.__name__!r} overrides symbol of same name in a parent class. ' - 'This override with a computed_field is incompatible.' - ) + raise ValueError("you can't override a field with a computed field") fields[ann_name] = field_info - if field_info._complete: - # If not complete, this will be called in `rebuild_model_fields()`: - update_field_from_config(config_wrapper, ann_name, field_info) - if typevars_map: for field in fields.values(): - if field._complete: - field.apply_typevars_map(typevars_map) + field.apply_typevars_map(typevars_map, types_namespace) - if config_wrapper.use_attribute_docstrings: - _update_fields_from_docstrings(cls, fields) return fields, class_vars -def rebuild_model_fields( - cls: type[BaseModel], - *, - config_wrapper: ConfigWrapper, - ns_resolver: NsResolver, - typevars_map: Mapping[TypeVar, Any], -) -> dict[str, FieldInfo]: - """Rebuild the (already present) model fields by trying to reevaluate annotations. +def _is_finalvar_with_default_val(type_: type[Any], val: Any) -> bool: + from ..fields import FieldInfo - This function should be called whenever a model with incomplete fields is encountered. - - Raises: - NameError: If one of the annotations failed to evaluate. - - Note: - This function *doesn't* mutate the model fields in place, as it can be called during - schema generation, where you don't want to mutate other model's fields. - """ - FieldInfo_ = import_cached_field_info() - - rebuilt_fields: dict[str, FieldInfo] = {} - with ns_resolver.push(cls): - for f_name, field_info in cls.__pydantic_fields__.items(): - if field_info._complete: - rebuilt_fields[f_name] = field_info - else: - existing_desc = field_info.description - ann = _typing_extra.eval_type( - field_info._original_annotation, - *ns_resolver.types_namespace, - ) - ann = _generics.replace_types(ann, typevars_map) - - if (assign := field_info._original_assignment) is PydanticUndefined: - new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS) - else: - new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS) - # The description might come from the docstring if `use_attribute_docstrings` was `True`: - new_field.description = new_field.description if new_field.description is not None else existing_desc - update_field_from_config(config_wrapper, f_name, new_field) - rebuilt_fields[f_name] = new_field - - return rebuilt_fields + if not is_finalvar(type_): + return False + elif val is PydanticUndefined: + return False + elif isinstance(val, FieldInfo) and (val.default is PydanticUndefined and val.default_factory is None): + return False + else: + return True def collect_dataclass_fields( - cls: type[StandardDataclass], - *, - config_wrapper: ConfigWrapper, - ns_resolver: NsResolver | None = None, - typevars_map: dict[Any, Any] | None = None, + cls: type[StandardDataclass], types_namespace: dict[str, Any] | None, *, typevars_map: dict[Any, Any] | None = None ) -> dict[str, FieldInfo]: """Collect the fields of a dataclass. Args: cls: dataclass. - config_wrapper: The config wrapper instance. - ns_resolver: Namespace resolver to use when getting dataclass annotations. - Defaults to an empty instance. + types_namespace: Optional extra namespace to look for types in. typevars_map: A dictionary mapping type variables to their concrete types. Returns: The dataclass fields. """ - FieldInfo_ = import_cached_field_info() + from ..fields import FieldInfo fields: dict[str, FieldInfo] = {} - ns_resolver = ns_resolver or NsResolver() - dataclass_fields = cls.__dataclass_fields__ + dataclass_fields: dict[str, dataclasses.Field] = cls.__dataclass_fields__ + cls_localns = dict(vars(cls)) # this matches get_cls_type_hints_lenient, but all tests pass with `= None` instead - # The logic here is similar to `_typing_extra.get_cls_type_hints`, - # although we do it manually as stdlib dataclasses already have annotations - # collected in each class: - for base in reversed(cls.__mro__): - if not dataclasses.is_dataclass(base): + for ann_name, dataclass_field in dataclass_fields.items(): + ann_type = _typing_extra.eval_type_lenient(dataclass_field.type, types_namespace, cls_localns) + if is_classvar(ann_type): continue - with ns_resolver.push(base): - for ann_name, dataclass_field in dataclass_fields.items(): - base_anns = _typing_extra.safe_get_annotations(base) + if ( + not dataclass_field.init + and dataclass_field.default == dataclasses.MISSING + and dataclass_field.default_factory == dataclasses.MISSING + ): + # TODO: We should probably do something with this so that validate_assignment behaves properly + # Issue: https://github.com/pydantic/pydantic/issues/5470 + continue - if ann_name not in base_anns: - # `__dataclass_fields__`contains every field, even the ones from base classes. - # Only collect the ones defined on `base`. - continue + if isinstance(dataclass_field.default, FieldInfo): + if dataclass_field.default.init_var: + # TODO: same note as above + continue + field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field.default) + else: + field_info = FieldInfo.from_annotated_attribute(ann_type, dataclass_field) + fields[ann_name] = field_info - globalns, localns = ns_resolver.types_namespace - ann_type, evaluated = _typing_extra.try_eval_type(dataclass_field.type, globalns, localns) - - if _typing_extra.is_classvar_annotation(ann_type): - continue - - if ( - not dataclass_field.init - and dataclass_field.default is dataclasses.MISSING - and dataclass_field.default_factory is dataclasses.MISSING - ): - # TODO: We should probably do something with this so that validate_assignment behaves properly - # Issue: https://github.com/pydantic/pydantic/issues/5470 - continue - - if isinstance(dataclass_field.default, FieldInfo_): - if dataclass_field.default.init_var: - if dataclass_field.default.init is False: - raise PydanticUserError( - f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.', - code='clashing-init-and-init-var', - ) - - # TODO: same note as above re validate_assignment - continue - field_info = FieldInfo_.from_annotated_attribute( - ann_type, dataclass_field.default, _source=AnnotationSource.DATACLASS - ) - field_info._original_assignment = dataclass_field.default - else: - field_info = FieldInfo_.from_annotated_attribute( - ann_type, dataclass_field, _source=AnnotationSource.DATACLASS - ) - field_info._original_assignment = dataclass_field - - if not evaluated: - field_info._complete = False - field_info._original_annotation = ann_type - - fields[ann_name] = field_info - update_field_from_config(config_wrapper, ann_name, field_info) - - if field_info.default is not PydanticUndefined and isinstance( - getattr(cls, ann_name, field_info), FieldInfo_ - ): - # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo - setattr(cls, ann_name, field_info.default) + if field_info.default is not PydanticUndefined and isinstance(getattr(cls, ann_name, field_info), FieldInfo): + # We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo + setattr(cls, ann_name, field_info.default) if typevars_map: for field in fields.values(): - # We don't pass any ns, as `field.annotation` - # was already evaluated. TODO: is this method relevant? - # Can't we juste use `_generics.replace_types`? - field.apply_typevars_map(typevars_map) - - if config_wrapper.use_attribute_docstrings: - _update_fields_from_docstrings( - cls, - fields, - # We can't rely on the (more reliable) frame inspection method - # for stdlib dataclasses: - use_inspect=not hasattr(cls, '__is_pydantic_dataclass__'), - ) + field.apply_typevars_map(typevars_map, types_namespace) return fields -def rebuild_dataclass_fields( - cls: type[PydanticDataclass], - *, - config_wrapper: ConfigWrapper, - ns_resolver: NsResolver, - typevars_map: Mapping[TypeVar, Any], -) -> dict[str, FieldInfo]: - """Rebuild the (already present) dataclass fields by trying to reevaluate annotations. - - This function should be called whenever a dataclass with incomplete fields is encountered. - - Raises: - NameError: If one of the annotations failed to evaluate. - - Note: - This function *doesn't* mutate the dataclass fields in place, as it can be called during - schema generation, where you don't want to mutate other dataclass's fields. - """ - FieldInfo_ = import_cached_field_info() - - rebuilt_fields: dict[str, FieldInfo] = {} - with ns_resolver.push(cls): - for f_name, field_info in cls.__pydantic_fields__.items(): - if field_info._complete: - rebuilt_fields[f_name] = field_info - else: - existing_desc = field_info.description - ann = _typing_extra.eval_type( - field_info._original_annotation, - *ns_resolver.types_namespace, - ) - ann = _generics.replace_types(ann, typevars_map) - new_field = FieldInfo_.from_annotated_attribute( - ann, - field_info._original_assignment, - _source=AnnotationSource.DATACLASS, - ) - - # The description might come from the docstring if `use_attribute_docstrings` was `True`: - new_field.description = new_field.description if new_field.description is not None else existing_desc - update_field_from_config(config_wrapper, f_name, new_field) - rebuilt_fields[f_name] = new_field - - return rebuilt_fields - - def is_valid_field_name(name: str) -> bool: return not name.startswith('_') def is_valid_privateattr_name(name: str) -> bool: return name.startswith('_') and not name.startswith('__') - - -def takes_validated_data_argument( - default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any], -) -> TypeIs[Callable[[dict[str, Any]], Any]]: - """Whether the provided default factory callable has a validated data parameter.""" - try: - sig = signature(default_factory) - except (ValueError, TypeError): - # `inspect.signature` might not be able to infer a signature, e.g. with C objects. - # In this case, we assume no data argument is present: - return False - - parameters = list(sig.parameters.values()) - - return len(parameters) == 1 and can_be_positional(parameters[0]) and parameters[0].default is Parameter.empty diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py index 839764ce..e87e256f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py @@ -1,80 +1,60 @@ """Convert python types to pydantic-core schema.""" - from __future__ import annotations as _annotations import collections.abc import dataclasses -import datetime import inspect -import os -import pathlib import re import sys import typing import warnings -from collections.abc import Generator, Iterable, Iterator, Mapping from contextlib import contextmanager -from copy import copy -from decimal import Decimal +from copy import copy, deepcopy from enum import Enum -from fractions import Fraction from functools import partial from inspect import Parameter, _ParameterKind, signature -from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network from itertools import chain from operator import attrgetter -from types import FunctionType, GenericAlias, LambdaType, MethodType +from types import FunctionType, LambdaType, MethodType from typing import ( TYPE_CHECKING, Any, Callable, - Final, + Dict, ForwardRef, - Literal, + Iterable, + Iterator, + Mapping, + Type, TypeVar, Union, cast, overload, ) -from uuid import UUID -from zoneinfo import ZoneInfo +from warnings import warn -import typing_extensions -from pydantic_core import ( - MISSING, - CoreSchema, - MultiHostUrl, - PydanticCustomError, - PydanticSerializationUnexpectedValue, - PydanticUndefined, - Url, - core_schema, - to_jsonable_python, -) -from typing_extensions import TypeAlias, TypeAliasType, get_args, get_origin, is_typeddict -from typing_inspection import typing_objects -from typing_inspection.introspection import AnnotationSource, get_literal_values, is_union_origin +from pydantic_core import CoreSchema, PydanticUndefined, core_schema, to_jsonable_python +from typing_extensions import Annotated, Final, Literal, TypeAliasType, TypedDict, get_args, get_origin, is_typeddict -from ..aliases import AliasChoices, AliasPath from ..annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler -from ..config import ConfigDict, JsonDict, JsonEncoder, JsonSchemaExtraCallable +from ..config import ConfigDict, JsonDict, JsonEncoder from ..errors import PydanticSchemaGenerationError, PydanticUndefinedAnnotation, PydanticUserError -from ..functional_validators import AfterValidator, BeforeValidator, FieldValidatorModes, PlainValidator, WrapValidator from ..json_schema import JsonSchemaValue from ..version import version_short -from ..warnings import ( - ArbitraryTypeWarning, - PydanticDeprecatedSince20, - TypedDictExtraConfigWarning, - UnsupportedFieldAttributeWarning, -) -from . import _decorators, _discriminated_union, _known_annotated_metadata, _repr, _typing_extra +from ..warnings import PydanticDeprecatedSince20 +from . import _core_utils, _decorators, _discriminated_union, _known_annotated_metadata, _typing_extra from ._config import ConfigWrapper, ConfigWrapperStack -from ._core_metadata import CoreMetadata, update_core_metadata +from ._core_metadata import CoreMetadataHandler, build_metadata_dict from ._core_utils import ( + NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY, + CoreSchemaOrField, + collect_invalid_schemas, + define_expected_missing_refs, get_ref, get_type_ref, is_list_like_schema_with_items_schema, + simplify_schema_references, + validate_core_schema, ) from ._decorators import ( Decorator, @@ -90,31 +70,25 @@ from ._decorators import ( inspect_model_serializer, inspect_validator, ) -from ._docs_extraction import extract_docstrings_from_cls -from ._fields import ( - collect_dataclass_fields, - rebuild_dataclass_fields, - rebuild_model_fields, - takes_validated_data_argument, - update_field_from_config, -) +from ._fields import collect_dataclass_fields, get_type_hints_infer_globalns from ._forward_ref import PydanticRecursiveRef -from ._generics import get_standard_typevars_map, replace_types -from ._import_utils import import_cached_base_model, import_cached_field_info -from ._mock_val_ser import MockCoreSchema -from ._namespace_utils import NamespacesTuple, NsResolver -from ._schema_gather import MissingDefinitionError, gather_schemas_for_cleaning -from ._schema_generation_shared import CallbackGetCoreSchemaHandler -from ._utils import lenient_issubclass, smart_deepcopy +from ._generics import get_standard_typevars_map, has_instance_in_type, recursively_defined_type_refs, replace_types +from ._schema_generation_shared import ( + CallbackGetCoreSchemaHandler, +) +from ._typing_extra import is_finalvar +from ._utils import is_valid_identifier, lenient_issubclass if TYPE_CHECKING: from ..fields import ComputedFieldInfo, FieldInfo from ..main import BaseModel from ..types import Discriminator + from ..validators import FieldValidatorModes from ._dataclasses import StandardDataclass from ._schema_generation_shared import GetJsonSchemaFunction _SUPPORTS_TYPEDDICT = sys.version_info >= (3, 12) +_AnnotatedType = type(Annotated[int, 123]) FieldDecoratorInfo = Union[ValidatorDecoratorInfo, FieldValidatorDecoratorInfo, FieldSerializerDecoratorInfo] FieldDecoratorInfoType = TypeVar('FieldDecoratorInfoType', bound=FieldDecoratorInfo) @@ -124,73 +98,15 @@ AnyFieldDecorator = Union[ Decorator[FieldSerializerDecoratorInfo], ] -ModifyCoreSchemaWrapHandler: TypeAlias = GetCoreSchemaHandler -GetCoreSchemaFunction: TypeAlias = Callable[[Any, ModifyCoreSchemaWrapHandler], core_schema.CoreSchema] -ParametersCallback: TypeAlias = "Callable[[int, str, Any], Literal['skip'] | None]" +ModifyCoreSchemaWrapHandler = GetCoreSchemaHandler +GetCoreSchemaFunction = Callable[[Any, ModifyCoreSchemaWrapHandler], core_schema.CoreSchema] -TUPLE_TYPES: list[type] = [typing.Tuple, tuple] # noqa: UP006 -LIST_TYPES: list[type] = [typing.List, list, collections.abc.MutableSequence] # noqa: UP006 -SET_TYPES: list[type] = [typing.Set, set, collections.abc.MutableSet] # noqa: UP006 -FROZEN_SET_TYPES: list[type] = [typing.FrozenSet, frozenset, collections.abc.Set] # noqa: UP006 -DICT_TYPES: list[type] = [typing.Dict, dict] # noqa: UP006 -IP_TYPES: list[type] = [IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network] -SEQUENCE_TYPES: list[type] = [typing.Sequence, collections.abc.Sequence] -ITERABLE_TYPES: list[type] = [typing.Iterable, collections.abc.Iterable, typing.Generator, collections.abc.Generator] -TYPE_TYPES: list[type] = [typing.Type, type] # noqa: UP006 -PATTERN_TYPES: list[type] = [typing.Pattern, re.Pattern] -PATH_TYPES: list[type] = [ - os.PathLike, - pathlib.Path, - pathlib.PurePath, - pathlib.PosixPath, - pathlib.PurePosixPath, - pathlib.PureWindowsPath, -] -MAPPING_TYPES = [ - typing.Mapping, - typing.MutableMapping, - collections.abc.Mapping, - collections.abc.MutableMapping, - collections.OrderedDict, - typing_extensions.OrderedDict, - typing.DefaultDict, # noqa: UP006 - collections.defaultdict, -] -COUNTER_TYPES = [collections.Counter, typing.Counter] -DEQUE_TYPES: list[type] = [collections.deque, typing.Deque] # noqa: UP006 -# Note: This does not play very well with type checkers. For example, -# `a: LambdaType = lambda x: x` will raise a type error by Pyright. -ValidateCallSupportedTypes = Union[ - LambdaType, - FunctionType, - MethodType, - partial, -] - -VALIDATE_CALL_SUPPORTED_TYPES = get_args(ValidateCallSupportedTypes) -UNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES: list[tuple[str, Any]] = [ - ('alias', None), - ('validation_alias', None), - ('serialization_alias', None), - # will be set if any alias is set, so disable it to avoid double warnings: - # 'alias_priority', - ('default', PydanticUndefined), - ('default_factory', None), - ('exclude', None), - ('deprecated', None), - ('repr', True), - ('validate_default', None), - ('frozen', None), - ('init', None), - ('init_var', None), - ('kw_only', None), -] -"""`FieldInfo` attributes (and their default value) that can't be used outside of a model (e.g. in a type adapter or a PEP 695 type alias).""" - -_mode_to_validator: dict[ - FieldValidatorModes, type[BeforeValidator | AfterValidator | PlainValidator | WrapValidator] -] = {'before': BeforeValidator, 'after': AfterValidator, 'plain': PlainValidator, 'wrap': WrapValidator} +TUPLE_TYPES: list[type] = [tuple, typing.Tuple] +LIST_TYPES: list[type] = [list, typing.List, collections.abc.MutableSequence] +SET_TYPES: list[type] = [set, typing.Set, collections.abc.MutableSet] +FROZEN_SET_TYPES: list[type] = [frozenset, typing.FrozenSet, collections.abc.Set] +DICT_TYPES: list[type] = [dict, typing.Dict, collections.abc.MutableMapping, collections.abc.Mapping] def check_validator_fields_against_field_name( @@ -206,8 +122,13 @@ def check_validator_fields_against_field_name( Returns: `True` if field name is in validator fields, `False` otherwise. """ - fields = info.fields - return '*' in fields or field in fields + if isinstance(info, (ValidatorDecoratorInfo, FieldValidatorDecoratorInfo)): + if '*' in info.fields: + return True + for v_field_name in info.fields: + if v_field_name == field: + return True + return False def check_decorator_fields_exist(decorators: Iterable[AnyFieldDecorator], fields: Iterable[str]) -> None: @@ -224,7 +145,7 @@ def check_decorator_fields_exist(decorators: Iterable[AnyFieldDecorator], fields """ fields = set(fields) for dec in decorators: - if '*' in dec.info.fields: + if isinstance(dec.info, (ValidatorDecoratorInfo, FieldValidatorDecoratorInfo)) and '*' in dec.info.fields: continue if dec.info.check_fields is False: continue @@ -246,52 +167,64 @@ def filter_field_decorator_info_by_field( def apply_each_item_validators( schema: core_schema.CoreSchema, each_item_validators: list[Decorator[ValidatorDecoratorInfo]], + field_name: str | None, ) -> core_schema.CoreSchema: # This V1 compatibility shim should eventually be removed - # fail early if each_item_validators is empty - if not each_item_validators: - return schema - # push down any `each_item=True` validators # note that this won't work for any Annotated types that get wrapped by a function validator # but that's okay because that didn't exist in V1 if schema['type'] == 'nullable': - schema['schema'] = apply_each_item_validators(schema['schema'], each_item_validators) + schema['schema'] = apply_each_item_validators(schema['schema'], each_item_validators, field_name) return schema - elif schema['type'] == 'tuple': - if (variadic_item_index := schema.get('variadic_item_index')) is not None: - schema['items_schema'][variadic_item_index] = apply_validators( - schema['items_schema'][variadic_item_index], - each_item_validators, - ) elif is_list_like_schema_with_items_schema(schema): - inner_schema = schema.get('items_schema', core_schema.any_schema()) - schema['items_schema'] = apply_validators(inner_schema, each_item_validators) + inner_schema = schema.get('items_schema', None) + if inner_schema is None: + inner_schema = core_schema.any_schema() + schema['items_schema'] = apply_validators(inner_schema, each_item_validators, field_name) elif schema['type'] == 'dict': - inner_schema = schema.get('values_schema', core_schema.any_schema()) - schema['values_schema'] = apply_validators(inner_schema, each_item_validators) - else: + # push down any `each_item=True` validators onto dict _values_ + # this is super arbitrary but it's the V1 behavior + inner_schema = schema.get('values_schema', None) + if inner_schema is None: + inner_schema = core_schema.any_schema() + schema['values_schema'] = apply_validators(inner_schema, each_item_validators, field_name) + elif each_item_validators: raise TypeError( - f'`@validator(..., each_item=True)` cannot be applied to fields with a schema of {schema["type"]}' + f"`@validator(..., each_item=True)` cannot be applied to fields with a schema of {schema['type']}" ) return schema -def _extract_json_schema_info_from_field_info( - info: FieldInfo | ComputedFieldInfo, -) -> tuple[JsonDict | None, JsonDict | JsonSchemaExtraCallable | None]: - json_schema_updates = { - 'title': info.title, - 'description': info.description, - 'deprecated': bool(info.deprecated) or info.deprecated == '' or None, - 'examples': to_jsonable_python(info.examples), - } - json_schema_updates = {k: v for k, v in json_schema_updates.items() if v is not None} - return (json_schema_updates or None, info.json_schema_extra) +def modify_model_json_schema( + schema_or_field: CoreSchemaOrField, handler: GetJsonSchemaHandler, *, cls: Any +) -> JsonSchemaValue: + """Add title and description for model-like classes' JSON schema. + + Args: + schema_or_field: The schema data to generate a JSON schema from. + handler: The `GetCoreSchemaHandler` instance. + cls: The model-like class. + + Returns: + JsonSchemaValue: The updated JSON schema. + """ + json_schema = handler(schema_or_field) + original_schema = handler.resolve_ref_schema(json_schema) + # Preserve the fact that definitions schemas should never have sibling keys: + if '$ref' in original_schema: + ref = original_schema['$ref'] + original_schema.clear() + original_schema['allOf'] = [{'$ref': ref}] + if 'title' not in original_schema: + original_schema['title'] = cls.__name__ + docstring = cls.__doc__ + if docstring and 'description' not in original_schema: + original_schema['description'] = inspect.cleandoc(docstring) + return json_schema -JsonEncoders = dict[type[Any], JsonEncoder] +JsonEncoders = Dict[Type[Any], JsonEncoder] def _add_custom_serialization_from_json_encoders( @@ -328,329 +261,103 @@ def _add_custom_serialization_from_json_encoders( return schema -class InvalidSchemaError(Exception): - """The core schema is invalid.""" - - class GenerateSchema: """Generate core schema for a Pydantic model, dataclass and types like `str`, `datetime`, ... .""" __slots__ = ( '_config_wrapper_stack', - '_ns_resolver', + '_types_namespace', '_typevars_map', + '_needs_apply_discriminated_union', + '_has_invalid_schema', 'field_name_stack', - 'model_type_stack', 'defs', ) def __init__( self, config_wrapper: ConfigWrapper, - ns_resolver: NsResolver | None = None, - typevars_map: Mapping[TypeVar, Any] | None = None, + types_namespace: dict[str, Any] | None, + typevars_map: dict[Any, Any] | None = None, ) -> None: - # we need a stack for recursing into nested models + # we need a stack for recursing into child models self._config_wrapper_stack = ConfigWrapperStack(config_wrapper) - self._ns_resolver = ns_resolver or NsResolver() + self._types_namespace = types_namespace self._typevars_map = typevars_map + self._needs_apply_discriminated_union = False + self._has_invalid_schema = False self.field_name_stack = _FieldNameStack() - self.model_type_stack = _ModelTypeStack() self.defs = _Definitions() - def __init_subclass__(cls) -> None: - super().__init_subclass__() - warnings.warn( - 'Subclassing `GenerateSchema` is not supported. The API is highly subject to change in minor versions.', - UserWarning, - stacklevel=2, - ) + @classmethod + def __from_parent( + cls, + config_wrapper_stack: ConfigWrapperStack, + types_namespace: dict[str, Any] | None, + typevars_map: dict[Any, Any] | None, + defs: _Definitions, + ) -> GenerateSchema: + obj = cls.__new__(cls) + obj._config_wrapper_stack = config_wrapper_stack + obj._types_namespace = types_namespace + obj._typevars_map = typevars_map + obj._needs_apply_discriminated_union = False + obj._has_invalid_schema = False + obj.field_name_stack = _FieldNameStack() + obj.defs = defs + return obj @property def _config_wrapper(self) -> ConfigWrapper: return self._config_wrapper_stack.tail @property - def _types_namespace(self) -> NamespacesTuple: - return self._ns_resolver.types_namespace + def _current_generate_schema(self) -> GenerateSchema: + cls = self._config_wrapper.schema_generator or GenerateSchema + return cls.__from_parent( + self._config_wrapper_stack, + self._types_namespace, + self._typevars_map, + self.defs, + ) @property def _arbitrary_types(self) -> bool: return self._config_wrapper.arbitrary_types_allowed + def str_schema(self) -> CoreSchema: + """Generate a CoreSchema for `str`""" + return core_schema.str_schema() + # the following methods can be overridden but should be considered # unstable / private APIs - def _list_schema(self, items_type: Any) -> CoreSchema: + def _list_schema(self, tp: Any, items_type: Any) -> CoreSchema: return core_schema.list_schema(self.generate_schema(items_type)) - def _dict_schema(self, keys_type: Any, values_type: Any) -> CoreSchema: + def _dict_schema(self, tp: Any, keys_type: Any, values_type: Any) -> CoreSchema: return core_schema.dict_schema(self.generate_schema(keys_type), self.generate_schema(values_type)) - def _set_schema(self, items_type: Any) -> CoreSchema: + def _set_schema(self, tp: Any, items_type: Any) -> CoreSchema: return core_schema.set_schema(self.generate_schema(items_type)) - def _frozenset_schema(self, items_type: Any) -> CoreSchema: + def _frozenset_schema(self, tp: Any, items_type: Any) -> CoreSchema: return core_schema.frozenset_schema(self.generate_schema(items_type)) - def _enum_schema(self, enum_type: type[Enum]) -> CoreSchema: - cases: list[Any] = list(enum_type.__members__.values()) + def _tuple_variable_schema(self, tp: Any, items_type: Any) -> CoreSchema: + return core_schema.tuple_variable_schema(self.generate_schema(items_type)) - enum_ref = get_type_ref(enum_type) - description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) - if ( - description == 'An enumeration.' - ): # This is the default value provided by enum.EnumMeta.__new__; don't use it - description = None - js_updates = {'title': enum_type.__name__, 'description': description} - js_updates = {k: v for k, v in js_updates.items() if v is not None} - - sub_type: Literal['str', 'int', 'float'] | None = None - if issubclass(enum_type, int): - sub_type = 'int' - value_ser_type: core_schema.SerSchema = core_schema.simple_ser_schema('int') - elif issubclass(enum_type, str): - # this handles `StrEnum` (3.11 only), and also `Foobar(str, Enum)` - sub_type = 'str' - value_ser_type = core_schema.simple_ser_schema('str') - elif issubclass(enum_type, float): - sub_type = 'float' - value_ser_type = core_schema.simple_ser_schema('float') - else: - # TODO this is an ugly hack, how do we trigger an Any schema for serialization? - value_ser_type = core_schema.plain_serializer_function_ser_schema(lambda x: x) - - if cases: - - def get_json_schema(schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: - json_schema = handler(schema) - original_schema = handler.resolve_ref_schema(json_schema) - original_schema.update(js_updates) - return json_schema - - # we don't want to add the missing to the schema if it's the default one - default_missing = getattr(enum_type._missing_, '__func__', None) is Enum._missing_.__func__ # pyright: ignore[reportFunctionMemberAccess] - enum_schema = core_schema.enum_schema( - enum_type, - cases, - sub_type=sub_type, - missing=None if default_missing else enum_type._missing_, - ref=enum_ref, - metadata={'pydantic_js_functions': [get_json_schema]}, - ) - - if self._config_wrapper.use_enum_values: - enum_schema = core_schema.no_info_after_validator_function( - attrgetter('value'), enum_schema, serialization=value_ser_type - ) - - return enum_schema - - else: - - def get_json_schema_no_cases(_, handler: GetJsonSchemaHandler) -> JsonSchemaValue: - json_schema = handler(core_schema.enum_schema(enum_type, cases, sub_type=sub_type, ref=enum_ref)) - original_schema = handler.resolve_ref_schema(json_schema) - original_schema.update(js_updates) - return json_schema - - # Use an isinstance check for enums with no cases. - # The most important use case for this is creating TypeVar bounds for generics that should - # be restricted to enums. This is more consistent than it might seem at first, since you can only - # subclass enum.Enum (or subclasses of enum.Enum) if all parent classes have no cases. - # We use the get_json_schema function when an Enum subclass has been declared with no cases - # so that we can still generate a valid json schema. - return core_schema.is_instance_schema( - enum_type, - metadata={'pydantic_js_functions': [get_json_schema_no_cases]}, - ) - - def _ip_schema(self, tp: Any) -> CoreSchema: - from ._validators import IP_VALIDATOR_LOOKUP, IpType - - ip_type_json_schema_format: dict[type[IpType], str] = { - IPv4Address: 'ipv4', - IPv4Network: 'ipv4network', - IPv4Interface: 'ipv4interface', - IPv6Address: 'ipv6', - IPv6Network: 'ipv6network', - IPv6Interface: 'ipv6interface', - } - - def ser_ip(ip: Any, info: core_schema.SerializationInfo) -> str | IpType: - if not isinstance(ip, (tp, str)): - raise PydanticSerializationUnexpectedValue( - f"Expected `{tp}` but got `{type(ip)}` with value `'{ip}'` - serialized value may not be as expected." - ) - if info.mode == 'python': - return ip - return str(ip) - - return core_schema.lax_or_strict_schema( - lax_schema=core_schema.no_info_plain_validator_function(IP_VALIDATOR_LOOKUP[tp]), - strict_schema=core_schema.json_or_python_schema( - json_schema=core_schema.no_info_after_validator_function(tp, core_schema.str_schema()), - python_schema=core_schema.is_instance_schema(tp), - ), - serialization=core_schema.plain_serializer_function_ser_schema(ser_ip, info_arg=True, when_used='always'), - metadata={ - 'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': ip_type_json_schema_format[tp]}] - }, - ) - - def _path_schema(self, tp: Any, path_type: Any) -> CoreSchema: - if tp is os.PathLike and (path_type not in {str, bytes} and not typing_objects.is_any(path_type)): - raise PydanticUserError( - '`os.PathLike` can only be used with `str`, `bytes` or `Any`', code='schema-for-unknown-type' - ) - - path_constructor = pathlib.PurePath if tp is os.PathLike else tp - strict_inner_schema = ( - core_schema.bytes_schema(strict=True) if (path_type is bytes) else core_schema.str_schema(strict=True) - ) - lax_inner_schema = core_schema.bytes_schema() if (path_type is bytes) else core_schema.str_schema() - - def path_validator(input_value: str | bytes) -> os.PathLike[Any]: # type: ignore - try: - if path_type is bytes: - if isinstance(input_value, bytes): - try: - input_value = input_value.decode() - except UnicodeDecodeError as e: - raise PydanticCustomError('bytes_type', 'Input must be valid bytes') from e - else: - raise PydanticCustomError('bytes_type', 'Input must be bytes') - elif not isinstance(input_value, str): - raise PydanticCustomError('path_type', 'Input is not a valid path') - - return path_constructor(input_value) # type: ignore - except TypeError as e: - raise PydanticCustomError('path_type', 'Input is not a valid path') from e - - def ser_path(path: Any, info: core_schema.SerializationInfo) -> str | os.PathLike[Any]: - if not isinstance(path, (tp, str)): - raise PydanticSerializationUnexpectedValue( - f"Expected `{tp}` but got `{type(path)}` with value `'{path}'` - serialized value may not be as expected." - ) - if info.mode == 'python': - return path - return str(path) - - instance_schema = core_schema.json_or_python_schema( - json_schema=core_schema.no_info_after_validator_function(path_validator, lax_inner_schema), - python_schema=core_schema.is_instance_schema(tp), - ) - - schema = core_schema.lax_or_strict_schema( - lax_schema=core_schema.union_schema( - [ - instance_schema, - core_schema.no_info_after_validator_function(path_validator, strict_inner_schema), - ], - custom_error_type='path_type', - custom_error_message=f'Input is not a valid path for {tp}', - ), - strict_schema=instance_schema, - serialization=core_schema.plain_serializer_function_ser_schema(ser_path, info_arg=True, when_used='always'), - metadata={'pydantic_js_functions': [lambda source, handler: {**handler(source), 'format': 'path'}]}, - ) - return schema - - def _deque_schema(self, items_type: Any) -> CoreSchema: - from ._serializers import serialize_sequence_via_list - from ._validators import deque_validator - - item_type_schema = self.generate_schema(items_type) - - # we have to use a lax list schema here, because we need to validate the deque's - # items via a list schema, but it's ok if the deque itself is not a list - list_schema = core_schema.list_schema(item_type_schema, strict=False) - - check_instance = core_schema.json_or_python_schema( - json_schema=list_schema, - python_schema=core_schema.is_instance_schema(collections.deque, cls_repr='Deque'), - ) - - lax_schema = core_schema.no_info_wrap_validator_function(deque_validator, list_schema) - - return core_schema.lax_or_strict_schema( - lax_schema=lax_schema, - strict_schema=core_schema.chain_schema([check_instance, lax_schema]), - serialization=core_schema.wrap_serializer_function_ser_schema( - serialize_sequence_via_list, schema=item_type_schema, info_arg=True - ), - ) - - def _mapping_schema(self, tp: Any, keys_type: Any, values_type: Any) -> CoreSchema: - from ._validators import MAPPING_ORIGIN_MAP, defaultdict_validator, get_defaultdict_default_default_factory - - mapped_origin = MAPPING_ORIGIN_MAP[tp] - keys_schema = self.generate_schema(keys_type) - with warnings.catch_warnings(): - # We kind of abused `Field()` default factories to be able to specify - # the `defaultdict`'s `default_factory`. As a consequence, we get warnings - # as normally `FieldInfo.default_factory` is unsupported in the context where - # `Field()` is used and our only solution is to ignore them (note that this might - # wrongfully ignore valid warnings, e.g. if the `value_type` is a PEP 695 type alias - # with unsupported metadata). - warnings.simplefilter('ignore', category=UnsupportedFieldAttributeWarning) - values_schema = self.generate_schema(values_type) - dict_schema = core_schema.dict_schema(keys_schema, values_schema, strict=False) - - if mapped_origin is dict: - schema = dict_schema - else: - check_instance = core_schema.json_or_python_schema( - json_schema=dict_schema, - python_schema=core_schema.is_instance_schema(mapped_origin), - ) - - if tp is collections.defaultdict: - default_default_factory = get_defaultdict_default_default_factory(values_type) - coerce_instance_wrap = partial( - core_schema.no_info_wrap_validator_function, - partial(defaultdict_validator, default_default_factory=default_default_factory), - ) - else: - coerce_instance_wrap = partial(core_schema.no_info_after_validator_function, mapped_origin) - - lax_schema = coerce_instance_wrap(dict_schema) - strict_schema = core_schema.chain_schema([check_instance, lax_schema]) - - schema = core_schema.lax_or_strict_schema( - lax_schema=lax_schema, - strict_schema=strict_schema, - serialization=core_schema.wrap_serializer_function_ser_schema( - lambda v, h: h(v), schema=dict_schema, info_arg=False - ), - ) - - return schema - - def _fraction_schema(self) -> CoreSchema: - """Support for [`fractions.Fraction`][fractions.Fraction].""" - from ._validators import fraction_validator - - # TODO: note, this is a fairly common pattern, re lax / strict for attempted type coercion, - # can we use a helper function to reduce boilerplate? - return core_schema.lax_or_strict_schema( - lax_schema=core_schema.no_info_plain_validator_function(fraction_validator), - strict_schema=core_schema.json_or_python_schema( - json_schema=core_schema.no_info_plain_validator_function(fraction_validator), - python_schema=core_schema.is_instance_schema(Fraction), - ), - # use str serialization to guarantee round trip behavior - serialization=core_schema.to_string_ser_schema(when_used='always'), - metadata={'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'fraction'}]}, - ) + def _tuple_positional_schema(self, tp: Any, items_types: list[Any]) -> CoreSchema: + items_schemas = [self.generate_schema(items_type) for items_type in items_types] + return core_schema.tuple_positional_schema(items_schemas) def _arbitrary_type_schema(self, tp: Any) -> CoreSchema: if not isinstance(tp, type): - warnings.warn( + warn( f'{tp!r} is not a Python type (it may be an instance of an object),' ' Pydantic will allow any object with no validation since we cannot even' ' enforce that the input is an instance of the given type.' ' To get rid of this error wrap the type with `pydantic.SkipValidation`.', - ArbitraryTypeWarning, + UserWarning, ) return core_schema.any_schema() return core_schema.is_instance_schema(tp) @@ -675,38 +382,65 @@ class GenerateSchema: return _discriminated_union.apply_discriminator( schema, discriminator, - self.defs._definitions, ) except _discriminated_union.MissingDefinitionForUnionRef: # defer until defs are resolved - _discriminated_union.set_discriminator_in_metadata( + _discriminated_union.set_discriminator( schema, discriminator, ) + if 'metadata' in schema: + schema['metadata'][NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY] = True + else: + schema['metadata'] = {NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY: True} + self._needs_apply_discriminated_union = True return schema + class CollectedInvalid(Exception): + pass + def clean_schema(self, schema: CoreSchema) -> CoreSchema: - return self.defs.finalize_schema(schema) + schema = self.collect_definitions(schema) + schema = simplify_schema_references(schema) + schema = _discriminated_union.apply_discriminators(schema) + if collect_invalid_schemas(schema): + raise self.CollectedInvalid() + schema = validate_core_schema(schema) + return schema + + def collect_definitions(self, schema: CoreSchema) -> CoreSchema: + ref = cast('str | None', schema.get('ref', None)) + if ref: + self.defs.definitions[ref] = schema + if 'ref' in schema: + schema = core_schema.definition_reference_schema(schema['ref']) + return core_schema.definitions_schema( + schema, + list(self.defs.definitions.values()), + ) def _add_js_function(self, metadata_schema: CoreSchema, js_function: Callable[..., Any]) -> None: - metadata = metadata_schema.get('metadata', {}) + metadata = CoreMetadataHandler(metadata_schema).metadata pydantic_js_functions = metadata.setdefault('pydantic_js_functions', []) # because of how we generate core schemas for nested generic models # we can end up adding `BaseModel.__get_pydantic_json_schema__` multiple times # this check may fail to catch duplicates if the function is a `functools.partial` - # or something like that, but if it does it'll fail by inserting the duplicate + # or something like that + # but if it does it'll fail by inserting the duplicate if js_function not in pydantic_js_functions: pydantic_js_functions.append(js_function) - metadata_schema['metadata'] = metadata def generate_schema( self, obj: Any, + from_dunder_get_core_schema: bool = True, ) -> core_schema.CoreSchema: """Generate core schema. Args: obj: The object to generate core schema for. + from_dunder_get_core_schema: Whether to generate schema from either the + `__get_pydantic_core_schema__` function or `__pydantic_core_schema__` property. Returns: The generated core schema. @@ -717,139 +451,90 @@ class GenerateSchema: PydanticSchemaGenerationError: If it is not possible to generate pydantic-core schema. TypeError: - - If `alias_generator` returns a disallowed type (must be str, AliasPath or AliasChoices). + - If `alias_generator` returns a non-string value. - If V1 style validator with `each_item=True` applied on a wrong field. PydanticUserError: - If `typing.TypedDict` is used instead of `typing_extensions.TypedDict` on Python < 3.12. - If `__modify_schema__` method is used instead of `__get_pydantic_json_schema__`. """ - schema = self._generate_schema_from_get_schema_method(obj, obj) + schema: CoreSchema | None = None + + if from_dunder_get_core_schema: + from_property = self._generate_schema_from_property(obj, obj) + if from_property is not None: + schema = from_property if schema is None: - schema = self._generate_schema_inner(obj) + schema = self._generate_schema(obj) - metadata_js_function = _extract_get_pydantic_json_schema(obj) + metadata_js_function = _extract_get_pydantic_json_schema(obj, schema) if metadata_js_function is not None: - metadata_schema = resolve_original_schema(schema, self.defs) + metadata_schema = resolve_original_schema(schema, self.defs.definitions) if metadata_schema: self._add_js_function(metadata_schema, metadata_js_function) schema = _add_custom_serialization_from_json_encoders(self._config_wrapper.json_encoders, obj, schema) + schema = self._post_process_generated_schema(schema) + return schema def _model_schema(self, cls: type[BaseModel]) -> core_schema.CoreSchema: """Generate schema for a Pydantic model.""" - BaseModel_ = import_cached_base_model() - with self.defs.get_schema_or_ref(cls) as (model_ref, maybe_schema): if maybe_schema is not None: return maybe_schema - schema = cls.__dict__.get('__pydantic_core_schema__') - if schema is not None and not isinstance(schema, MockCoreSchema): - if schema['type'] == 'definitions': - schema = self.defs.unpack_definitions(schema) - ref = get_ref(schema) - if ref: - return self.defs.create_definition_reference_schema(schema) - else: - return schema - + fields = cls.model_fields + decorators = cls.__pydantic_decorators__ + computed_fields = decorators.computed_fields + check_decorator_fields_exist( + chain( + decorators.field_validators.values(), + decorators.field_serializers.values(), + decorators.validators.values(), + ), + {*fields.keys(), *computed_fields.keys()}, + ) config_wrapper = ConfigWrapper(cls.model_config, check=False) + core_config = config_wrapper.core_config(cls) + metadata = build_metadata_dict(js_functions=[partial(modify_model_json_schema, cls=cls)]) - with self._config_wrapper_stack.push(config_wrapper), self._ns_resolver.push(cls): - core_config = self._config_wrapper.core_config(title=cls.__name__) + model_validators = decorators.model_validators.values() - if cls.__pydantic_fields_complete__ or cls is BaseModel_: - fields = getattr(cls, '__pydantic_fields__', {}) - else: - if '__pydantic_fields__' not in cls.__dict__: - # This happens when we have a loop in the schema generation: - # class Base[T](BaseModel): - # t: T - # - # class Other(BaseModel): - # b: 'Base[Other]' - # When we build fields for `Other`, we evaluate the forward annotation. - # At this point, `Other` doesn't have the model fields set. We create - # `Base[Other]`; model fields are successfully built, and we try to generate - # a schema for `t: Other`. As `Other.__pydantic_fields__` aren't set, we abort. - raise PydanticUndefinedAnnotation( - name=cls.__name__, - message=f'Class {cls.__name__!r} is not defined', - ) - try: - fields = rebuild_model_fields( - cls, - config_wrapper=self._config_wrapper, - ns_resolver=self._ns_resolver, - typevars_map=self._typevars_map or {}, - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - - decorators = cls.__pydantic_decorators__ - computed_fields = decorators.computed_fields - check_decorator_fields_exist( - chain( - decorators.field_validators.values(), - decorators.field_serializers.values(), - decorators.validators.values(), - ), - {*fields.keys(), *computed_fields.keys()}, - ) - - model_validators = decorators.model_validators.values() - - extras_schema = None - extras_keys_schema = None - if core_config.get('extra_fields_behavior') == 'allow': - assert cls.__mro__[0] is cls - assert cls.__mro__[-1] is object - for candidate_cls in cls.__mro__[:-1]: - extras_annotation = getattr(candidate_cls, '__annotations__', {}).get( - '__pydantic_extra__', None - ) - if extras_annotation is not None: - if isinstance(extras_annotation, str): - extras_annotation = _typing_extra.eval_type_backport( - _typing_extra._make_forward_ref( - extras_annotation, is_argument=False, is_class=True - ), - *self._types_namespace, - ) - tp = get_origin(extras_annotation) - if tp not in DICT_TYPES: - raise PydanticSchemaGenerationError( - 'The type annotation for `__pydantic_extra__` must be `dict[str, ...]`' - ) - extra_keys_type, extra_items_type = self._get_args_resolving_forward_refs( - extras_annotation, - required=True, + extras_schema = None + if core_config.get('extra_fields_behavior') == 'allow': + for tp in (cls, *cls.__mro__): + extras_annotation = cls.__annotations__.get('__pydantic_extra__', None) + if extras_annotation is not None: + tp = get_origin(extras_annotation) + if tp not in (Dict, dict): + raise PydanticSchemaGenerationError( + 'The type annotation for `__pydantic_extra__` must be `Dict[str, ...]`' ) - if extra_keys_type is not str: - extras_keys_schema = self.generate_schema(extra_keys_type) - if not typing_objects.is_any(extra_items_type): - extras_schema = self.generate_schema(extra_items_type) - if extras_keys_schema is not None or extras_schema is not None: - break - - generic_origin: type[BaseModel] | None = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin') + extra_items_type = self._get_args_resolving_forward_refs( + cls.__annotations__['__pydantic_extra__'], + required=True, + )[1] + if extra_items_type is not Any: + extras_schema = self.generate_schema(extra_items_type) + break + with self._config_wrapper_stack.push(config_wrapper): + self = self._current_generate_schema if cls.__pydantic_root_model__: - # FIXME: should the common field metadata be used here? - inner_schema, _ = self._common_field_schema('root', fields['root'], decorators) + root_field = self._common_field_schema('root', fields['root'], decorators) + inner_schema = root_field['schema'] inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') model_schema = core_schema.model_schema( cls, inner_schema, - generic_origin=generic_origin, custom_init=getattr(cls, '__pydantic_custom_init__', None), root_model=True, post_init=getattr(cls, '__pydantic_post_init__', None), config=core_config, ref=model_ref, + metadata=metadata, ) else: fields_schema: core_schema.CoreSchema = core_schema.model_fields_schema( @@ -859,91 +544,89 @@ class GenerateSchema: for d in computed_fields.values() ], extras_schema=extras_schema, - extras_keys_schema=extras_keys_schema, model_name=cls.__name__, ) - inner_schema = apply_validators(fields_schema, decorators.root_validators.values()) + inner_schema = apply_validators(fields_schema, decorators.root_validators.values(), None) + new_inner_schema = define_expected_missing_refs(inner_schema, recursively_defined_type_refs()) + if new_inner_schema is not None: + inner_schema = new_inner_schema inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') model_schema = core_schema.model_schema( cls, inner_schema, - generic_origin=generic_origin, custom_init=getattr(cls, '__pydantic_custom_init__', None), root_model=False, post_init=getattr(cls, '__pydantic_post_init__', None), config=core_config, ref=model_ref, + metadata=metadata, ) schema = self._apply_model_serializers(model_schema, decorators.model_serializers.values()) schema = apply_model_validators(schema, model_validators, 'outer') - return self.defs.create_definition_reference_schema(schema) + self.defs.definitions[model_ref] = self._post_process_generated_schema(schema) + return core_schema.definition_reference_schema(model_ref) - def _resolve_self_type(self, obj: Any) -> Any: - obj = self.model_type_stack.get() - if obj is None: - raise PydanticUserError('`typing.Self` is invalid in this context', code='invalid-self-type') - return obj + def _unpack_refs_defs(self, schema: CoreSchema) -> CoreSchema: + """Unpack all 'definitions' schemas into `GenerateSchema.defs.definitions` + and return the inner schema. + """ - def _generate_schema_from_get_schema_method(self, obj: Any, source: Any) -> core_schema.CoreSchema | None: - BaseModel_ = import_cached_base_model() + def get_ref(s: CoreSchema) -> str: + return s['ref'] # type: ignore + if schema['type'] == 'definitions': + self.defs.definitions.update({get_ref(s): s for s in schema['definitions']}) + schema = schema['schema'] + return schema + + def _generate_schema_from_property(self, obj: Any, source: Any) -> core_schema.CoreSchema | None: + """Try to generate schema from either the `__get_pydantic_core_schema__` function or + `__pydantic_core_schema__` property. + + Note: `__get_pydantic_core_schema__` takes priority so it can + decide whether to use a `__pydantic_core_schema__` attribute, or generate a fresh schema. + """ + # avoid calling `__get_pydantic_core_schema__` if we've already visited this object + with self.defs.get_schema_or_ref(obj) as (_, maybe_schema): + if maybe_schema is not None: + return maybe_schema + if obj is source: + ref_mode = 'unpack' + else: + ref_mode = 'to-def' + + schema: CoreSchema get_schema = getattr(obj, '__get_pydantic_core_schema__', None) - is_base_model_get_schema = ( - getattr(get_schema, '__func__', None) is BaseModel_.__get_pydantic_core_schema__.__func__ # pyright: ignore[reportFunctionMemberAccess] - ) - - if ( - get_schema is not None - # BaseModel.__get_pydantic_core_schema__ is defined for backwards compatibility, - # to allow existing code to call `super().__get_pydantic_core_schema__` in Pydantic - # model that overrides `__get_pydantic_core_schema__`. However, it raises a deprecation - # warning stating that the method will be removed, and during the core schema gen we actually - # don't call the method: - and not is_base_model_get_schema - ): - # Some referenceable types might have a `__get_pydantic_core_schema__` method - # defined on it by users (e.g. on a dataclass). This generally doesn't play well - # as these types are already recognized by the `GenerateSchema` class and isn't ideal - # as we might end up calling `get_schema_or_ref` (expensive) on types that are actually - # not referenceable: - with self.defs.get_schema_or_ref(obj) as (_, maybe_schema): - if maybe_schema is not None: - return maybe_schema - - if obj is source: - ref_mode = 'unpack' - else: - ref_mode = 'to-def' - schema = get_schema( - source, CallbackGetCoreSchemaHandler(self._generate_schema_inner, self, ref_mode=ref_mode) + if get_schema is None: + validators = getattr(obj, '__get_validators__', None) + if validators is None: + return None + warn( + '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', + PydanticDeprecatedSince20, ) - if schema['type'] == 'definitions': - schema = self.defs.unpack_definitions(schema) - - ref = get_ref(schema) - if ref: - return self.defs.create_definition_reference_schema(schema) - - # Note: if schema is of type `'definition-ref'`, we might want to copy it as a - # safety measure (because these are inlined in place -- i.e. mutated directly) - return schema - - if get_schema is None and (validators := getattr(obj, '__get_validators__', None)) is not None: - from pydantic.v1 import BaseModel as BaseModelV1 - - if issubclass(obj, BaseModelV1): - warnings.warn( - f'Mixing V1 models and V2 models (or constructs, like `TypeAdapter`) is not supported. Please upgrade `{obj.__name__}` to V2.', - UserWarning, - ) + schema = core_schema.chain_schema([core_schema.with_info_plain_validator_function(v) for v in validators()]) + else: + if len(inspect.signature(get_schema).parameters) == 1: + # (source) -> CoreSchema + schema = get_schema(source) else: - warnings.warn( - '`__get_validators__` is deprecated and will be removed, use `__get_pydantic_core_schema__` instead.', - PydanticDeprecatedSince20, + schema = get_schema( + source, CallbackGetCoreSchemaHandler(self._generate_schema, self, ref_mode=ref_mode) ) - return core_schema.chain_schema([core_schema.with_info_plain_validator_function(v) for v in validators()]) + + schema = self._unpack_refs_defs(schema) + + ref = get_ref(schema) + if ref: + self.defs.definitions[ref] = self._post_process_generated_schema(schema) + return core_schema.definition_reference_schema(ref) + + schema = self._post_process_generated_schema(schema) + + return schema def _resolve_forward_ref(self, obj: Any) -> Any: # we assume that types_namespace has the target of forward references in its scope, @@ -954,7 +637,7 @@ class GenerateSchema: # class Model(BaseModel): # x: SomeImportedTypeAliasWithAForwardReference try: - obj = _typing_extra.eval_type_backport(obj, *self._types_namespace) + obj = _typing_extra.evaluate_fwd_ref(obj, globalns=self._types_namespace) except NameError as e: raise PydanticUndefinedAnnotation.from_name_error(e) from e @@ -968,18 +651,17 @@ class GenerateSchema: return obj @overload - def _get_args_resolving_forward_refs(self, obj: Any, required: Literal[True]) -> tuple[Any, ...]: ... + def _get_args_resolving_forward_refs(self, obj: Any, required: Literal[True]) -> tuple[Any, ...]: + ... @overload - def _get_args_resolving_forward_refs(self, obj: Any) -> tuple[Any, ...] | None: ... + def _get_args_resolving_forward_refs(self, obj: Any) -> tuple[Any, ...] | None: + ... def _get_args_resolving_forward_refs(self, obj: Any, required: bool = False) -> tuple[Any, ...] | None: args = get_args(obj) if args: - if isinstance(obj, GenericAlias): - # PEP 585 generic aliases don't convert args to ForwardRefs, unlike `typing.List/Dict` etc. - args = (_typing_extra._make_forward_ref(a) if isinstance(a, str) else a for a in args) - args = tuple(self._resolve_forward_ref(a) if isinstance(a, ForwardRef) else a for a in args) + args = tuple([self._resolve_forward_ref(a) if isinstance(a, ForwardRef) else a for a in args]) elif required: # pragma: no cover raise TypeError(f'Expected {obj} to have generic parameters but it had none') return args @@ -999,11 +681,29 @@ class GenerateSchema: raise TypeError(f'Expected two type arguments for {origin}, got 1') return args[0], args[1] - def _generate_schema_inner(self, obj: Any) -> core_schema.CoreSchema: - if typing_objects.is_self(obj): - obj = self._resolve_self_type(obj) + def _post_process_generated_schema(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema: + if 'metadata' in schema: + metadata = schema['metadata'] + metadata[NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY] = self._needs_apply_discriminated_union + else: + schema['metadata'] = { + NEEDS_APPLY_DISCRIMINATED_UNION_METADATA_KEY: self._needs_apply_discriminated_union, + } + return schema - if typing_objects.is_annotated(get_origin(obj)): + def _generate_schema(self, obj: Any) -> core_schema.CoreSchema: + """Recursively generate a pydantic-core schema for any supported python type.""" + has_invalid_schema = self._has_invalid_schema + self._has_invalid_schema = False + needs_apply_discriminated_union = self._needs_apply_discriminated_union + self._needs_apply_discriminated_union = False + schema = self._post_process_generated_schema(self._generate_schema_inner(obj)) + self._has_invalid_schema = self._has_invalid_schema or has_invalid_schema + self._needs_apply_discriminated_union = self._needs_apply_discriminated_union or needs_apply_discriminated_union + return schema + + def _generate_schema_inner(self, obj: Any) -> core_schema.CoreSchema: + if isinstance(obj, _AnnotatedType): return self._annotated_schema(obj) if isinstance(obj, dict): @@ -1016,11 +716,10 @@ class GenerateSchema: if isinstance(obj, ForwardRef): return self.generate_schema(self._resolve_forward_ref(obj)) - BaseModel = import_cached_base_model() + from ..main import BaseModel if lenient_issubclass(obj, BaseModel): - with self.model_type_stack.push(obj): - return self._model_schema(obj) + return self._model_schema(obj) if isinstance(obj, PydanticRecursiveRef): return core_schema.definition_reference_schema(schema_ref=obj.type_ref) @@ -1041,7 +740,7 @@ class GenerateSchema: as they get requested and we figure out what the right API for them is. """ if obj is str: - return core_schema.str_schema() + return self.str_schema() elif obj is bytes: return core_schema.bytes_schema() elif obj is int: @@ -1050,94 +749,61 @@ class GenerateSchema: return core_schema.float_schema() elif obj is bool: return core_schema.bool_schema() - elif obj is complex: - return core_schema.complex_schema() - elif typing_objects.is_any(obj) or obj is object: + elif obj is Any or obj is object: return core_schema.any_schema() - elif obj is datetime.date: - return core_schema.date_schema() - elif obj is datetime.datetime: - return core_schema.datetime_schema() - elif obj is datetime.time: - return core_schema.time_schema() - elif obj is datetime.timedelta: - return core_schema.timedelta_schema() - elif obj is Decimal: - return core_schema.decimal_schema() - elif obj is UUID: - return core_schema.uuid_schema() - elif obj is Url: - return core_schema.url_schema() - elif obj is Fraction: - return self._fraction_schema() - elif obj is MultiHostUrl: - return core_schema.multi_host_url_schema() elif obj is None or obj is _typing_extra.NoneType: return core_schema.none_schema() - if obj is MISSING: - return core_schema.missing_sentinel_schema() - elif obj in IP_TYPES: - return self._ip_schema(obj) elif obj in TUPLE_TYPES: return self._tuple_schema(obj) elif obj in LIST_TYPES: - return self._list_schema(Any) + return self._list_schema(obj, self._get_first_arg_or_any(obj)) elif obj in SET_TYPES: - return self._set_schema(Any) + return self._set_schema(obj, self._get_first_arg_or_any(obj)) elif obj in FROZEN_SET_TYPES: - return self._frozenset_schema(Any) - elif obj in SEQUENCE_TYPES: - return self._sequence_schema(Any) - elif obj in ITERABLE_TYPES: - return self._iterable_schema(obj) + return self._frozenset_schema(obj, self._get_first_arg_or_any(obj)) elif obj in DICT_TYPES: - return self._dict_schema(Any, Any) - elif obj in PATH_TYPES: - return self._path_schema(obj, Any) - elif obj in DEQUE_TYPES: - return self._deque_schema(Any) - elif obj in MAPPING_TYPES: - return self._mapping_schema(obj, Any, Any) - elif obj in COUNTER_TYPES: - return self._mapping_schema(obj, Any, int) - elif typing_objects.is_typealiastype(obj): + return self._dict_schema(obj, *self._get_first_two_args_or_any(obj)) + elif isinstance(obj, TypeAliasType): return self._type_alias_type_schema(obj) - elif obj is type: + elif obj == type: return self._type_schema() - elif _typing_extra.is_callable(obj): + elif _typing_extra.is_callable_type(obj): return core_schema.callable_schema() - elif typing_objects.is_literal(get_origin(obj)): + elif _typing_extra.is_literal_type(obj): return self._literal_schema(obj) elif is_typeddict(obj): return self._typed_dict_schema(obj, None) elif _typing_extra.is_namedtuple(obj): return self._namedtuple_schema(obj, None) - elif typing_objects.is_newtype(obj): - # NewType, can't use isinstance because it fails <3.10 + elif _typing_extra.is_new_type(obj): + # NewType, can't use isinstance because it fails <3.7 return self.generate_schema(obj.__supertype__) - elif obj in PATTERN_TYPES: + elif obj == re.Pattern: return self._pattern_schema(obj) - elif _typing_extra.is_hashable(obj): + elif obj is collections.abc.Hashable or obj is typing.Hashable: return self._hashable_schema() elif isinstance(obj, typing.TypeVar): return self._unsubstituted_typevar_schema(obj) - elif _typing_extra.is_finalvar(obj): + elif is_finalvar(obj): if obj is Final: return core_schema.any_schema() return self.generate_schema( self._get_first_arg_or_any(obj), ) - elif isinstance(obj, VALIDATE_CALL_SUPPORTED_TYPES): - return self._call_schema(obj) + elif isinstance(obj, (FunctionType, LambdaType, MethodType, partial)): + return self._callable_schema(obj) elif inspect.isclass(obj) and issubclass(obj, Enum): - return self._enum_schema(obj) - elif obj is ZoneInfo: - return self._zoneinfo_schema() + from ._std_types_schema import get_enum_core_schema - # dataclasses.is_dataclass coerces dc instances to types, but we only handle - # the case of a dc type here - if dataclasses.is_dataclass(obj): - return self._dataclass_schema(obj, None) # pyright: ignore[reportArgumentType] + return get_enum_core_schema(obj, self._config_wrapper.config_dict) + + if _typing_extra.is_dataclass(obj): + return self._dataclass_schema(obj, None) + + res = self._get_prepare_pydantic_annotations_for_known_type(obj, ()) + if res is not None: + source_type, annotations = res + return self._apply_annotations(source_type, annotations) origin = get_origin(obj) if origin is not None: @@ -1148,50 +814,43 @@ class GenerateSchema: return self._unknown_type_schema(obj) def _match_generic_type(self, obj: Any, origin: Any) -> CoreSchema: # noqa: C901 + if isinstance(origin, TypeAliasType): + return self._type_alias_type_schema(obj) + # Need to handle generic dataclasses before looking for the schema properties because attribute accesses # on _GenericAlias delegate to the origin type, so lose the information about the concrete parametrization # As a result, currently, there is no way to cache the schema for generic dataclasses. This may be possible # to resolve by modifying the value returned by `Generic.__class_getitem__`, but that is a dangerous game. - if dataclasses.is_dataclass(origin): - return self._dataclass_schema(obj, origin) # pyright: ignore[reportArgumentType] + if _typing_extra.is_dataclass(origin): + return self._dataclass_schema(obj, origin) if _typing_extra.is_namedtuple(origin): return self._namedtuple_schema(obj, origin) - schema = self._generate_schema_from_get_schema_method(origin, obj) - if schema is not None: - return schema + from_property = self._generate_schema_from_property(origin, obj) + if from_property is not None: + return from_property - if typing_objects.is_typealiastype(origin): - return self._type_alias_type_schema(obj) - elif is_union_origin(origin): + if _typing_extra.origin_is_union(origin): return self._union_schema(obj) elif origin in TUPLE_TYPES: return self._tuple_schema(obj) elif origin in LIST_TYPES: - return self._list_schema(self._get_first_arg_or_any(obj)) + return self._list_schema(obj, self._get_first_arg_or_any(obj)) elif origin in SET_TYPES: - return self._set_schema(self._get_first_arg_or_any(obj)) + return self._set_schema(obj, self._get_first_arg_or_any(obj)) elif origin in FROZEN_SET_TYPES: - return self._frozenset_schema(self._get_first_arg_or_any(obj)) + return self._frozenset_schema(obj, self._get_first_arg_or_any(obj)) elif origin in DICT_TYPES: - return self._dict_schema(*self._get_first_two_args_or_any(obj)) - elif origin in PATH_TYPES: - return self._path_schema(origin, self._get_first_arg_or_any(obj)) - elif origin in DEQUE_TYPES: - return self._deque_schema(self._get_first_arg_or_any(obj)) - elif origin in MAPPING_TYPES: - return self._mapping_schema(origin, *self._get_first_two_args_or_any(obj)) - elif origin in COUNTER_TYPES: - return self._mapping_schema(origin, self._get_first_arg_or_any(obj), int) + return self._dict_schema(obj, *self._get_first_two_args_or_any(obj)) elif is_typeddict(origin): return self._typed_dict_schema(obj, origin) - elif origin in TYPE_TYPES: + elif origin in (typing.Type, type): return self._subclass_schema(obj) - elif origin in SEQUENCE_TYPES: - return self._sequence_schema(self._get_first_arg_or_any(obj)) - elif origin in ITERABLE_TYPES: + elif origin in {typing.Sequence, collections.abc.Sequence}: + return self._sequence_schema(obj) + elif origin in {typing.Iterable, collections.abc.Iterable, typing.Generator, collections.abc.Generator}: return self._iterable_schema(obj) - elif origin in PATTERN_TYPES: + elif origin in (re.Pattern, typing.Pattern): return self._pattern_schema(obj) if self._arbitrary_types: @@ -1207,15 +866,14 @@ class GenerateSchema: required: bool = True, ) -> core_schema.TypedDictField: """Prepare a TypedDictField to represent a model or typeddict field.""" - schema, metadata = self._common_field_schema(name, field_info, decorators) + common_field = self._common_field_schema(name, field_info, decorators) return core_schema.typed_dict_field( - schema, + common_field['schema'], required=False if not field_info.is_required() else required, - serialization_exclude=field_info.exclude, - validation_alias=_convert_to_aliases(field_info.validation_alias), - serialization_alias=field_info.serialization_alias, - serialization_exclude_if=field_info.exclude_if, - metadata=metadata, + serialization_exclude=common_field['serialization_exclude'], + validation_alias=common_field['validation_alias'], + serialization_alias=common_field['serialization_alias'], + metadata=common_field['metadata'], ) def _generate_md_field_schema( @@ -1225,15 +883,14 @@ class GenerateSchema: decorators: DecoratorInfos, ) -> core_schema.ModelField: """Prepare a ModelField to represent a model field.""" - schema, metadata = self._common_field_schema(name, field_info, decorators) + common_field = self._common_field_schema(name, field_info, decorators) return core_schema.model_field( - schema, - serialization_exclude=field_info.exclude, - validation_alias=_convert_to_aliases(field_info.validation_alias), - serialization_alias=field_info.serialization_alias, - serialization_exclude_if=field_info.exclude_if, - frozen=field_info.frozen, - metadata=metadata, + common_field['schema'], + serialization_exclude=common_field['serialization_exclude'], + validation_alias=common_field['validation_alias'], + serialization_alias=common_field['serialization_alias'], + frozen=common_field['frozen'], + metadata=common_field['metadata'], ) def _generate_dc_field_schema( @@ -1243,45 +900,57 @@ class GenerateSchema: decorators: DecoratorInfos, ) -> core_schema.DataclassField: """Prepare a DataclassField to represent the parameter/field, of a dataclass.""" - schema, metadata = self._common_field_schema(name, field_info, decorators) + common_field = self._common_field_schema(name, field_info, decorators) return core_schema.dataclass_field( name, - schema, - init=field_info.init, + common_field['schema'], init_only=field_info.init_var or None, kw_only=None if field_info.kw_only else False, - serialization_exclude=field_info.exclude, - validation_alias=_convert_to_aliases(field_info.validation_alias), - serialization_alias=field_info.serialization_alias, - serialization_exclude_if=field_info.exclude_if, - frozen=field_info.frozen, - metadata=metadata, + serialization_exclude=common_field['serialization_exclude'], + validation_alias=common_field['validation_alias'], + serialization_alias=common_field['serialization_alias'], + frozen=common_field['frozen'], + metadata=common_field['metadata'], ) - def _common_field_schema( # C901 + def _common_field_schema( # noqa C901 self, name: str, field_info: FieldInfo, decorators: DecoratorInfos - ) -> tuple[CoreSchema, dict[str, Any]]: + ) -> _CommonField: + # Update FieldInfo annotation if appropriate: + from ..fields import AliasChoices, AliasPath, FieldInfo + + if has_instance_in_type(field_info.annotation, (ForwardRef, str)): + types_namespace = self._types_namespace + if self._typevars_map: + types_namespace = (types_namespace or {}).copy() + # Ensure that typevars get mapped to their concrete types: + types_namespace.update({k.__name__: v for k, v in self._typevars_map.items()}) + + evaluated = _typing_extra.eval_type_lenient(field_info.annotation, types_namespace, None) + if evaluated is not field_info.annotation and not has_instance_in_type(evaluated, PydanticRecursiveRef): + field_info.annotation = evaluated + + # Handle any field info attributes that may have been obtained from now-resolved annotations + new_field_info = FieldInfo.from_annotation(evaluated) + for k, v in new_field_info._attributes_set.items(): + # If an attribute is already set, it means it was set by assigning to a call to Field (or just a + # default value), and that should take the highest priority. So don't overwrite existing attributes. + if k not in field_info._attributes_set: + setattr(field_info, k, v) + source_type, annotations = field_info.annotation, field_info.metadata def set_discriminator(schema: CoreSchema) -> CoreSchema: schema = self._apply_discriminator_to_union(schema, field_info.discriminator) return schema - # Convert `@field_validator` decorators to `Before/After/Plain/WrapValidator` instances: - validators_from_decorators = [ - _mode_to_validator[decorator.info.mode]._from_decorator(decorator) - for decorator in filter_field_decorator_info_by_field(decorators.field_validators.values(), name) - ] - with self.field_name_stack.push(name): if field_info.discriminator is not None: - schema = self._apply_annotations( - source_type, annotations + validators_from_decorators, transform_inner_schema=set_discriminator - ) + schema = self._apply_annotations(source_type, annotations, transform_inner_schema=set_discriminator) else: schema = self._apply_annotations( source_type, - annotations + validators_from_decorators, + annotations, ) # This V1 compatibility shim should eventually be removed @@ -1293,9 +962,12 @@ class GenerateSchema: field_info.validate_default = True each_item_validators = [v for v in this_field_validators if v.info.each_item is True] this_field_validators = [v for v in this_field_validators if v not in each_item_validators] - schema = apply_each_item_validators(schema, each_item_validators) + schema = apply_each_item_validators(schema, each_item_validators, name) - schema = apply_validators(schema, this_field_validators) + schema = apply_validators(schema, filter_field_decorator_info_by_field(this_field_validators, name), name) + schema = apply_validators( + schema, filter_field_decorator_info_by_field(decorators.field_validators.values(), name), name + ) # the default validator needs to go outside of any other validators # so that it is the topmost validator for the field validator @@ -1306,14 +978,51 @@ class GenerateSchema: schema = self._apply_field_serializers( schema, filter_field_decorator_info_by_field(decorators.field_serializers.values(), name) ) + json_schema_updates = { + 'title': field_info.title, + 'description': field_info.description, + 'examples': to_jsonable_python(field_info.examples), + } + json_schema_updates = {k: v for k, v in json_schema_updates.items() if v is not None} - pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(field_info) - core_metadata: dict[str, Any] = {} - update_core_metadata( - core_metadata, pydantic_js_updates=pydantic_js_updates, pydantic_js_extra=pydantic_js_extra + json_schema_extra = field_info.json_schema_extra + + metadata = build_metadata_dict( + js_annotation_functions=[get_json_schema_update_func(json_schema_updates, json_schema_extra)] ) - return schema, core_metadata + # apply alias generator + alias_generator = self._config_wrapper.alias_generator + if alias_generator and ( + field_info.alias_priority is None or field_info.alias_priority <= 1 or field_info.alias is None + ): + alias = alias_generator(name) + if not isinstance(alias, str): + raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}') + if field_info.alias is None: + if field_info.serialization_alias is None: + field_info.serialization_alias = alias + if field_info.validation_alias is None: + field_info.validation_alias = alias + else: + field_info.serialization_alias = alias + field_info.validation_alias = alias + field_info.alias_priority = 1 + field_info.alias = alias + + if isinstance(field_info.validation_alias, (AliasChoices, AliasPath)): + validation_alias = field_info.validation_alias.convert_to_aliases() + else: + validation_alias = field_info.validation_alias + + return _common_field( + schema, + serialization_exclude=True if field_info.exclude else None, + validation_alias=validation_alias, + serialization_alias=field_info.serialization_alias, + frozen=field_info.frozen, + metadata=metadata, + ) def _union_schema(self, union_type: Any) -> core_schema.CoreSchema: """Generate schema for a Union.""" @@ -1331,68 +1040,70 @@ class GenerateSchema: else: choices_with_tags: list[CoreSchema | tuple[CoreSchema, str]] = [] for choice in choices: - tag = cast(CoreMetadata, choice.get('metadata', {})).get('pydantic_internal_union_tag_key') - if tag is not None: - choices_with_tags.append((choice, tag)) - else: - choices_with_tags.append(choice) + metadata = choice.get('metadata') + if isinstance(metadata, dict): + tag = metadata.get(_core_utils.TAGGED_UNION_TAG_KEY) + if tag is not None: + choices_with_tags.append((choice, tag)) + else: + choices_with_tags.append(choice) s = core_schema.union_schema(choices_with_tags) if nullable: s = core_schema.nullable_schema(s) return s - def _type_alias_type_schema(self, obj: TypeAliasType) -> CoreSchema: + def _type_alias_type_schema( + self, + obj: Any, # TypeAliasType + ) -> CoreSchema: with self.defs.get_schema_or_ref(obj) as (ref, maybe_schema): if maybe_schema is not None: return maybe_schema - origin: TypeAliasType = get_origin(obj) or obj + origin = get_origin(obj) or obj + + namespace = (self._types_namespace or {}).copy() + new_namespace = {**_typing_extra.get_cls_types_namespace(origin), **namespace} + annotation = origin.__value__ + + self._types_namespace = new_namespace typevars_map = get_standard_typevars_map(obj) - with self._ns_resolver.push(origin): - try: - annotation = _typing_extra.eval_type(origin.__value__, *self._types_namespace) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - annotation = replace_types(annotation, typevars_map) - schema = self.generate_schema(annotation) - assert schema['type'] != 'definitions' - schema['ref'] = ref # type: ignore - return self.defs.create_definition_reference_schema(schema) + annotation = _typing_extra.eval_type_lenient(annotation, self._types_namespace, None) + annotation = replace_types(annotation, typevars_map) + schema = self.generate_schema(annotation) + assert schema['type'] != 'definitions' + schema['ref'] = ref # type: ignore + self._types_namespace = namespace or None + self.defs.definitions[ref] = schema + return core_schema.definition_reference_schema(ref) def _literal_schema(self, literal_type: Any) -> CoreSchema: """Generate schema for a Literal.""" - expected = list(get_literal_values(literal_type, type_check=False, unpack_type_aliases='eager')) + expected = _typing_extra.all_literal_values(literal_type) assert expected, f'literal "expected" cannot be empty, obj={literal_type}' - schema = core_schema.literal_schema(expected) - - if self._config_wrapper.use_enum_values and any(isinstance(v, Enum) for v in expected): - schema = core_schema.no_info_after_validator_function( - lambda v: v.value if isinstance(v, Enum) else v, schema - ) - - return schema + return core_schema.literal_schema(expected) def _typed_dict_schema(self, typed_dict_cls: Any, origin: Any) -> core_schema.CoreSchema: - """Generate a core schema for a `TypedDict` class. + """Generate schema for a TypedDict. - To be able to build a `DecoratorInfos` instance for the `TypedDict` class (which will include - validators, serializers, etc.), we need to have access to the original bases of the class - (see https://docs.python.org/3/library/types.html#types.get_original_bases). - However, the `__orig_bases__` attribute was only added in 3.12 (https://github.com/python/cpython/pull/103698). + It is not possible to track required/optional keys in TypedDict without __required_keys__ + since TypedDict.__new__ erases the base classes (it replaces them with just `dict`) + and thus we can track usage of total=True/False + __required_keys__ was added in Python 3.9 + (https://github.com/miss-islington/cpython/blob/1e9939657dd1f8eb9f596f77c1084d2d351172fc/Doc/library/typing.rst?plain=1#L1546-L1548) + however it is buggy + (https://github.com/python/typing_extensions/blob/ac52ac5f2cb0e00e7988bae1e2a1b8257ac88d6d/src/typing_extensions.py#L657-L666). - For this reason, we require Python 3.12 (or using the `typing_extensions` backport). + On 3.11 but < 3.12 TypedDict does not preserve inheritance information. + + Hence to avoid creating validators that do not do what users expect we only + support typing.TypedDict on Python >= 3.12 or typing_extension.TypedDict on all versions """ - FieldInfo = import_cached_field_info() + from ..fields import FieldInfo - with ( - self.model_type_stack.push(typed_dict_cls), - self.defs.get_schema_or_ref(typed_dict_cls) as ( - typed_dict_ref, - maybe_schema, - ), - ): + with self.defs.get_schema_or_ref(typed_dict_cls) as (typed_dict_ref, maybe_schema): if maybe_schema is not None: return maybe_schema @@ -1407,134 +1118,80 @@ class GenerateSchema: ) try: - # if a typed dictionary class doesn't have config, we use the parent's config, hence a default of `None` - # see https://github.com/pydantic/pydantic/issues/10917 config: ConfigDict | None = get_attribute_from_bases(typed_dict_cls, '__pydantic_config__') except AttributeError: config = None with self._config_wrapper_stack.push(config): - core_config = self._config_wrapper.core_config(title=typed_dict_cls.__name__) + core_config = self._config_wrapper.core_config(typed_dict_cls) + + self = self._current_generate_schema required_keys: frozenset[str] = typed_dict_cls.__required_keys__ fields: dict[str, core_schema.TypedDictField] = {} decorators = DecoratorInfos.build(typed_dict_cls) - decorators.update_from_config(self._config_wrapper) - if self._config_wrapper.use_attribute_docstrings: - field_docstrings = extract_docstrings_from_cls(typed_dict_cls, use_inspect=True) - else: - field_docstrings = None + for field_name, annotation in get_type_hints_infer_globalns( + typed_dict_cls, localns=self._types_namespace, include_extras=True + ).items(): + annotation = replace_types(annotation, typevars_map) + required = field_name in required_keys - try: - annotations = _typing_extra.get_cls_type_hints(typed_dict_cls, ns_resolver=self._ns_resolver) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - - readonly_fields: list[str] = [] - - for field_name, annotation in annotations.items(): - field_info = FieldInfo.from_annotation(annotation, _source=AnnotationSource.TYPED_DICT) - field_info.annotation = replace_types(field_info.annotation, typevars_map) - - required = ( - field_name in required_keys or 'required' in field_info._qualifiers - ) and 'not_required' not in field_info._qualifiers - if 'read_only' in field_info._qualifiers: - readonly_fields.append(field_name) - - if ( - field_docstrings is not None - and field_info.description is None - and field_name in field_docstrings - ): - field_info.description = field_docstrings[field_name] - update_field_from_config(self._config_wrapper, field_name, field_info) + if get_origin(annotation) == _typing_extra.Required: + required = True + annotation = self._get_args_resolving_forward_refs( + annotation, + required=True, + )[0] + elif get_origin(annotation) == _typing_extra.NotRequired: + required = False + annotation = self._get_args_resolving_forward_refs( + annotation, + required=True, + )[0] + field_info = FieldInfo.from_annotation(annotation) fields[field_name] = self._generate_td_field_schema( field_name, field_info, decorators, required=required ) - if readonly_fields: - fields_repr = ', '.join(repr(f) for f in readonly_fields) - plural = len(readonly_fields) >= 2 - warnings.warn( - f'Item{"s" if plural else ""} {fields_repr} on TypedDict class {typed_dict_cls.__name__!r} ' - f'{"are" if plural else "is"} using the `ReadOnly` qualifier. Pydantic will not protect items ' - 'from any mutation on dictionary instances.', - UserWarning, - ) - - extra_behavior: core_schema.ExtraBehavior = 'ignore' - extras_schema: CoreSchema | None = None # For 'allow', equivalent to `Any` - no validation performed. - - # `__closed__` is `None` when not specified (equivalent to `False`): - is_closed = bool(getattr(typed_dict_cls, '__closed__', False)) - extra_items = getattr(typed_dict_cls, '__extra_items__', typing_extensions.NoExtraItems) - if is_closed: - extra_behavior = 'forbid' - extras_schema = None - elif not typing_objects.is_noextraitems(extra_items): - extra_behavior = 'allow' - extras_schema = self.generate_schema(replace_types(extra_items, typevars_map)) - - if (config_extra := self._config_wrapper.extra) in ('allow', 'forbid'): - if is_closed and config_extra == 'allow': - warnings.warn( - f"TypedDict class {typed_dict_cls.__qualname__!r} is closed, but 'extra' configuration " - "is set to `'allow'`. The 'extra' configuration value will be ignored.", - category=TypedDictExtraConfigWarning, - ) - elif not typing_objects.is_noextraitems(extra_items) and config_extra == 'forbid': - warnings.warn( - f"TypedDict class {typed_dict_cls.__qualname__!r} allows extra items, but 'extra' configuration " - "is set to `'forbid'`. The 'extra' configuration value will be ignored.", - category=TypedDictExtraConfigWarning, - ) - else: - extra_behavior = config_extra + metadata = build_metadata_dict( + js_functions=[partial(modify_model_json_schema, cls=typed_dict_cls)], typed_dict_cls=typed_dict_cls + ) td_schema = core_schema.typed_dict_schema( fields, - cls=typed_dict_cls, computed_fields=[ self._computed_field_schema(d, decorators.field_serializers) for d in decorators.computed_fields.values() ], - extra_behavior=extra_behavior, - extras_schema=extras_schema, ref=typed_dict_ref, + metadata=metadata, config=core_config, ) schema = self._apply_model_serializers(td_schema, decorators.model_serializers.values()) schema = apply_model_validators(schema, decorators.model_validators.values(), 'all') - return self.defs.create_definition_reference_schema(schema) + self.defs.definitions[typed_dict_ref] = self._post_process_generated_schema(schema) + return core_schema.definition_reference_schema(typed_dict_ref) def _namedtuple_schema(self, namedtuple_cls: Any, origin: Any) -> core_schema.CoreSchema: """Generate schema for a NamedTuple.""" - with ( - self.model_type_stack.push(namedtuple_cls), - self.defs.get_schema_or_ref(namedtuple_cls) as ( - namedtuple_ref, - maybe_schema, - ), - ): + with self.defs.get_schema_or_ref(namedtuple_cls) as (namedtuple_ref, maybe_schema): if maybe_schema is not None: return maybe_schema typevars_map = get_standard_typevars_map(namedtuple_cls) if origin is not None: namedtuple_cls = origin - try: - annotations = _typing_extra.get_cls_type_hints(namedtuple_cls, ns_resolver=self._ns_resolver) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e + annotations: dict[str, Any] = get_type_hints_infer_globalns( + namedtuple_cls, include_extras=True, localns=self._types_namespace + ) if not annotations: # annotations is empty, happens if namedtuple_cls defined via collections.namedtuple(...) - annotations: dict[str, Any] = dict.fromkeys(namedtuple_cls._fields, Any) + annotations = {k: Any for k in namedtuple_cls._fields} if typevars_map: annotations = { @@ -1545,111 +1202,45 @@ class GenerateSchema: arguments_schema = core_schema.arguments_schema( [ self._generate_parameter_schema( - field_name, - annotation, - source=AnnotationSource.NAMED_TUPLE, - default=namedtuple_cls._field_defaults.get(field_name, Parameter.empty), + field_name, annotation, default=namedtuple_cls._field_defaults.get(field_name, Parameter.empty) ) for field_name, annotation in annotations.items() ], - metadata={'pydantic_js_prefer_positional_arguments': True}, + metadata=build_metadata_dict(js_prefer_positional_arguments=True), ) - schema = core_schema.call_schema(arguments_schema, namedtuple_cls, ref=namedtuple_ref) - return self.defs.create_definition_reference_schema(schema) + return core_schema.call_schema(arguments_schema, namedtuple_cls, ref=namedtuple_ref) def _generate_parameter_schema( self, name: str, annotation: type[Any], - source: AnnotationSource, default: Any = Parameter.empty, mode: Literal['positional_only', 'positional_or_keyword', 'keyword_only'] | None = None, ) -> core_schema.ArgumentsParameter: - """Generate the definition of a field in a namedtuple or a parameter in a function signature. - - This definition is meant to be used for the `'arguments'` core schema, which will be replaced - in V3 by the `'arguments-v3`'. - """ - FieldInfo = import_cached_field_info() + """Prepare a ArgumentsParameter to represent a field in a namedtuple or function signature.""" + from ..fields import FieldInfo if default is Parameter.empty: - field = FieldInfo.from_annotation(annotation, _source=source) + field = FieldInfo.from_annotation(annotation) else: - field = FieldInfo.from_annotated_attribute(annotation, default, _source=source) - + field = FieldInfo.from_annotated_attribute(annotation, default) assert field.annotation is not None, 'field.annotation should not be None when generating a schema' - update_field_from_config(self._config_wrapper, name, field) - + source_type, annotations = field.annotation, field.metadata with self.field_name_stack.push(name): - schema = self._apply_annotations( - field.annotation, - [field], - # Because we pass `field` as metadata above (required for attributes relevant for - # JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo` - # attributes that will not be used: - check_unsupported_field_info_attributes=False, - ) + schema = self._apply_annotations(source_type, annotations) if not field.is_required(): schema = wrap_default(field, schema) - parameter_schema = core_schema.arguments_parameter( - name, - schema, - mode=mode, - alias=_convert_to_aliases(field.validation_alias), - ) - - return parameter_schema - - def _generate_parameter_v3_schema( - self, - name: str, - annotation: Any, - source: AnnotationSource, - mode: Literal[ - 'positional_only', - 'positional_or_keyword', - 'keyword_only', - 'var_args', - 'var_kwargs_uniform', - 'var_kwargs_unpacked_typed_dict', - ], - default: Any = Parameter.empty, - ) -> core_schema.ArgumentsV3Parameter: - """Generate the definition of a parameter in a function signature. - - This definition is meant to be used for the `'arguments-v3'` core schema, which will replace - the `'arguments`' schema in V3. - """ - FieldInfo = import_cached_field_info() - - if default is Parameter.empty: - field = FieldInfo.from_annotation(annotation, _source=source) + parameter_schema = core_schema.arguments_parameter(name, schema) + if mode is not None: + parameter_schema['mode'] = mode + if field.alias is not None: + parameter_schema['alias'] = field.alias else: - field = FieldInfo.from_annotated_attribute(annotation, default, _source=source) - update_field_from_config(self._config_wrapper, name, field) - - with self.field_name_stack.push(name): - schema = self._apply_annotations( - field.annotation, - [field], - # Because we pass `field` as metadata above (required for attributes relevant for - # JSON Scheme generation), we need to ignore the potential warnings about `FieldInfo` - # attributes that will not be used: - check_unsupported_field_info_attributes=False, - ) - - if not field.is_required(): - schema = wrap_default(field, schema) - - parameter_schema = core_schema.arguments_v3_parameter( - name=name, - schema=schema, - mode=mode, - alias=_convert_to_aliases(field.validation_alias), - ) - + alias_generator = self._config_wrapper.alias_generator + if alias_generator: + parameter_schema['alias'] = alias_generator(name) return parameter_schema def _tuple_schema(self, tuple_type: Any) -> core_schema.CoreSchema: @@ -1665,22 +1256,22 @@ class GenerateSchema: # This is only true for <3.11, on Python 3.11+ `typing.Tuple[()]` gives `params=()` if not params: if tuple_type in TUPLE_TYPES: - return core_schema.tuple_schema([core_schema.any_schema()], variadic_item_index=0) + return core_schema.tuple_variable_schema() else: # special case for `tuple[()]` which means `tuple[]` - an empty tuple - return core_schema.tuple_schema([]) + return core_schema.tuple_positional_schema([]) elif params[-1] is Ellipsis: if len(params) == 2: - return core_schema.tuple_schema([self.generate_schema(params[0])], variadic_item_index=0) + return self._tuple_variable_schema(tuple_type, params[0]) else: # TODO: something like https://github.com/pydantic/pydantic/issues/5952 raise ValueError('Variable tuples can only have one type') elif len(params) == 1 and params[0] == (): - # special case for `tuple[()]` which means `tuple[]` - an empty tuple + # special case for `Tuple[()]` which means `Tuple[]` - an empty tuple # NOTE: This conditional can be removed when we drop support for Python 3.10. - return core_schema.tuple_schema([]) + return self._tuple_positional_schema(tuple_type, []) else: - return core_schema.tuple_schema([self.generate_schema(param) for param in params]) + return self._tuple_positional_schema(tuple_type, list(params)) def _type_schema(self) -> core_schema.CoreSchema: return core_schema.custom_error_schema( @@ -1689,83 +1280,45 @@ class GenerateSchema: custom_error_message='Input should be a type', ) - def _zoneinfo_schema(self) -> core_schema.CoreSchema: - """Generate schema for a zone_info.ZoneInfo object""" - from ._validators import validate_str_is_valid_iana_tz - - metadata = {'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'zoneinfo'}]} - return core_schema.no_info_plain_validator_function( - validate_str_is_valid_iana_tz, - serialization=core_schema.to_string_ser_schema(), - metadata=metadata, - ) - def _union_is_subclass_schema(self, union_type: Any) -> core_schema.CoreSchema: - """Generate schema for `type[Union[X, ...]]`.""" + """Generate schema for `Type[Union[X, ...]]`.""" args = self._get_args_resolving_forward_refs(union_type, required=True) - return core_schema.union_schema([self.generate_schema(type[args]) for args in args]) + return core_schema.union_schema([self.generate_schema(typing.Type[args]) for args in args]) def _subclass_schema(self, type_: Any) -> core_schema.CoreSchema: - """Generate schema for a type, e.g. `type[int]`.""" + """Generate schema for a Type, e.g. `Type[int]`.""" type_param = self._get_first_arg_or_any(type_) - - # Assume `type[Annotated[, ...]]` is equivalent to `type[]`: - type_param = _typing_extra.annotated_type(type_param) or type_param - - if typing_objects.is_any(type_param): + if type_param == Any: return self._type_schema() - elif typing_objects.is_typealiastype(type_param): - return self.generate_schema(type[type_param.__value__]) - elif typing_objects.is_typevar(type_param): + elif isinstance(type_param, typing.TypeVar): if type_param.__bound__: - if is_union_origin(get_origin(type_param.__bound__)): + if _typing_extra.origin_is_union(get_origin(type_param.__bound__)): return self._union_is_subclass_schema(type_param.__bound__) return core_schema.is_subclass_schema(type_param.__bound__) elif type_param.__constraints__: - return core_schema.union_schema([self.generate_schema(type[c]) for c in type_param.__constraints__]) + return core_schema.union_schema( + [self.generate_schema(typing.Type[c]) for c in type_param.__constraints__] + ) else: return self._type_schema() - elif is_union_origin(get_origin(type_param)): + elif _typing_extra.origin_is_union(get_origin(type_param)): return self._union_is_subclass_schema(type_param) else: - if typing_objects.is_self(type_param): - type_param = self._resolve_self_type(type_param) - if _typing_extra.is_generic_alias(type_param): - raise PydanticUserError( - 'Subscripting `type[]` with an already parametrized type is not supported. ' - f'Instead of using type[{type_param!r}], use type[{_repr.display_as_type(get_origin(type_param))}].', - code=None, - ) - if not inspect.isclass(type_param): - # when using type[None], this doesn't type convert to type[NoneType], and None isn't a class - # so we handle it manually here - if type_param is None: - return core_schema.is_subclass_schema(_typing_extra.NoneType) - raise TypeError(f'Expected a class, got {type_param!r}') return core_schema.is_subclass_schema(type_param) - def _sequence_schema(self, items_type: Any) -> core_schema.CoreSchema: + def _sequence_schema(self, sequence_type: Any) -> core_schema.CoreSchema: """Generate schema for a Sequence, e.g. `Sequence[int]`.""" - from ._serializers import serialize_sequence_via_list + item_type = self._get_first_arg_or_any(sequence_type) - item_type_schema = self.generate_schema(items_type) - list_schema = core_schema.list_schema(item_type_schema) - - json_schema = smart_deepcopy(list_schema) + list_schema = core_schema.list_schema(self.generate_schema(item_type)) python_schema = core_schema.is_instance_schema(typing.Sequence, cls_repr='Sequence') - if not typing_objects.is_any(items_type): + if item_type != Any: from ._validators import sequence_validator python_schema = core_schema.chain_schema( [python_schema, core_schema.no_info_wrap_validator_function(sequence_validator, list_schema)], ) - - serialization = core_schema.wrap_serializer_function_ser_schema( - serialize_sequence_via_list, schema=item_type_schema, info_arg=True - ) - return core_schema.json_or_python_schema( - json_schema=json_schema, python_schema=python_schema, serialization=serialization - ) + return core_schema.json_or_python_schema(json_schema=list_schema, python_schema=python_schema) def _iterable_schema(self, type_: Any) -> core_schema.GeneratorSchema: """Generate a schema for an `Iterable`.""" @@ -1776,11 +1329,11 @@ class GenerateSchema: def _pattern_schema(self, pattern_type: Any) -> core_schema.CoreSchema: from . import _validators - metadata = {'pydantic_js_functions': [lambda _1, _2: {'type': 'string', 'format': 'regex'}]} + metadata = build_metadata_dict(js_functions=[lambda _1, _2: {'type': 'string', 'format': 'regex'}]) ser = core_schema.plain_serializer_function_ser_schema( attrgetter('pattern'), when_used='json', return_schema=core_schema.str_schema() ) - if pattern_type is typing.Pattern or pattern_type is re.Pattern: + if pattern_type == typing.Pattern or pattern_type == re.Pattern: # bare type return core_schema.no_info_plain_validator_function( _validators.pattern_either_validator, serialization=ser, metadata=metadata @@ -1790,11 +1343,11 @@ class GenerateSchema: pattern_type, required=True, )[0] - if param is str: + if param == str: return core_schema.no_info_plain_validator_function( _validators.pattern_str_validator, serialization=ser, metadata=metadata ) - elif param is bytes: + elif param == bytes: return core_schema.no_info_plain_validator_function( _validators.pattern_bytes_validator, serialization=ser, metadata=metadata ) @@ -1803,12 +1356,7 @@ class GenerateSchema: def _hashable_schema(self) -> core_schema.CoreSchema: return core_schema.custom_error_schema( - schema=core_schema.json_or_python_schema( - json_schema=core_schema.chain_schema( - [core_schema.any_schema(), core_schema.is_instance_schema(collections.abc.Hashable)] - ), - python_schema=core_schema.is_instance_schema(collections.abc.Hashable), - ), + core_schema.is_instance_schema(collections.abc.Hashable), custom_error_type='is_hashable', custom_error_message='Input should be hashable', ) @@ -1817,81 +1365,34 @@ class GenerateSchema: self, dataclass: type[StandardDataclass], origin: type[StandardDataclass] | None ) -> core_schema.CoreSchema: """Generate schema for a dataclass.""" - with ( - self.model_type_stack.push(dataclass), - self.defs.get_schema_or_ref(dataclass) as ( - dataclass_ref, - maybe_schema, - ), - ): + with self.defs.get_schema_or_ref(dataclass) as (dataclass_ref, maybe_schema): if maybe_schema is not None: return maybe_schema - schema = dataclass.__dict__.get('__pydantic_core_schema__') - if schema is not None and not isinstance(schema, MockCoreSchema): - if schema['type'] == 'definitions': - schema = self.defs.unpack_definitions(schema) - ref = get_ref(schema) - if ref: - return self.defs.create_definition_reference_schema(schema) - else: - return schema - typevars_map = get_standard_typevars_map(dataclass) if origin is not None: dataclass = origin - # if (plain) dataclass doesn't have config, we use the parent's config, hence a default of `None` - # (Pydantic dataclasses have an empty dict config by default). - # see https://github.com/pydantic/pydantic/issues/10917 config = getattr(dataclass, '__pydantic_config__', None) + with self._config_wrapper_stack.push(config): + core_config = self._config_wrapper.core_config(dataclass) - from ..dataclasses import is_pydantic_dataclass + self = self._current_generate_schema + + from ..dataclasses import is_pydantic_dataclass - with self._ns_resolver.push(dataclass), self._config_wrapper_stack.push(config): if is_pydantic_dataclass(dataclass): - if dataclass.__pydantic_fields_complete__(): - # Copy the field info instances to avoid mutating the `FieldInfo` instances - # of the generic dataclass generic origin (e.g. `apply_typevars_map` below). - # Note that we don't apply `deepcopy` on `__pydantic_fields__` because we - # don't want to copy the `FieldInfo` attributes: - fields = { - f_name: copy(field_info) for f_name, field_info in dataclass.__pydantic_fields__.items() - } - if typevars_map: - for field in fields.values(): - field.apply_typevars_map(typevars_map, *self._types_namespace) - else: - try: - fields = rebuild_dataclass_fields( - dataclass, - config_wrapper=self._config_wrapper, - ns_resolver=self._ns_resolver, - typevars_map=typevars_map or {}, - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e + fields = deepcopy(dataclass.__pydantic_fields__) + if typevars_map: + for field in fields.values(): + field.apply_typevars_map(typevars_map, self._types_namespace) else: fields = collect_dataclass_fields( dataclass, + self._types_namespace, typevars_map=typevars_map, - config_wrapper=self._config_wrapper, ) - - if self._config_wrapper.extra == 'allow': - # disallow combination of init=False on a dataclass field and extra='allow' on a dataclass - for field_name, field in fields.items(): - if field.init is False: - raise PydanticUserError( - f'Field {field_name} has `init=False` and dataclass has config setting `extra="allow"`. ' - f'This combination is not allowed.', - code='dataclass-init-false-extra-allow', - ) - - decorators = dataclass.__dict__.get('__pydantic_decorators__') - if decorators is None: - decorators = DecoratorInfos.build(dataclass) - decorators.update_from_config(self._config_wrapper) + decorators = dataclass.__dict__.get('__pydantic_decorators__') or DecoratorInfos.build(dataclass) # Move kw_only=False args to the start of the list, as this is how vanilla dataclasses work. # Note that when kw_only is missing or None, it is treated as equivalent to kw_only=True args = sorted( @@ -1911,237 +1412,116 @@ class GenerateSchema: collect_init_only=has_post_init, ) - inner_schema = apply_validators(args_schema, decorators.root_validators.values()) + inner_schema = apply_validators(args_schema, decorators.root_validators.values(), None) model_validators = decorators.model_validators.values() inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') - core_config = self._config_wrapper.core_config(title=dataclass.__name__) - dc_schema = core_schema.dataclass_schema( dataclass, inner_schema, - generic_origin=origin, post_init=has_post_init, ref=dataclass_ref, fields=[field.name for field in dataclasses.fields(dataclass)], slots=has_slots, config=core_config, - # we don't use a custom __setattr__ for dataclasses, so we must - # pass along the frozen config setting to the pydantic-core schema - frozen=self._config_wrapper_stack.tail.frozen, ) schema = self._apply_model_serializers(dc_schema, decorators.model_serializers.values()) schema = apply_model_validators(schema, model_validators, 'outer') - return self.defs.create_definition_reference_schema(schema) + self.defs.definitions[dataclass_ref] = self._post_process_generated_schema(schema) + return core_schema.definition_reference_schema(dataclass_ref) - def _call_schema(self, function: ValidateCallSupportedTypes) -> core_schema.CallSchema: + def _callable_schema(self, function: Callable[..., Any]) -> core_schema.CallSchema: """Generate schema for a Callable. TODO support functional validators once we support them in Config """ - arguments_schema = self._arguments_schema(function) + sig = signature(function) - return_schema: core_schema.CoreSchema | None = None - config_wrapper = self._config_wrapper - if config_wrapper.validate_return: - sig = signature(function) - return_hint = sig.return_annotation - if return_hint is not sig.empty: - globalns, localns = self._types_namespace - type_hints = _typing_extra.get_function_type_hints( - function, globalns=globalns, localns=localns, include_keys={'return'} - ) - return_schema = self.generate_schema(type_hints['return']) + type_hints = _typing_extra.get_function_type_hints(function) - return core_schema.call_schema( - arguments_schema, - function, - return_schema=return_schema, - ) - - def _arguments_schema( - self, function: ValidateCallSupportedTypes, parameters_callback: ParametersCallback | None = None - ) -> core_schema.ArgumentsSchema: - """Generate schema for a Signature.""" mode_lookup: dict[_ParameterKind, Literal['positional_only', 'positional_or_keyword', 'keyword_only']] = { Parameter.POSITIONAL_ONLY: 'positional_only', Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword', Parameter.KEYWORD_ONLY: 'keyword_only', } - sig = signature(function) - globalns, localns = self._types_namespace - type_hints = _typing_extra.get_function_type_hints(function, globalns=globalns, localns=localns) - arguments_list: list[core_schema.ArgumentsParameter] = [] var_args_schema: core_schema.CoreSchema | None = None var_kwargs_schema: core_schema.CoreSchema | None = None - var_kwargs_mode: core_schema.VarKwargsMode | None = None - for i, (name, p) in enumerate(sig.parameters.items()): + for name, p in sig.parameters.items(): if p.annotation is sig.empty: - annotation = typing.cast(Any, Any) + annotation = Any else: annotation = type_hints[name] - if parameters_callback is not None: - result = parameters_callback(i, name, annotation) - if result == 'skip': - continue - parameter_mode = mode_lookup.get(p.kind) if parameter_mode is not None: - arg_schema = self._generate_parameter_schema( - name, annotation, AnnotationSource.FUNCTION, p.default, parameter_mode - ) + arg_schema = self._generate_parameter_schema(name, annotation, p.default, parameter_mode) arguments_list.append(arg_schema) elif p.kind == Parameter.VAR_POSITIONAL: var_args_schema = self.generate_schema(annotation) else: assert p.kind == Parameter.VAR_KEYWORD, p.kind + var_kwargs_schema = self.generate_schema(annotation) - unpack_type = _typing_extra.unpack_type(annotation) - if unpack_type is not None: - origin = get_origin(unpack_type) or unpack_type - if not is_typeddict(origin): - raise PydanticUserError( - f'Expected a `TypedDict` class inside `Unpack[...]`, got {unpack_type!r}', - code='unpack-typed-dict', - ) - non_pos_only_param_names = { - name for name, p in sig.parameters.items() if p.kind != Parameter.POSITIONAL_ONLY - } - overlapping_params = non_pos_only_param_names.intersection(origin.__annotations__) - if overlapping_params: - raise PydanticUserError( - f'Typed dictionary {origin.__name__!r} overlaps with parameter' - f'{"s" if len(overlapping_params) >= 2 else ""} ' - f'{", ".join(repr(p) for p in sorted(overlapping_params))}', - code='overlapping-unpack-typed-dict', - ) + return_schema: core_schema.CoreSchema | None = None + config_wrapper = self._config_wrapper + if config_wrapper.validate_return: + return_hint = type_hints.get('return') + if return_hint is not None: + return_schema = self.generate_schema(return_hint) - var_kwargs_mode = 'unpacked-typed-dict' - var_kwargs_schema = self._typed_dict_schema(unpack_type, get_origin(unpack_type)) - else: - var_kwargs_mode = 'uniform' - var_kwargs_schema = self.generate_schema(annotation) - - return core_schema.arguments_schema( - arguments_list, - var_args_schema=var_args_schema, - var_kwargs_mode=var_kwargs_mode, - var_kwargs_schema=var_kwargs_schema, - validate_by_name=self._config_wrapper.validate_by_name, - ) - - def _arguments_v3_schema( - self, function: ValidateCallSupportedTypes, parameters_callback: ParametersCallback | None = None - ) -> core_schema.ArgumentsV3Schema: - mode_lookup: dict[ - _ParameterKind, Literal['positional_only', 'positional_or_keyword', 'var_args', 'keyword_only'] - ] = { - Parameter.POSITIONAL_ONLY: 'positional_only', - Parameter.POSITIONAL_OR_KEYWORD: 'positional_or_keyword', - Parameter.VAR_POSITIONAL: 'var_args', - Parameter.KEYWORD_ONLY: 'keyword_only', - } - - sig = signature(function) - globalns, localns = self._types_namespace - type_hints = _typing_extra.get_function_type_hints(function, globalns=globalns, localns=localns) - - parameters_list: list[core_schema.ArgumentsV3Parameter] = [] - - for i, (name, p) in enumerate(sig.parameters.items()): - if parameters_callback is not None: - result = parameters_callback(i, name, p.annotation) - if result == 'skip': - continue - - if p.annotation is Parameter.empty: - annotation = typing.cast(Any, Any) - else: - annotation = type_hints[name] - - parameter_mode = mode_lookup.get(p.kind) - if parameter_mode is None: - assert p.kind == Parameter.VAR_KEYWORD, p.kind - - unpack_type = _typing_extra.unpack_type(annotation) - if unpack_type is not None: - origin = get_origin(unpack_type) or unpack_type - if not is_typeddict(origin): - raise PydanticUserError( - f'Expected a `TypedDict` class inside `Unpack[...]`, got {unpack_type!r}', - code='unpack-typed-dict', - ) - non_pos_only_param_names = { - name for name, p in sig.parameters.items() if p.kind != Parameter.POSITIONAL_ONLY - } - overlapping_params = non_pos_only_param_names.intersection(origin.__annotations__) - if overlapping_params: - raise PydanticUserError( - f'Typed dictionary {origin.__name__!r} overlaps with parameter' - f'{"s" if len(overlapping_params) >= 2 else ""} ' - f'{", ".join(repr(p) for p in sorted(overlapping_params))}', - code='overlapping-unpack-typed-dict', - ) - parameter_mode = 'var_kwargs_unpacked_typed_dict' - annotation = unpack_type - else: - parameter_mode = 'var_kwargs_uniform' - - parameters_list.append( - self._generate_parameter_v3_schema( - name, annotation, AnnotationSource.FUNCTION, parameter_mode, default=p.default - ) - ) - - return core_schema.arguments_v3_schema( - parameters_list, - validate_by_name=self._config_wrapper.validate_by_name, + return core_schema.call_schema( + core_schema.arguments_schema( + arguments_list, + var_args_schema=var_args_schema, + var_kwargs_schema=var_kwargs_schema, + populate_by_name=config_wrapper.populate_by_name, + ), + function, + return_schema=return_schema, ) def _unsubstituted_typevar_schema(self, typevar: typing.TypeVar) -> core_schema.CoreSchema: - try: - has_default = typevar.has_default() # pyright: ignore[reportAttributeAccessIssue] - except AttributeError: - # Happens if using `typing.TypeVar` (and not `typing_extensions`) on Python < 3.13 - pass - else: - if has_default: - return self.generate_schema(typevar.__default__) # pyright: ignore[reportAttributeAccessIssue] + assert isinstance(typevar, typing.TypeVar) - if constraints := typevar.__constraints__: - return self._union_schema(typing.Union[constraints]) + bound = typevar.__bound__ + constraints = typevar.__constraints__ + default = getattr(typevar, '__default__', None) - if bound := typevar.__bound__: + if (bound is not None) + (len(constraints) != 0) + (default is not None) > 1: + raise NotImplementedError( + 'Pydantic does not support mixing more than one of TypeVar bounds, constraints and defaults' + ) + + if default is not None: + return self.generate_schema(default) + elif constraints: + return self._union_schema(typing.Union[constraints]) # type: ignore + elif bound: schema = self.generate_schema(bound) - schema['serialization'] = core_schema.simple_ser_schema('any') + schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( + lambda x, h: h(x), schema=core_schema.any_schema() + ) return schema - - return core_schema.any_schema() + else: + return core_schema.any_schema() def _computed_field_schema( self, d: Decorator[ComputedFieldInfo], field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]], ) -> core_schema.ComputedField: - if d.info.return_type is not PydanticUndefined: - return_type = d.info.return_type - else: - try: - # Do not pass in globals as the function could be defined in a different module. - # Instead, let `get_callable_return_type` infer the globals to use, but still pass - # in locals that may contain a parent/rebuild namespace: - return_type = _decorators.get_callable_return_type(d.func, localns=self._types_namespace.locals) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e + try: + return_type = _decorators.get_function_return_type(d.func, d.info.return_type, self._types_namespace) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e if return_type is PydanticUndefined: raise PydanticUserError( 'Computed field is missing return type annotation or specifying `return_type`' - ' to the `@computed_field` decorator (e.g. `@computed_field(return_type=int | str)`)', + ' to the `@computed_field` decorator (e.g. `@computed_field(return_type=int|str)`)', code='model-field-missing-annotation', ) @@ -2154,22 +1534,51 @@ class GenerateSchema: return_type_schema = self._apply_field_serializers( return_type_schema, filter_field_decorator_info_by_field(field_serializers.values(), d.cls_var_name), + computed_field=True, ) + # Handle alias_generator using similar logic to that from + # pydantic._internal._generate_schema.GenerateSchema._common_field_schema, + # with field_info -> d.info and name -> d.cls_var_name + alias_generator = self._config_wrapper.alias_generator + if alias_generator and (d.info.alias_priority is None or d.info.alias_priority <= 1): + alias = alias_generator(d.cls_var_name) + if not isinstance(alias, str): + raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}') + d.info.alias = alias + d.info.alias_priority = 1 - pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(d.info) - core_metadata: dict[str, Any] = {} - update_core_metadata( - core_metadata, - pydantic_js_updates={'readOnly': True, **(pydantic_js_updates if pydantic_js_updates else {})}, - pydantic_js_extra=pydantic_js_extra, - ) + def set_computed_field_metadata(schema: CoreSchemaOrField, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + json_schema = handler(schema) + + json_schema['readOnly'] = True + + title = d.info.title + if title is not None: + json_schema['title'] = title + + description = d.info.description + if description is not None: + json_schema['description'] = description + + examples = d.info.examples + if examples is not None: + json_schema['examples'] = to_jsonable_python(examples) + + json_schema_extra = d.info.json_schema_extra + if json_schema_extra is not None: + add_json_schema_extra(json_schema, json_schema_extra) + + return json_schema + + metadata = build_metadata_dict(js_annotation_functions=[set_computed_field_metadata]) return core_schema.computed_field( - d.cls_var_name, return_schema=return_type_schema, alias=d.info.alias, metadata=core_metadata + d.cls_var_name, return_schema=return_type_schema, alias=d.info.alias, metadata=metadata ) def _annotated_schema(self, annotated_type: Any) -> core_schema.CoreSchema: """Generate schema for an Annotated type, e.g. `Annotated[int, Field(...)]` or `Annotated[int, Gt(0)]`.""" - FieldInfo = import_cached_field_info() + from ..fields import FieldInfo + source_type, *annotations = self._get_args_resolving_forward_refs( annotated_type, required=True, @@ -2182,12 +1591,30 @@ class GenerateSchema: schema = wrap_default(annotation, schema) return schema + def _get_prepare_pydantic_annotations_for_known_type( + self, obj: Any, annotations: tuple[Any, ...] + ) -> tuple[Any, list[Any]] | None: + from ._std_types_schema import PREPARE_METHODS + + # This check for hashability is only necessary for python 3.7 + try: + hash(obj) + except TypeError: + # obj is definitely not a known type if this fails + return None + + for gen in PREPARE_METHODS: + res = gen(obj, annotations, self._config_wrapper.config_dict) + if res is not None: + return res + + return None + def _apply_annotations( self, source_type: Any, annotations: list[Any], transform_inner_schema: Callable[[CoreSchema], CoreSchema] = lambda x: x, - check_unsupported_field_info_attributes: bool = True, ) -> CoreSchema: """Apply arguments from `Annotated` or from `FieldInfo` to a schema. @@ -2196,18 +1623,21 @@ class GenerateSchema: (in other words, `GenerateSchema._annotated_schema` just unpacks `Annotated`, this process it). """ annotations = list(_known_annotated_metadata.expand_grouped_metadata(annotations)) + res = self._get_prepare_pydantic_annotations_for_known_type(source_type, tuple(annotations)) + if res is not None: + source_type, annotations = res pydantic_js_annotation_functions: list[GetJsonSchemaFunction] = [] def inner_handler(obj: Any) -> CoreSchema: - schema = self._generate_schema_from_get_schema_method(obj, source_type) - - if schema is None: - schema = self._generate_schema_inner(obj) - - metadata_js_function = _extract_get_pydantic_json_schema(obj) + from_property = self._generate_schema_from_property(obj, obj) + if from_property is None: + schema = self._generate_schema(obj) + else: + schema = from_property + metadata_js_function = _extract_get_pydantic_json_schema(obj, schema) if metadata_js_function is not None: - metadata_schema = resolve_original_schema(schema, self.defs) + metadata_schema = resolve_original_schema(schema, self.defs.definitions) if metadata_schema is not None: self._add_js_function(metadata_schema, metadata_js_function) return transform_inner_schema(schema) @@ -2218,54 +1648,19 @@ class GenerateSchema: if annotation is None: continue get_inner_schema = self._get_wrapped_inner_schema( - get_inner_schema, - annotation, - pydantic_js_annotation_functions, - check_unsupported_field_info_attributes=check_unsupported_field_info_attributes, + get_inner_schema, annotation, pydantic_js_annotation_functions ) schema = get_inner_schema(source_type) if pydantic_js_annotation_functions: - core_metadata = schema.setdefault('metadata', {}) - update_core_metadata(core_metadata, pydantic_js_annotation_functions=pydantic_js_annotation_functions) + metadata = CoreMetadataHandler(schema).metadata + metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions) return _add_custom_serialization_from_json_encoders(self._config_wrapper.json_encoders, source_type, schema) - def _apply_single_annotation( - self, - schema: core_schema.CoreSchema, - metadata: Any, - check_unsupported_field_info_attributes: bool = True, - ) -> core_schema.CoreSchema: - FieldInfo = import_cached_field_info() + def _apply_single_annotation(self, schema: core_schema.CoreSchema, metadata: Any) -> core_schema.CoreSchema: + from ..fields import FieldInfo if isinstance(metadata, FieldInfo): - if ( - check_unsupported_field_info_attributes - # HACK: we don't want to emit the warning for `FieldInfo` subclasses, because FastAPI does weird manipulations - # with its subclasses and their annotations: - and type(metadata) is FieldInfo - ): - for attr, value in (unsupported_attributes := self._get_unsupported_field_info_attributes(metadata)): - warnings.warn( - f'The {attr!r} attribute with value {value!r} was provided to the `Field()` function, ' - f'which has no effect in the context it was used. {attr!r} is field-specific metadata, ' - 'and can only be attached to a model field using `Annotated` metadata or by assignment. ' - 'This may have happened because an `Annotated` type alias using the `type` statement was ' - 'used, or if the `Field()` function was attached to a single member of a union type.', - category=UnsupportedFieldAttributeWarning, - ) - - if ( - metadata.default_factory_takes_validated_data - and self.model_type_stack.get() is None - and 'defaut_factory' not in unsupported_attributes - ): - warnings.warn( - "A 'default_factory' taking validated data as an argument was provided to the `Field()` function, " - 'but no validated data is available in the context it was used.', - category=UnsupportedFieldAttributeWarning, - ) - for field_metadata in metadata.metadata: schema = self._apply_single_annotation(schema, field_metadata) @@ -2282,23 +1677,23 @@ class GenerateSchema: return schema original_schema = schema - ref = schema.get('ref') + ref = schema.get('ref', None) if ref is not None: schema = schema.copy() new_ref = ref + f'_{repr(metadata)}' - if (existing := self.defs.get_schema_from_ref(new_ref)) is not None: - return existing - schema['ref'] = new_ref # pyright: ignore[reportGeneralTypeIssues] + if new_ref in self.defs.definitions: + return self.defs.definitions[new_ref] + schema['ref'] = new_ref # type: ignore elif schema['type'] == 'definition-ref': ref = schema['schema_ref'] - if (referenced_schema := self.defs.get_schema_from_ref(ref)) is not None: - schema = referenced_schema.copy() + if ref in self.defs.definitions: + schema = self.defs.definitions[ref].copy() new_ref = ref + f'_{repr(metadata)}' - if (existing := self.defs.get_schema_from_ref(new_ref)) is not None: - return existing - schema['ref'] = new_ref # pyright: ignore[reportGeneralTypeIssues] + if new_ref in self.defs.definitions: + return self.defs.definitions[new_ref] + schema['ref'] = new_ref # type: ignore - maybe_updated_schema = _known_annotated_metadata.apply_known_metadata(metadata, schema) + maybe_updated_schema = _known_annotated_metadata.apply_known_metadata(metadata, schema.copy()) if maybe_updated_schema is not None: return maybe_updated_schema @@ -2307,63 +1702,42 @@ class GenerateSchema: def _apply_single_annotation_json_schema( self, schema: core_schema.CoreSchema, metadata: Any ) -> core_schema.CoreSchema: - FieldInfo = import_cached_field_info() + from ..fields import FieldInfo if isinstance(metadata, FieldInfo): for field_metadata in metadata.metadata: schema = self._apply_single_annotation_json_schema(schema, field_metadata) + json_schema_update: JsonSchemaValue = {} + if metadata.title: + json_schema_update['title'] = metadata.title + if metadata.description: + json_schema_update['description'] = metadata.description + if metadata.examples: + json_schema_update['examples'] = to_jsonable_python(metadata.examples) - pydantic_js_updates, pydantic_js_extra = _extract_json_schema_info_from_field_info(metadata) - core_metadata = schema.setdefault('metadata', {}) - update_core_metadata( - core_metadata, pydantic_js_updates=pydantic_js_updates, pydantic_js_extra=pydantic_js_extra - ) + json_schema_extra = metadata.json_schema_extra + if json_schema_update or json_schema_extra: + CoreMetadataHandler(schema).metadata.setdefault('pydantic_js_annotation_functions', []).append( + get_json_schema_update_func(json_schema_update, json_schema_extra) + ) return schema - def _get_unsupported_field_info_attributes(self, field_info: FieldInfo) -> list[tuple[str, Any]]: - """Get the list of unsupported `FieldInfo` attributes when not directly used in `Annotated` for field annotations.""" - unused_metadata: list[tuple[str, Any]] = [] - for unused_metadata_name, unset_value in UNSUPPORTED_STANDALONE_FIELDINFO_ATTRIBUTES: - if ( - (unused_metadata_value := getattr(field_info, unused_metadata_name)) is not unset_value - # `default` and `default_factory` can still be used with a type adapter, so only include them - # if used with a model-like class: - and ( - unused_metadata_name not in ('default', 'default_factory') - or self.model_type_stack.get() is not None - ) - # Setting `alias` will set `validation/serialization_alias` as well, so we want to avoid duplicate warnings: - and ( - unused_metadata_name not in ('validation_alias', 'serialization_alias') - or 'alias' not in field_info._attributes_set - ) - ): - unused_metadata.append((unused_metadata_name, unused_metadata_value)) - - return unused_metadata - def _get_wrapped_inner_schema( self, get_inner_schema: GetCoreSchemaHandler, annotation: Any, pydantic_js_annotation_functions: list[GetJsonSchemaFunction], - check_unsupported_field_info_attributes: bool = False, ) -> CallbackGetCoreSchemaHandler: - annotation_get_schema: GetCoreSchemaFunction | None = getattr(annotation, '__get_pydantic_core_schema__', None) + metadata_get_schema: GetCoreSchemaFunction = getattr(annotation, '__get_pydantic_core_schema__', None) or ( + lambda source, handler: handler(source) + ) def new_handler(source: Any) -> core_schema.CoreSchema: - if annotation_get_schema is not None: - schema = annotation_get_schema(source, get_inner_schema) - else: - schema = get_inner_schema(source) - schema = self._apply_single_annotation( - schema, - annotation, - check_unsupported_field_info_attributes=check_unsupported_field_info_attributes, - ) - schema = self._apply_single_annotation_json_schema(schema, annotation) + schema = metadata_get_schema(source, get_inner_schema) + schema = self._apply_single_annotation(schema, annotation) + schema = self._apply_single_annotation_json_schema(schema, annotation) - metadata_js_function = _extract_get_pydantic_json_schema(annotation) + metadata_js_function = _extract_get_pydantic_json_schema(annotation, schema) if metadata_js_function is not None: pydantic_js_annotation_functions.append(metadata_js_function) return schema @@ -2374,6 +1748,7 @@ class GenerateSchema: self, schema: core_schema.CoreSchema, serializers: list[Decorator[FieldSerializerDecoratorInfo]], + computed_field: bool = False, ) -> core_schema.CoreSchema: """Apply field serializers to a schema.""" if serializers: @@ -2382,25 +1757,23 @@ class GenerateSchema: inner_schema = schema['schema'] schema['schema'] = self._apply_field_serializers(inner_schema, serializers) return schema - elif 'ref' in schema: - schema = self.defs.create_definition_reference_schema(schema) + else: + ref = typing.cast('str|None', schema.get('ref', None)) + if ref is not None: + schema = core_schema.definition_reference_schema(ref) # use the last serializer to make it easy to override a serializer set on a parent model serializer = serializers[-1] - is_field_serializer, info_arg = inspect_field_serializer(serializer.func, serializer.info.mode) + is_field_serializer, info_arg = inspect_field_serializer( + serializer.func, serializer.info.mode, computed_field=computed_field + ) - if serializer.info.return_type is not PydanticUndefined: - return_type = serializer.info.return_type - else: - try: - # Do not pass in globals as the function could be defined in a different module. - # Instead, let `get_callable_return_type` infer the globals to use, but still pass - # in locals that may contain a parent/rebuild namespace: - return_type = _decorators.get_callable_return_type( - serializer.func, localns=self._types_namespace.locals - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e + try: + return_type = _decorators.get_function_return_type( + serializer.func, serializer.info.return_type, self._types_namespace + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e if return_type is PydanticUndefined: return_schema = None @@ -2435,19 +1808,12 @@ class GenerateSchema: serializer = list(serializers)[-1] info_arg = inspect_model_serializer(serializer.func, serializer.info.mode) - if serializer.info.return_type is not PydanticUndefined: - return_type = serializer.info.return_type - else: - try: - # Do not pass in globals as the function could be defined in a different module. - # Instead, let `get_callable_return_type` infer the globals to use, but still pass - # in locals that may contain a parent/rebuild namespace: - return_type = _decorators.get_callable_return_type( - serializer.func, localns=self._types_namespace.locals - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - + try: + return_type = _decorators.get_function_return_type( + serializer.func, serializer.info.return_type, self._types_namespace + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e if return_type is PydanticUndefined: return_schema = None else: @@ -2476,26 +1842,33 @@ class GenerateSchema: _VALIDATOR_F_MATCH: Mapping[ tuple[FieldValidatorModes, Literal['no-info', 'with-info']], - Callable[[Callable[..., Any], core_schema.CoreSchema], core_schema.CoreSchema], + Callable[[Callable[..., Any], core_schema.CoreSchema, str | None], core_schema.CoreSchema], ] = { - ('before', 'no-info'): lambda f, schema: core_schema.no_info_before_validator_function(f, schema), - ('after', 'no-info'): lambda f, schema: core_schema.no_info_after_validator_function(f, schema), - ('plain', 'no-info'): lambda f, _: core_schema.no_info_plain_validator_function(f), - ('wrap', 'no-info'): lambda f, schema: core_schema.no_info_wrap_validator_function(f, schema), - ('before', 'with-info'): lambda f, schema: core_schema.with_info_before_validator_function(f, schema), - ('after', 'with-info'): lambda f, schema: core_schema.with_info_after_validator_function(f, schema), - ('plain', 'with-info'): lambda f, _: core_schema.with_info_plain_validator_function(f), - ('wrap', 'with-info'): lambda f, schema: core_schema.with_info_wrap_validator_function(f, schema), + ('before', 'no-info'): lambda f, schema, _: core_schema.no_info_before_validator_function(f, schema), + ('after', 'no-info'): lambda f, schema, _: core_schema.no_info_after_validator_function(f, schema), + ('plain', 'no-info'): lambda f, _1, _2: core_schema.no_info_plain_validator_function(f), + ('wrap', 'no-info'): lambda f, schema, _: core_schema.no_info_wrap_validator_function(f, schema), + ('before', 'with-info'): lambda f, schema, field_name: core_schema.with_info_before_validator_function( + f, schema, field_name=field_name + ), + ('after', 'with-info'): lambda f, schema, field_name: core_schema.with_info_after_validator_function( + f, schema, field_name=field_name + ), + ('plain', 'with-info'): lambda f, _, field_name: core_schema.with_info_plain_validator_function( + f, field_name=field_name + ), + ('wrap', 'with-info'): lambda f, schema, field_name: core_schema.with_info_wrap_validator_function( + f, schema, field_name=field_name + ), } -# TODO V3: this function is only used for deprecated decorators. It should -# be removed once we drop support for those. def apply_validators( schema: core_schema.CoreSchema, validators: Iterable[Decorator[RootValidatorDecoratorInfo]] | Iterable[Decorator[ValidatorDecoratorInfo]] | Iterable[Decorator[FieldValidatorDecoratorInfo]], + field_name: str | None, ) -> core_schema.CoreSchema: """Apply validators to a schema. @@ -2508,12 +1881,10 @@ def apply_validators( The updated schema. """ for validator in validators: - # Actually, type could be 'field' or 'model', but this is only used for deprecated - # decorators, so let's not worry about it. - info_arg = inspect_validator(validator.func, mode=validator.info.mode, type='field') + info_arg = inspect_validator(validator.func, validator.info.mode) val_type = 'with-info' if info_arg else 'no-info' - schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema) + schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema, field_name) return schema @@ -2533,15 +1904,6 @@ def _validators_require_validate_default(validators: Iterable[Decorator[Validato return False -def _convert_to_aliases( - alias: str | AliasChoices | AliasPath | None, -) -> str | list[str | int] | list[list[str | int]] | None: - if isinstance(alias, (AliasChoices, AliasPath)): - return alias.convert_to_aliases() - else: - return alias - - def apply_model_validators( schema: core_schema.CoreSchema, validators: Iterable[Decorator[ModelValidatorDecoratorInfo]], @@ -2567,7 +1929,7 @@ def apply_model_validators( continue if mode == 'outer' and validator.info.mode == 'before': continue - info_arg = inspect_validator(validator.func, mode=validator.info.mode, type='model') + info_arg = inspect_validator(validator.func, validator.info.mode) if validator.info.mode == 'wrap': if info_arg: schema = core_schema.with_info_wrap_validator_function(function=validator.func, schema=schema) @@ -2601,10 +1963,7 @@ def wrap_default(field_info: FieldInfo, schema: core_schema.CoreSchema) -> core_ """ if field_info.default_factory: return core_schema.with_default_schema( - schema, - default_factory=field_info.default_factory, - default_factory_takes_data=takes_validated_data_argument(field_info.default_factory), - validate_default=field_info.validate_default, + schema, default_factory=field_info.default_factory, validate_default=field_info.validate_default ) elif field_info.default is not PydanticUndefined: return core_schema.with_default_schema( @@ -2614,31 +1973,29 @@ def wrap_default(field_info: FieldInfo, schema: core_schema.CoreSchema) -> core_ return schema -def _extract_get_pydantic_json_schema(tp: Any) -> GetJsonSchemaFunction | None: +def _extract_get_pydantic_json_schema(tp: Any, schema: CoreSchema) -> GetJsonSchemaFunction | None: """Extract `__get_pydantic_json_schema__` from a type, handling the deprecated `__modify_schema__`.""" js_modify_function = getattr(tp, '__get_pydantic_json_schema__', None) if hasattr(tp, '__modify_schema__'): - BaseModel = import_cached_base_model() + from pydantic import BaseModel # circular reference has_custom_v2_modify_js_func = ( js_modify_function is not None - and BaseModel.__get_pydantic_json_schema__.__func__ # type: ignore + and BaseModel.__get_pydantic_json_schema__.__func__ not in (js_modify_function, getattr(js_modify_function, '__func__', None)) ) if not has_custom_v2_modify_js_func: - cls_name = getattr(tp, '__name__', None) raise PydanticUserError( - f'The `__modify_schema__` method is not supported in Pydantic v2. ' - f'Use `__get_pydantic_json_schema__` instead{f" in class `{cls_name}`" if cls_name else ""}.', + 'The `__modify_schema__` method is not supported in Pydantic v2. ' + 'Use `__get_pydantic_json_schema__` instead.', code='custom-json-schema', ) - if (origin := get_origin(tp)) is not None: - # Generic aliases proxy attribute access to the origin, *except* dunder attributes, - # such as `__get_pydantic_json_schema__`, hence the explicit check. - return _extract_get_pydantic_json_schema(origin) + # handle GenericAlias' but ignore Annotated which "lies" about its origin (in this case it would be `int`) + if hasattr(tp, '__origin__') and not isinstance(tp, type(Annotated[int, 'placeholder'])): + return _extract_get_pydantic_json_schema(tp.__origin__, schema) if js_modify_function is None: return None @@ -2646,62 +2003,65 @@ def _extract_get_pydantic_json_schema(tp: Any) -> GetJsonSchemaFunction | None: return js_modify_function -def resolve_original_schema(schema: CoreSchema, definitions: _Definitions) -> CoreSchema | None: - if schema['type'] == 'definition-ref': - return definitions.get_schema_from_ref(schema['schema_ref']) - elif schema['type'] == 'definitions': - return schema['schema'] - else: - return schema +def get_json_schema_update_func( + json_schema_update: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None +) -> GetJsonSchemaFunction: + def json_schema_update_func( + core_schema_or_field: CoreSchemaOrField, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + json_schema = {**handler(core_schema_or_field), **json_schema_update} + add_json_schema_extra(json_schema, json_schema_extra) + return json_schema + + return json_schema_update_func -def _inlining_behavior( - def_ref: core_schema.DefinitionReferenceSchema, -) -> Literal['inline', 'keep', 'preserve_metadata']: - """Determine the inlining behavior of the `'definition-ref'` schema. +def add_json_schema_extra( + json_schema: JsonSchemaValue, json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None +): + if isinstance(json_schema_extra, dict): + json_schema.update(to_jsonable_python(json_schema_extra)) + elif callable(json_schema_extra): + json_schema_extra(json_schema) - - If no `'serialization'` schema and no metadata is attached, the schema can safely be inlined. - - If it has metadata but only related to the deferred discriminator application, it can be inlined - provided that such metadata is kept. - - Otherwise, the schema should not be inlined. Doing so would remove the `'serialization'` schema or metadata. - """ - if 'serialization' in def_ref: - return 'keep' - metadata = def_ref.get('metadata') - if not metadata: - return 'inline' - if len(metadata) == 1 and 'pydantic_internal_union_discriminator' in metadata: - return 'preserve_metadata' - return 'keep' + +class _CommonField(TypedDict): + schema: core_schema.CoreSchema + validation_alias: str | list[str | int] | list[list[str | int]] | None + serialization_alias: str | None + serialization_exclude: bool | None + frozen: bool | None + metadata: dict[str, Any] + + +def _common_field( + schema: core_schema.CoreSchema, + *, + validation_alias: str | list[str | int] | list[list[str | int]] | None = None, + serialization_alias: str | None = None, + serialization_exclude: bool | None = None, + frozen: bool | None = None, + metadata: Any = None, +) -> _CommonField: + return { + 'schema': schema, + 'validation_alias': validation_alias, + 'serialization_alias': serialization_alias, + 'serialization_exclude': serialization_exclude, + 'frozen': frozen, + 'metadata': metadata, + } class _Definitions: """Keeps track of references and definitions.""" - _recursively_seen: set[str] - """A set of recursively seen references. - - When a referenceable type is encountered, the `get_schema_or_ref` context manager is - entered to compute the reference. If the type references itself by some way (e.g. for - a dataclass a Pydantic model, the class can be referenced as a field annotation), - entering the context manager again will yield a `'definition-ref'` schema that should - short-circuit the normal generation process, as the reference was already in this set. - """ - - _definitions: dict[str, core_schema.CoreSchema] - """A mapping of references to their corresponding schema. - - When a schema for a referenceable type is generated, it is stored in this mapping. If the - same type is encountered again, the reference is yielded by the `get_schema_or_ref` context - manager. - """ - def __init__(self) -> None: - self._recursively_seen = set() - self._definitions = {} + self.seen: set[str] = set() + self.definitions: dict[str, core_schema.CoreSchema] = {} @contextmanager - def get_schema_or_ref(self, tp: Any, /) -> Generator[tuple[str, core_schema.DefinitionReferenceSchema | None]]: + def get_schema_or_ref(self, tp: Any) -> Iterator[tuple[str, None] | tuple[str, CoreSchema]]: """Get a definition for `tp` if one exists. If a definition exists, a tuple of `(ref_string, CoreSchema)` is returned. @@ -2715,119 +2075,31 @@ class _Definitions: At present the following types can be named/recursive: - - Pydantic model - - Pydantic and stdlib dataclasses - - Typed dictionaries - - Named tuples - - `TypeAliasType` instances - - Enums + - BaseModel + - Dataclasses + - TypedDict + - TypeAliasType """ ref = get_type_ref(tp) - # return the reference if we're either (1) in a cycle or (2) it the reference was already encountered: - if ref in self._recursively_seen or ref in self._definitions: + # return the reference if we're either (1) in a cycle or (2) it was already defined + if ref in self.seen or ref in self.definitions: yield (ref, core_schema.definition_reference_schema(ref)) else: - self._recursively_seen.add(ref) + self.seen.add(ref) try: yield (ref, None) finally: - self._recursively_seen.discard(ref) + self.seen.discard(ref) - def get_schema_from_ref(self, ref: str) -> CoreSchema | None: - """Resolve the schema from the given reference.""" - return self._definitions.get(ref) - def create_definition_reference_schema(self, schema: CoreSchema) -> core_schema.DefinitionReferenceSchema: - """Store the schema as a definition and return a `'definition-reference'` schema pointing to it. - - The schema must have a reference attached to it. - """ - ref = schema['ref'] # pyright: ignore - self._definitions[ref] = schema - return core_schema.definition_reference_schema(ref) - - def unpack_definitions(self, schema: core_schema.DefinitionsSchema) -> CoreSchema: - """Store the definitions of the `'definitions'` core schema and return the inner core schema.""" - for def_schema in schema['definitions']: - self._definitions[def_schema['ref']] = def_schema # pyright: ignore +def resolve_original_schema(schema: CoreSchema, definitions: dict[str, CoreSchema]) -> CoreSchema | None: + if schema['type'] == 'definition-ref': + return definitions.get(schema['schema_ref'], None) + elif schema['type'] == 'definitions': return schema['schema'] - - def finalize_schema(self, schema: CoreSchema) -> CoreSchema: - """Finalize the core schema. - - This traverses the core schema and referenced definitions, replaces `'definition-ref'` schemas - by the referenced definition if possible, and applies deferred discriminators. - """ - definitions = self._definitions - try: - gather_result = gather_schemas_for_cleaning( - schema, - definitions=definitions, - ) - except MissingDefinitionError as e: - raise InvalidSchemaError from e - - remaining_defs: dict[str, CoreSchema] = {} - - # Note: this logic doesn't play well when core schemas with deferred discriminator metadata - # and references are encountered. See the `test_deferred_discriminated_union_and_references()` test. - for ref, inlinable_def_ref in gather_result['collected_references'].items(): - if inlinable_def_ref is not None and (inlining_behavior := _inlining_behavior(inlinable_def_ref)) != 'keep': - if inlining_behavior == 'inline': - # `ref` was encountered, and only once: - # - `inlinable_def_ref` is a `'definition-ref'` schema and is guaranteed to be - # the only one. Transform it into the definition it points to. - # - Do not store the definition in the `remaining_defs`. - inlinable_def_ref.clear() # pyright: ignore[reportAttributeAccessIssue] - inlinable_def_ref.update(self._resolve_definition(ref, definitions)) # pyright: ignore - elif inlining_behavior == 'preserve_metadata': - # `ref` was encountered, and only once, but contains discriminator metadata. - # We will do the same thing as if `inlining_behavior` was `'inline'`, but make - # sure to keep the metadata for the deferred discriminator application logic below. - meta = inlinable_def_ref.pop('metadata') - inlinable_def_ref.clear() # pyright: ignore[reportAttributeAccessIssue] - inlinable_def_ref.update(self._resolve_definition(ref, definitions)) # pyright: ignore - inlinable_def_ref['metadata'] = meta - else: - # `ref` was encountered, at least two times (or only once, but with metadata or a serialization schema): - # - Do not inline the `'definition-ref'` schemas (they are not provided in the gather result anyway). - # - Store the the definition in the `remaining_defs` - remaining_defs[ref] = self._resolve_definition(ref, definitions) - - for cs in gather_result['deferred_discriminator_schemas']: - discriminator: str | None = cs['metadata'].pop('pydantic_internal_union_discriminator', None) # pyright: ignore[reportTypedDictNotRequiredAccess] - if discriminator is None: - # This can happen in rare scenarios, when a deferred schema is present multiple times in the - # gather result (e.g. when using the `Sequence` type -- see `test_sequence_discriminated_union()`). - # In this case, a previous loop iteration applied the discriminator and so we can just skip it here. - continue - applied = _discriminated_union.apply_discriminator(cs.copy(), discriminator, remaining_defs) - # Mutate the schema directly to have the discriminator applied - cs.clear() # pyright: ignore[reportAttributeAccessIssue] - cs.update(applied) # pyright: ignore - - if remaining_defs: - schema = core_schema.definitions_schema(schema=schema, definitions=[*remaining_defs.values()]) + else: return schema - def _resolve_definition(self, ref: str, definitions: dict[str, CoreSchema]) -> CoreSchema: - definition = definitions[ref] - if definition['type'] != 'definition-ref': - return definition - - # Some `'definition-ref'` schemas might act as "intermediate" references (e.g. when using - # a PEP 695 type alias (which is referenceable) that references another PEP 695 type alias): - visited: set[str] = set() - while definition['type'] == 'definition-ref' and _inlining_behavior(definition) == 'inline': - schema_ref = definition['schema_ref'] - if schema_ref in visited: - raise PydanticUserError( - f'{ref} contains a circular reference to itself.', code='circular-reference-schema' - ) - visited.add(schema_ref) - definition = definitions[schema_ref] - return {**definition, 'ref': ref} # pyright: ignore[reportReturnType] - class _FieldNameStack: __slots__ = ('_stack',) @@ -2848,20 +2120,85 @@ class _FieldNameStack: return None -class _ModelTypeStack: - __slots__ = ('_stack',) +def generate_pydantic_signature( + init: Callable[..., None], + fields: dict[str, FieldInfo], + config_wrapper: ConfigWrapper, + post_process_parameter: Callable[[Parameter], Parameter] = lambda x: x, +) -> inspect.Signature: + """Generate signature for a pydantic class generated by inheriting from BaseModel or + using the dataclass annotation - def __init__(self) -> None: - self._stack: list[type] = [] + Args: + init: The class init. + fields: The model fields. + config_wrapper: The config wrapper instance. + post_process_parameter: Optional additional processing for parameter - @contextmanager - def push(self, type_obj: type) -> Iterator[None]: - self._stack.append(type_obj) - yield - self._stack.pop() + Returns: + The dataclass/BaseModel subclass signature. + """ + from itertools import islice - def get(self) -> type | None: - if self._stack: - return self._stack[-1] + present_params = signature(init).parameters.values() + merged_params: dict[str, Parameter] = {} + var_kw = None + use_var_kw = False + + for param in islice(present_params, 1, None): # skip self arg + # inspect does "clever" things to show annotations as strings because we have + # `from __future__ import annotations` in main, we don't want that + if param.annotation == 'Any': + param = param.replace(annotation=Any) + if param.kind is param.VAR_KEYWORD: + var_kw = param + continue + merged_params[param.name] = post_process_parameter(param) + + if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through + allow_names = config_wrapper.populate_by_name + for field_name, field in fields.items(): + # when alias is a str it should be used for signature generation + if isinstance(field.alias, str): + param_name = field.alias + else: + param_name = field_name + + if field_name in merged_params or param_name in merged_params: + continue + + if not is_valid_identifier(param_name): + if allow_names and is_valid_identifier(field_name): + param_name = field_name + else: + use_var_kw = True + continue + + kwargs = {} if field.is_required() else {'default': field.get_default(call_default_factory=False)} + merged_params[param_name] = post_process_parameter( + Parameter(param_name, Parameter.KEYWORD_ONLY, annotation=field.rebuild_annotation(), **kwargs) + ) + + if config_wrapper.extra == 'allow': + use_var_kw = True + + if var_kw and use_var_kw: + # Make sure the parameter for extra kwargs + # does not have the same name as a field + default_model_signature = [ + ('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD), + ('data', Parameter.VAR_KEYWORD), + ] + if [(p.name, p.kind) for p in present_params] == default_model_signature: + # if this is the standard model signature, use extra_data as the extra args name + var_kw_name = 'extra_data' else: - return None + # else start from var_kw + var_kw_name = var_kw.name + + # generate a name that's definitely unique + while var_kw_name in fields: + var_kw_name += '_' + merged_params[var_kw_name] = post_process_parameter(var_kw.replace(name=var_kw_name)) + + return inspect.Signature(parameters=list(merged_params.values()), return_annotation=None) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generics.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generics.py index f9f88a67..e93be9f4 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generics.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_generics.py @@ -1,32 +1,29 @@ from __future__ import annotations -import operator import sys import types import typing from collections import ChainMap -from collections.abc import Iterator, Mapping from contextlib import contextmanager from contextvars import ContextVar -from functools import reduce -from itertools import zip_longest from types import prepare_class -from typing import TYPE_CHECKING, Annotated, Any, TypedDict, TypeVar, cast +from typing import TYPE_CHECKING, Any, Iterator, List, Mapping, MutableMapping, Tuple, TypeVar from weakref import WeakValueDictionary import typing_extensions -from typing_inspection import typing_objects -from typing_inspection.introspection import is_union_origin -from . import _typing_extra from ._core_utils import get_type_ref from ._forward_ref import PydanticRecursiveRef +from ._typing_extra import TypeVarType, typing_base from ._utils import all_identical, is_model_class +if sys.version_info >= (3, 10): + from typing import _UnionGenericAlias # type: ignore[attr-defined] + if TYPE_CHECKING: from ..main import BaseModel -GenericTypesCacheKey = tuple[Any, Any, tuple[Any, ...]] +GenericTypesCacheKey = Tuple[Any, Any, Tuple[Any, ...]] # Note: We want to remove LimitedDict, but to do this, we'd need to improve the handling of generics caching. # Right now, to handle recursive generics, we some types must remain cached for brief periods without references. @@ -37,25 +34,43 @@ GenericTypesCacheKey = tuple[Any, Any, tuple[Any, ...]] KT = TypeVar('KT') VT = TypeVar('VT') _LIMITED_DICT_SIZE = 100 +if TYPE_CHECKING: + class LimitedDict(dict, MutableMapping[KT, VT]): + def __init__(self, size_limit: int = _LIMITED_DICT_SIZE): + ... -class LimitedDict(dict[KT, VT]): - def __init__(self, size_limit: int = _LIMITED_DICT_SIZE) -> None: - self.size_limit = size_limit - super().__init__() +else: - def __setitem__(self, key: KT, value: VT, /) -> None: - super().__setitem__(key, value) - if len(self) > self.size_limit: - excess = len(self) - self.size_limit + self.size_limit // 10 - to_remove = list(self.keys())[:excess] - for k in to_remove: - del self[k] + class LimitedDict(dict): + """Limit the size/length of a dict used for caching to avoid unlimited increase in memory usage. + + Since the dict is ordered, and we always remove elements from the beginning, this is effectively a FIFO cache. + """ + + def __init__(self, size_limit: int = _LIMITED_DICT_SIZE): + self.size_limit = size_limit + super().__init__() + + def __setitem__(self, __key: Any, __value: Any) -> None: + super().__setitem__(__key, __value) + if len(self) > self.size_limit: + excess = len(self) - self.size_limit + self.size_limit // 10 + to_remove = list(self.keys())[:excess] + for key in to_remove: + del self[key] + + def __class_getitem__(cls, *args: Any) -> Any: + # to avoid errors with 3.7 + return cls # weak dictionaries allow the dynamically created parametrized versions of generic models to get collected # once they are no longer referenced by the caller. -GenericTypesCache = WeakValueDictionary[GenericTypesCacheKey, 'type[BaseModel]'] +if sys.version_info >= (3, 9): # Typing for weak dictionaries available at 3.9 + GenericTypesCache = WeakValueDictionary[GenericTypesCacheKey, 'type[BaseModel]'] +else: + GenericTypesCache = WeakValueDictionary if TYPE_CHECKING: @@ -93,13 +108,13 @@ else: # and discover later on that we need to re-add all this infrastructure... # _GENERIC_TYPES_CACHE = DeepChainMap(GenericTypesCache(), LimitedDict()) -_GENERIC_TYPES_CACHE: ContextVar[GenericTypesCache | None] = ContextVar('_GENERIC_TYPES_CACHE', default=None) +_GENERIC_TYPES_CACHE = GenericTypesCache() -class PydanticGenericMetadata(TypedDict): +class PydanticGenericMetadata(typing_extensions.TypedDict): origin: type[BaseModel] | None # analogous to typing._GenericAlias.__origin__ args: tuple[Any, ...] # analogous to typing._GenericAlias.__args__ - parameters: tuple[TypeVar, ...] # analogous to typing.Generic.__parameters__ + parameters: tuple[type[Any], ...] # analogous to typing.Generic.__parameters__ def create_generic_submodel( @@ -156,7 +171,7 @@ def _get_caller_frame_info(depth: int = 2) -> tuple[str | None, bool]: depth: The depth to get the frame. Returns: - A tuple contains `module_name` and `called_globally`. + A tuple contains `module_nam` and `called_globally`. Raises: RuntimeError: If the function is not called inside a function. @@ -174,7 +189,7 @@ def _get_caller_frame_info(depth: int = 2) -> tuple[str | None, bool]: DictValues: type[Any] = {}.values().__class__ -def iter_contained_typevars(v: Any) -> Iterator[TypeVar]: +def iter_contained_typevars(v: Any) -> Iterator[TypeVarType]: """Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found. This is inspired as an alternative to directly accessing the `__parameters__` attribute of a GenericAlias, @@ -207,7 +222,7 @@ def get_origin(v: Any) -> Any: return typing_extensions.get_origin(v) -def get_standard_typevars_map(cls: Any) -> dict[TypeVar, Any] | None: +def get_standard_typevars_map(cls: type[Any]) -> dict[TypeVarType, Any] | None: """Package a generic type's typevars and parametrization (if present) into a dictionary compatible with the `replace_types` function. Specifically, this works with standard typing generics and typing._GenericAlias. """ @@ -220,11 +235,11 @@ def get_standard_typevars_map(cls: Any) -> dict[TypeVar, Any] | None: # In this case, we know that cls is a _GenericAlias, and origin is the generic type # So it is safe to access cls.__args__ and origin.__parameters__ args: tuple[Any, ...] = cls.__args__ # type: ignore - parameters: tuple[TypeVar, ...] = origin.__parameters__ + parameters: tuple[TypeVarType, ...] = origin.__parameters__ return dict(zip(parameters, args)) -def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVar, Any]: +def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVarType, Any] | None: """Package a generic BaseModel's typevars and concrete parametrization (if present) into a dictionary compatible with the `replace_types` function. @@ -236,13 +251,10 @@ def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVar, Any]: generic_metadata = cls.__pydantic_generic_metadata__ origin = generic_metadata['origin'] args = generic_metadata['args'] - if not args: - # No need to go into `iter_contained_typevars`: - return {} return dict(zip(iter_contained_typevars(origin), args)) -def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: +def replace_types(type_: Any, type_map: Mapping[Any, Any] | None) -> Any: """Return type with all occurrences of `type_map` keys recursively replaced with their values. Args: @@ -254,13 +266,13 @@ def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: `typevar_map` keys recursively replaced. Example: - ```python - from typing import Union + ```py + from typing import List, Tuple, Union from pydantic._internal._generics import replace_types - replace_types(tuple[str, Union[list[str], float]], {str: int}) - #> tuple[int, Union[list[int], float]] + replace_types(Tuple[str, Union[List[str], float]], {str: int}) + #> Tuple[int, Union[List[int], float]] ``` """ if not type_map: @@ -269,25 +281,25 @@ def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: type_args = get_args(type_) origin_type = get_origin(type_) - if typing_objects.is_annotated(origin_type): + if origin_type is typing_extensions.Annotated: annotated_type, *annotations = type_args - annotated_type = replace_types(annotated_type, type_map) - # TODO remove parentheses when we drop support for Python 3.10: - return Annotated[(annotated_type, *annotations)] + annotated = replace_types(annotated_type, type_map) + for annotation in annotations: + annotated = typing_extensions.Annotated[annotated, annotation] + return annotated - # Having type args is a good indicator that this is a typing special form - # instance or a generic alias of some sort. + # Having type args is a good indicator that this is a typing module + # class instantiation or a generic alias of some sort. if type_args: resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args) if all_identical(type_args, resolved_type_args): # If all arguments are the same, there is no need to modify the # type or create a new object at all return type_ - if ( origin_type is not None - and isinstance(type_, _typing_extra.typing_base) - and not isinstance(origin_type, _typing_extra.typing_base) + and isinstance(type_, typing_base) + and not isinstance(origin_type, typing_base) and getattr(type_, '_name', None) is not None ): # In python < 3.9 generic aliases don't exist so any of these like `list`, @@ -295,22 +307,10 @@ def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: # See: https://www.python.org/dev/peps/pep-0585 origin_type = getattr(typing, type_._name) assert origin_type is not None - - if is_union_origin(origin_type): - if any(typing_objects.is_any(arg) for arg in resolved_type_args): - # `Any | T` ~ `Any`: - resolved_type_args = (Any,) - # `Never | T` ~ `T`: - resolved_type_args = tuple( - arg - for arg in resolved_type_args - if not (typing_objects.is_noreturn(arg) or typing_objects.is_never(arg)) - ) - # PEP-604 syntax (Ex.: list | str) is represented with a types.UnionType object that does not have __getitem__. # We also cannot use isinstance() since we have to compare types. if sys.version_info >= (3, 10) and origin_type is types.UnionType: - return reduce(operator.or_, resolved_type_args) + return _UnionGenericAlias(origin_type, resolved_type_args) # NotRequired[T] and Required[T] don't support tuple type resolved_type_args, hence the condition below return origin_type[resolved_type_args[0] if len(resolved_type_args) == 1 else resolved_type_args] @@ -328,8 +328,8 @@ def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: # Handle special case for typehints that can have lists as arguments. # `typing.Callable[[int, str], int]` is an example for this. - if isinstance(type_, list): - resolved_list = [replace_types(element, type_map) for element in type_] + if isinstance(type_, (List, list)): + resolved_list = list(replace_types(element, type_map) for element in type_) if all_identical(type_, resolved_list): return type_ return resolved_list @@ -339,57 +339,49 @@ def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any: return type_map.get(type_, type_) -def map_generic_model_arguments(cls: type[BaseModel], args: tuple[Any, ...]) -> dict[TypeVar, Any]: - """Return a mapping between the parameters of a generic model and the provided arguments during parameterization. +def has_instance_in_type(type_: Any, isinstance_target: Any) -> bool: + """Checks if the type, or any of its arbitrary nested args, satisfy + `isinstance(, isinstance_target)`. + """ + if isinstance(type_, isinstance_target): + return True + + type_args = get_args(type_) + origin_type = get_origin(type_) + + if origin_type is typing_extensions.Annotated: + annotated_type, *annotations = type_args + return has_instance_in_type(annotated_type, isinstance_target) + + # Having type args is a good indicator that this is a typing module + # class instantiation or a generic alias of some sort. + if any(has_instance_in_type(a, isinstance_target) for a in type_args): + return True + + # Handle special case for typehints that can have lists as arguments. + # `typing.Callable[[int, str], int]` is an example for this. + if isinstance(type_, (List, list)) and not isinstance(type_, typing_extensions.ParamSpec): + if any(has_instance_in_type(element, isinstance_target) for element in type_): + return True + + return False + + +def check_parameters_count(cls: type[BaseModel], parameters: tuple[Any, ...]) -> None: + """Check the generic model parameters count is equal. + + Args: + cls: The generic model. + parameters: A tuple of passed parameters to the generic model. Raises: - TypeError: If the number of arguments does not match the parameters (i.e. if providing too few or too many arguments). - - Example: - ```python {test="skip" lint="skip"} - class Model[T, U, V = int](BaseModel): ... - - map_generic_model_arguments(Model, (str, bytes)) - #> {T: str, U: bytes, V: int} - - map_generic_model_arguments(Model, (str,)) - #> TypeError: Too few arguments for ; actual 1, expected at least 2 - - map_generic_model_arguments(Model, (str, bytes, int, complex)) - #> TypeError: Too many arguments for ; actual 4, expected 3 - ``` - - Note: - This function is analogous to the private `typing._check_generic_specialization` function. + TypeError: If the passed parameters count is not equal to generic model parameters count. """ - parameters = cls.__pydantic_generic_metadata__['parameters'] - expected_len = len(parameters) - typevars_map: dict[TypeVar, Any] = {} - - _missing = object() - for parameter, argument in zip_longest(parameters, args, fillvalue=_missing): - if parameter is _missing: - raise TypeError(f'Too many arguments for {cls}; actual {len(args)}, expected {expected_len}') - - if argument is _missing: - param = cast(TypeVar, parameter) - try: - has_default = param.has_default() # pyright: ignore[reportAttributeAccessIssue] - except AttributeError: - # Happens if using `typing.TypeVar` (and not `typing_extensions`) on Python < 3.13. - has_default = False - if has_default: - # The default might refer to other type parameters. For an example, see: - # https://typing.python.org/en/latest/spec/generics.html#type-parameters-as-parameters-to-generics - typevars_map[param] = replace_types(param.__default__, typevars_map) # pyright: ignore[reportAttributeAccessIssue] - else: - expected_len -= sum(hasattr(p, 'has_default') and p.has_default() for p in parameters) # pyright: ignore[reportAttributeAccessIssue] - raise TypeError(f'Too few arguments for {cls}; actual {len(args)}, expected at least {expected_len}') - else: - param = cast(TypeVar, parameter) - typevars_map[param] = argument - - return typevars_map + actual = len(parameters) + expected = len(cls.__pydantic_generic_metadata__['parameters']) + if actual != expected: + description = 'many' if actual > expected else 'few' + raise TypeError(f'Too {description} parameters for {cls}; actual {actual}, expected {expected}') _generic_recursion_cache: ContextVar[set[str] | None] = ContextVar('_generic_recursion_cache', default=None) @@ -420,8 +412,7 @@ def generic_recursion_self_type( yield self_type else: previously_seen_type_refs.add(type_ref) - yield - previously_seen_type_refs.remove(type_ref) + yield None finally: if token: _generic_recursion_cache.reset(token) @@ -452,24 +443,14 @@ def get_cached_generic_type_early(parent: type[BaseModel], typevar_values: Any) during validation, I think it is worthwhile to ensure that types that are functionally equivalent are actually equal. """ - generic_types_cache = _GENERIC_TYPES_CACHE.get() - if generic_types_cache is None: - generic_types_cache = GenericTypesCache() - _GENERIC_TYPES_CACHE.set(generic_types_cache) - return generic_types_cache.get(_early_cache_key(parent, typevar_values)) + return _GENERIC_TYPES_CACHE.get(_early_cache_key(parent, typevar_values)) def get_cached_generic_type_late( parent: type[BaseModel], typevar_values: Any, origin: type[BaseModel], args: tuple[Any, ...] ) -> type[BaseModel] | None: """See the docstring of `get_cached_generic_type_early` for more information about the two-stage cache lookup.""" - generic_types_cache = _GENERIC_TYPES_CACHE.get() - if ( - generic_types_cache is None - ): # pragma: no cover (early cache is guaranteed to run first and initialize the cache) - generic_types_cache = GenericTypesCache() - _GENERIC_TYPES_CACHE.set(generic_types_cache) - cached = generic_types_cache.get(_late_cache_key(origin, args, typevar_values)) + cached = _GENERIC_TYPES_CACHE.get(_late_cache_key(origin, args, typevar_values)) if cached is not None: set_cached_generic_type(parent, typevar_values, cached, origin, args) return cached @@ -485,17 +466,11 @@ def set_cached_generic_type( """See the docstring of `get_cached_generic_type_early` for more information about why items are cached with two different keys. """ - generic_types_cache = _GENERIC_TYPES_CACHE.get() - if ( - generic_types_cache is None - ): # pragma: no cover (cache lookup is guaranteed to run first and initialize the cache) - generic_types_cache = GenericTypesCache() - _GENERIC_TYPES_CACHE.set(generic_types_cache) - generic_types_cache[_early_cache_key(parent, typevar_values)] = type_ + _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values)] = type_ if len(typevar_values) == 1: - generic_types_cache[_early_cache_key(parent, typevar_values[0])] = type_ + _GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values[0])] = type_ if origin and args: - generic_types_cache[_late_cache_key(origin, args, typevar_values)] = type_ + _GENERIC_TYPES_CACHE[_late_cache_key(origin, args, typevar_values)] = type_ def _union_orderings_key(typevar_values: Any) -> Any: @@ -512,8 +487,11 @@ def _union_orderings_key(typevar_values: Any) -> Any: (See https://github.com/python/cpython/issues/86483 for reference.) """ if isinstance(typevar_values, tuple): - return tuple(_union_orderings_key(value) for value in typevar_values) - elif typing_objects.is_union(typing_extensions.get_origin(typevar_values)): + args_data = [] + for value in typevar_values: + args_data.append(_union_orderings_key(value)) + return tuple(args_data) + elif typing_extensions.get_origin(typevar_values) is typing.Union: return get_args(typevar_values) else: return () diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_git.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_git.py deleted file mode 100644 index 96dcda28..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_git.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Git utilities, adopted from mypy's git utilities (https://github.com/python/mypy/blob/master/mypy/git.py).""" - -from __future__ import annotations - -import subprocess -from pathlib import Path - - -def is_git_repo(dir: Path) -> bool: - """Is the given directory version-controlled with git?""" - return dir.joinpath('.git').exists() - - -def have_git() -> bool: # pragma: no cover - """Can we run the git executable?""" - try: - subprocess.check_output(['git', '--help']) - return True - except subprocess.CalledProcessError: - return False - except OSError: - return False - - -def git_revision(dir: Path) -> str: - """Get the SHA-1 of the HEAD of a git repository.""" - return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=dir).decode('utf-8').strip() diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_import_utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_import_utils.py deleted file mode 100644 index 638102f7..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_import_utils.py +++ /dev/null @@ -1,20 +0,0 @@ -from functools import cache -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pydantic import BaseModel - from pydantic.fields import FieldInfo - - -@cache -def import_cached_base_model() -> type['BaseModel']: - from pydantic import BaseModel - - return BaseModel - - -@cache -def import_cached_field_info() -> type['FieldInfo']: - from pydantic.fields import FieldInfo - - return FieldInfo diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_internal_dataclass.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_internal_dataclass.py index 33e152cc..317a3d9c 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_internal_dataclass.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_internal_dataclass.py @@ -1,4 +1,7 @@ import sys +from typing import Any, Dict + +dataclass_kwargs: Dict[str, Any] # `slots` is available on Python >= 3.10 if sys.version_info >= (3, 10): diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_known_annotated_metadata.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_known_annotated_metadata.py index 7d61f4ab..77caf705 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_known_annotated_metadata.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_known_annotated_metadata.py @@ -1,48 +1,35 @@ from __future__ import annotations from collections import defaultdict -from collections.abc import Iterable from copy import copy -from functools import lru_cache, partial -from typing import TYPE_CHECKING, Any +from functools import partial +from typing import TYPE_CHECKING, Any, Callable, Iterable -from pydantic_core import CoreSchema, PydanticCustomError, ValidationError, to_jsonable_python +from pydantic_core import CoreSchema, PydanticCustomError, to_jsonable_python from pydantic_core import core_schema as cs from ._fields import PydanticMetadata -from ._import_utils import import_cached_field_info if TYPE_CHECKING: - pass + from ..annotated_handlers import GetJsonSchemaHandler + STRICT = {'strict'} -FAIL_FAST = {'fail_fast'} -LENGTH_CONSTRAINTS = {'min_length', 'max_length'} +SEQUENCE_CONSTRAINTS = {'min_length', 'max_length'} INEQUALITY = {'le', 'ge', 'lt', 'gt'} -NUMERIC_CONSTRAINTS = {'multiple_of', *INEQUALITY} -ALLOW_INF_NAN = {'allow_inf_nan'} +NUMERIC_CONSTRAINTS = {'multiple_of', 'allow_inf_nan', *INEQUALITY} -STR_CONSTRAINTS = { - *LENGTH_CONSTRAINTS, - *STRICT, - 'strip_whitespace', - 'to_lower', - 'to_upper', - 'pattern', - 'coerce_numbers_to_str', -} -BYTES_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} +STR_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT, 'strip_whitespace', 'to_lower', 'to_upper', 'pattern'} +BYTES_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} -LIST_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} -TUPLE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} -SET_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST} -DICT_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} -GENERATOR_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT} -SEQUENCE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *FAIL_FAST} +LIST_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} +TUPLE_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} +SET_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} +DICT_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} +GENERATOR_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} -FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT} -DECIMAL_CONSTRAINTS = {'max_digits', 'decimal_places', *FLOAT_CONSTRAINTS} -INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT} +FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} +INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} BOOL_CONSTRAINTS = STRICT UUID_CONSTRAINTS = STRICT @@ -50,8 +37,6 @@ DATE_TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} TIMEDELTA_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} LAX_OR_STRICT_CONSTRAINTS = STRICT -ENUM_CONSTRAINTS = STRICT -COMPLEX_CONSTRAINTS = STRICT UNION_CONSTRAINTS = {'union_mode'} URL_CONSTRAINTS = { @@ -68,33 +53,58 @@ SEQUENCE_SCHEMA_TYPES = ('list', 'tuple', 'set', 'frozenset', 'generator', *TEXT NUMERIC_SCHEMA_TYPES = ('float', 'int', 'date', 'time', 'timedelta', 'datetime') CONSTRAINTS_TO_ALLOWED_SCHEMAS: dict[str, set[str]] = defaultdict(set) +for constraint in STR_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(TEXT_SCHEMA_TYPES) +for constraint in BYTES_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('bytes',)) +for constraint in LIST_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('list',)) +for constraint in TUPLE_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('tuple',)) +for constraint in SET_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('set', 'frozenset')) +for constraint in DICT_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('dict',)) +for constraint in GENERATOR_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('generator',)) +for constraint in FLOAT_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('float',)) +for constraint in INT_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('int',)) +for constraint in DATE_TIME_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('date', 'time', 'datetime')) +for constraint in TIMEDELTA_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('timedelta',)) +for constraint in TIME_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('time',)) +for schema_type in (*TEXT_SCHEMA_TYPES, *SEQUENCE_SCHEMA_TYPES, *NUMERIC_SCHEMA_TYPES, 'typed-dict', 'model'): + CONSTRAINTS_TO_ALLOWED_SCHEMAS['strict'].add(schema_type) +for constraint in UNION_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('union',)) +for constraint in URL_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('url', 'multi-host-url')) +for constraint in BOOL_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('bool',)) +for constraint in UUID_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('uuid',)) +for constraint in LAX_OR_STRICT_CONSTRAINTS: + CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('lax-or-strict',)) -constraint_schema_pairings: list[tuple[set[str], tuple[str, ...]]] = [ - (STR_CONSTRAINTS, TEXT_SCHEMA_TYPES), - (BYTES_CONSTRAINTS, ('bytes',)), - (LIST_CONSTRAINTS, ('list',)), - (TUPLE_CONSTRAINTS, ('tuple',)), - (SET_CONSTRAINTS, ('set', 'frozenset')), - (DICT_CONSTRAINTS, ('dict',)), - (GENERATOR_CONSTRAINTS, ('generator',)), - (FLOAT_CONSTRAINTS, ('float',)), - (INT_CONSTRAINTS, ('int',)), - (DATE_TIME_CONSTRAINTS, ('date', 'time', 'datetime', 'timedelta')), - # TODO: this is a bit redundant, we could probably avoid some of these - (STRICT, (*TEXT_SCHEMA_TYPES, *SEQUENCE_SCHEMA_TYPES, *NUMERIC_SCHEMA_TYPES, 'typed-dict', 'model')), - (UNION_CONSTRAINTS, ('union',)), - (URL_CONSTRAINTS, ('url', 'multi-host-url')), - (BOOL_CONSTRAINTS, ('bool',)), - (UUID_CONSTRAINTS, ('uuid',)), - (LAX_OR_STRICT_CONSTRAINTS, ('lax-or-strict',)), - (ENUM_CONSTRAINTS, ('enum',)), - (DECIMAL_CONSTRAINTS, ('decimal',)), - (COMPLEX_CONSTRAINTS, ('complex',)), -] -for constraints, schemas in constraint_schema_pairings: - for c in constraints: - CONSTRAINTS_TO_ALLOWED_SCHEMAS[c].update(schemas) +def add_js_update_schema(s: cs.CoreSchema, f: Callable[[], dict[str, Any]]) -> None: + def update_js_schema(s: cs.CoreSchema, handler: GetJsonSchemaHandler) -> dict[str, Any]: + js_schema = handler(s) + js_schema.update(f()) + return js_schema + + if 'metadata' in s: + metadata = s['metadata'] + if 'pydantic_js_functions' in s: + metadata['pydantic_js_functions'].append(update_js_schema) + else: + metadata['pydantic_js_functions'] = [update_js_schema] + else: + s['metadata'] = {'pydantic_js_functions': [update_js_schema]} def as_jsonable_value(v: Any) -> Any: @@ -113,7 +123,7 @@ def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: An iterable of expanded annotations. Example: - ```python + ```py from annotated_types import Ge, Len from pydantic._internal._known_annotated_metadata import expand_grouped_metadata @@ -124,7 +134,7 @@ def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: """ import annotated_types as at - FieldInfo = import_cached_field_info() + from pydantic.fields import FieldInfo # circular import for annotation in annotations: if isinstance(annotation, at.GroupedMetadata): @@ -143,28 +153,6 @@ def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: yield annotation -@lru_cache -def _get_at_to_constraint_map() -> dict[type, str]: - """Return a mapping of annotated types to constraints. - - Normally, we would define a mapping like this in the module scope, but we can't do that - because we don't permit module level imports of `annotated_types`, in an attempt to speed up - the import time of `pydantic`. We still only want to have this dictionary defined in one place, - so we use this function to cache the result. - """ - import annotated_types as at - - return { - at.Gt: 'gt', - at.Ge: 'ge', - at.Lt: 'lt', - at.Le: 'le', - at.MultipleOf: 'multiple_of', - at.MinLen: 'min_length', - at.MaxLen: 'max_length', - } - - def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | None: # noqa: C901 """Apply `annotation` to `schema` if it is an annotation we know about (Gt, Le, etc.). Otherwise return `None`. @@ -182,40 +170,20 @@ def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | No An updated schema with annotation if it is an annotation we know about, `None` otherwise. Raises: - RuntimeError: If a constraint can't be applied to a specific schema type. - ValueError: If an unknown constraint is encountered. + PydanticCustomError: If `Predicate` fails. """ import annotated_types as at - from ._validators import NUMERIC_VALIDATOR_LOOKUP, forbid_inf_nan_check + from . import _validators schema = schema.copy() schema_update, other_metadata = collect_known_metadata([annotation]) schema_type = schema['type'] - - chain_schema_constraints: set[str] = { - 'pattern', - 'strip_whitespace', - 'to_lower', - 'to_upper', - 'coerce_numbers_to_str', - } - chain_schema_steps: list[CoreSchema] = [] - for constraint, value in schema_update.items(): if constraint not in CONSTRAINTS_TO_ALLOWED_SCHEMAS: raise ValueError(f'Unknown constraint {constraint}') allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint] - # if it becomes necessary to handle more than one constraint - # in this recursive case with function-after or function-wrap, we should refactor - # this is a bit challenging because we sometimes want to apply constraints to the inner schema, - # whereas other times we want to wrap the existing schema with a new one that enforces a new constraint. - if schema_type in {'function-before', 'function-wrap', 'function-after'} and constraint == 'strict': - schema['schema'] = apply_known_metadata(annotation, schema['schema']) # type: ignore # schema is function schema - return schema - - # if we're allowed to apply constraint directly to the schema, like le to int, do that if schema_type in allowed_schemas: if constraint == 'union_mode' and schema_type == 'union': schema['mode'] = value # type: ignore # schema is UnionSchema @@ -223,116 +191,145 @@ def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | No schema[constraint] = value continue - # else, apply a function after validator to the schema to enforce the corresponding constraint - if constraint in chain_schema_constraints: - - def _apply_constraint_with_incompatibility_info( - value: Any, handler: cs.ValidatorFunctionWrapHandler - ) -> Any: - try: - x = handler(value) - except ValidationError as ve: - # if the error is about the type, it's likely that the constraint is incompatible the type of the field - # for example, the following invalid schema wouldn't be caught during schema build, but rather at this point - # with a cryptic 'string_type' error coming from the string validator, - # that we'd rather express as a constraint incompatibility error (TypeError) - # Annotated[list[int], Field(pattern='abc')] - if 'type' in ve.errors()[0]['type']: - raise TypeError( - f"Unable to apply constraint '{constraint}' to supplied value {value} for schema of type '{schema_type}'" # noqa: B023 - ) - raise ve - return x - - chain_schema_steps.append( - cs.no_info_wrap_validator_function( - _apply_constraint_with_incompatibility_info, cs.str_schema(**{constraint: value}) - ) + if constraint == 'allow_inf_nan' and value is False: + return cs.no_info_after_validator_function( + _validators.forbid_inf_nan_check, + schema, ) - elif constraint in NUMERIC_VALIDATOR_LOOKUP: - if constraint in LENGTH_CONSTRAINTS: - inner_schema = schema - while inner_schema['type'] in {'function-before', 'function-wrap', 'function-after'}: - inner_schema = inner_schema['schema'] # type: ignore - inner_schema_type = inner_schema['type'] - if inner_schema_type == 'list' or ( - inner_schema_type == 'json-or-python' and inner_schema['json_schema']['type'] == 'list' # type: ignore - ): - js_constraint_key = 'minItems' if constraint == 'min_length' else 'maxItems' - else: - js_constraint_key = 'minLength' if constraint == 'min_length' else 'maxLength' - else: - js_constraint_key = constraint - - schema = cs.no_info_after_validator_function( - partial(NUMERIC_VALIDATOR_LOOKUP[constraint], **{constraint: value}), schema + elif constraint == 'pattern': + # insert a str schema to make sure the regex engine matches + return cs.chain_schema( + [ + schema, + cs.str_schema(pattern=value), + ] ) - metadata = schema.get('metadata', {}) - if (existing_json_schema_updates := metadata.get('pydantic_js_updates')) is not None: - metadata['pydantic_js_updates'] = { - **existing_json_schema_updates, - **{js_constraint_key: as_jsonable_value(value)}, - } - else: - metadata['pydantic_js_updates'] = {js_constraint_key: as_jsonable_value(value)} - schema['metadata'] = metadata - elif constraint == 'allow_inf_nan' and value is False: - schema = cs.no_info_after_validator_function( - forbid_inf_nan_check, + elif constraint == 'gt': + s = cs.no_info_after_validator_function( + partial(_validators.greater_than_validator, gt=value), + schema, + ) + add_js_update_schema(s, lambda: {'gt': as_jsonable_value(value)}) + return s + elif constraint == 'ge': + return cs.no_info_after_validator_function( + partial(_validators.greater_than_or_equal_validator, ge=value), + schema, + ) + elif constraint == 'lt': + return cs.no_info_after_validator_function( + partial(_validators.less_than_validator, lt=value), + schema, + ) + elif constraint == 'le': + return cs.no_info_after_validator_function( + partial(_validators.less_than_or_equal_validator, le=value), + schema, + ) + elif constraint == 'multiple_of': + return cs.no_info_after_validator_function( + partial(_validators.multiple_of_validator, multiple_of=value), + schema, + ) + elif constraint == 'min_length': + s = cs.no_info_after_validator_function( + partial(_validators.min_length_validator, min_length=value), + schema, + ) + add_js_update_schema(s, lambda: {'minLength': (as_jsonable_value(value))}) + return s + elif constraint == 'max_length': + s = cs.no_info_after_validator_function( + partial(_validators.max_length_validator, max_length=value), + schema, + ) + add_js_update_schema(s, lambda: {'maxLength': (as_jsonable_value(value))}) + return s + elif constraint == 'strip_whitespace': + return cs.chain_schema( + [ + schema, + cs.str_schema(strip_whitespace=True), + ] + ) + elif constraint == 'to_lower': + return cs.chain_schema( + [ + schema, + cs.str_schema(to_lower=True), + ] + ) + elif constraint == 'to_upper': + return cs.chain_schema( + [ + schema, + cs.str_schema(to_upper=True), + ] + ) + elif constraint == 'min_length': + return cs.no_info_after_validator_function( + partial(_validators.min_length_validator, min_length=annotation.min_length), + schema, + ) + elif constraint == 'max_length': + return cs.no_info_after_validator_function( + partial(_validators.max_length_validator, max_length=annotation.max_length), schema, ) else: - # It's rare that we'd get here, but it's possible if we add a new constraint and forget to handle it - # Most constraint errors are caught at runtime during attempted application - raise RuntimeError(f"Unable to apply constraint '{constraint}' to schema of type '{schema_type}'") + raise RuntimeError(f'Unable to apply constraint {constraint} to schema {schema_type}') for annotation in other_metadata: - if (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()): - constraint = at_to_constraint_map[annotation_type] - validator = NUMERIC_VALIDATOR_LOOKUP.get(constraint) - if validator is None: - raise ValueError(f'Unknown constraint {constraint}') - schema = cs.no_info_after_validator_function( - partial(validator, {constraint: getattr(annotation, constraint)}), schema + if isinstance(annotation, at.Gt): + return cs.no_info_after_validator_function( + partial(_validators.greater_than_validator, gt=annotation.gt), + schema, ) - continue - elif isinstance(annotation, (at.Predicate, at.Not)): - predicate_name = f'{annotation.func.__qualname__!r} ' if hasattr(annotation.func, '__qualname__') else '' + elif isinstance(annotation, at.Ge): + return cs.no_info_after_validator_function( + partial(_validators.greater_than_or_equal_validator, ge=annotation.ge), + schema, + ) + elif isinstance(annotation, at.Lt): + return cs.no_info_after_validator_function( + partial(_validators.less_than_validator, lt=annotation.lt), + schema, + ) + elif isinstance(annotation, at.Le): + return cs.no_info_after_validator_function( + partial(_validators.less_than_or_equal_validator, le=annotation.le), + schema, + ) + elif isinstance(annotation, at.MultipleOf): + return cs.no_info_after_validator_function( + partial(_validators.multiple_of_validator, multiple_of=annotation.multiple_of), + schema, + ) + elif isinstance(annotation, at.MinLen): + return cs.no_info_after_validator_function( + partial(_validators.min_length_validator, min_length=annotation.min_length), + schema, + ) + elif isinstance(annotation, at.MaxLen): + return cs.no_info_after_validator_function( + partial(_validators.max_length_validator, max_length=annotation.max_length), + schema, + ) + elif isinstance(annotation, at.Predicate): + predicate_name = f'{annotation.func.__qualname__} ' if hasattr(annotation.func, '__qualname__') else '' - # Note: B023 is ignored because even though we iterate over `other_metadata`, it is guaranteed - # to be of length 1. `apply_known_metadata()` is called from `GenerateSchema`, where annotations - # were already expanded via `expand_grouped_metadata()`. Confusing, but this falls into the annotations - # refactor. - if isinstance(annotation, at.Predicate): + def val_func(v: Any) -> Any: + # annotation.func may also raise an exception, let it pass through + if not annotation.func(v): + raise PydanticCustomError( + 'predicate_failed', + f'Predicate {predicate_name}failed', # type: ignore + ) + return v - def val_func(v: Any) -> Any: - predicate_satisfied = annotation.func(v) # noqa: B023 - if not predicate_satisfied: - raise PydanticCustomError( - 'predicate_failed', - f'Predicate {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023 - ) - return v - - else: - - def val_func(v: Any) -> Any: - predicate_satisfied = annotation.func(v) # noqa: B023 - if predicate_satisfied: - raise PydanticCustomError( - 'not_operation_failed', - f'Not of {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023 - ) - return v - - schema = cs.no_info_after_validator_function(val_func, schema) - else: - # ignore any other unknown metadata - return None - - if chain_schema_steps: - chain_schema_steps = [schema] + chain_schema_steps - return cs.chain_schema(chain_schema_steps) + return cs.no_info_after_validator_function(val_func, schema) + # ignore any other unknown metadata + return None return schema @@ -347,7 +344,7 @@ def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], A tuple contains a dict of known metadata and a list of unknown annotations. Example: - ```python + ```py from annotated_types import Gt, Len from pydantic._internal._known_annotated_metadata import collect_known_metadata @@ -356,19 +353,31 @@ def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], #> ({'gt': 1, 'min_length': 42}, [Ellipsis]) ``` """ + import annotated_types as at + annotations = expand_grouped_metadata(annotations) res: dict[str, Any] = {} remaining: list[Any] = [] - for annotation in annotations: # isinstance(annotation, PydanticMetadata) also covers ._fields:_PydanticGeneralMetadata if isinstance(annotation, PydanticMetadata): res.update(annotation.__dict__) # we don't use dataclasses.asdict because that recursively calls asdict on the field values - elif (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()): - constraint = at_to_constraint_map[annotation_type] - res[constraint] = getattr(annotation, constraint) + elif isinstance(annotation, at.MinLen): + res.update({'min_length': annotation.min_length}) + elif isinstance(annotation, at.MaxLen): + res.update({'max_length': annotation.max_length}) + elif isinstance(annotation, at.Gt): + res.update({'gt': annotation.gt}) + elif isinstance(annotation, at.Ge): + res.update({'ge': annotation.ge}) + elif isinstance(annotation, at.Lt): + res.update({'lt': annotation.lt}) + elif isinstance(annotation, at.Le): + res.update({'le': annotation.le}) + elif isinstance(annotation, at.MultipleOf): + res.update({'multiple_of': annotation.multiple_of}) elif isinstance(annotation, type) and issubclass(annotation, PydanticMetadata): # also support PydanticMetadata classes being used without initialisation, # e.g. `Annotated[int, Strict]` as well as `Annotated[int, Strict()]` diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_mock_val_ser.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_mock_val_ser.py index 9125ab32..b303fed2 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_mock_val_ser.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_mock_val_ser.py @@ -1,71 +1,18 @@ from __future__ import annotations -from collections.abc import Iterator, Mapping -from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, Union +from typing import TYPE_CHECKING, Callable, Generic, TypeVar -from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator +from pydantic_core import SchemaSerializer, SchemaValidator +from typing_extensions import Literal from ..errors import PydanticErrorCodes, PydanticUserError -from ..plugin._schema_validator import PluggableSchemaValidator if TYPE_CHECKING: from ..dataclasses import PydanticDataclass from ..main import BaseModel - from ..type_adapter import TypeAdapter -ValSer = TypeVar('ValSer', bound=Union[SchemaValidator, PluggableSchemaValidator, SchemaSerializer]) -T = TypeVar('T') - - -class MockCoreSchema(Mapping[str, Any]): - """Mocker for `pydantic_core.CoreSchema` which optionally attempts to - rebuild the thing it's mocking when one of its methods is accessed and raises an error if that fails. - """ - - __slots__ = '_error_message', '_code', '_attempt_rebuild', '_built_memo' - - def __init__( - self, - error_message: str, - *, - code: PydanticErrorCodes, - attempt_rebuild: Callable[[], CoreSchema | None] | None = None, - ) -> None: - self._error_message = error_message - self._code: PydanticErrorCodes = code - self._attempt_rebuild = attempt_rebuild - self._built_memo: CoreSchema | None = None - - def __getitem__(self, key: str) -> Any: - return self._get_built().__getitem__(key) - - def __len__(self) -> int: - return self._get_built().__len__() - - def __iter__(self) -> Iterator[str]: - return self._get_built().__iter__() - - def _get_built(self) -> CoreSchema: - if self._built_memo is not None: - return self._built_memo - - if self._attempt_rebuild: - schema = self._attempt_rebuild() - if schema is not None: - self._built_memo = schema - return schema - raise PydanticUserError(self._error_message, code=self._code) - - def rebuild(self) -> CoreSchema | None: - self._built_memo = None - if self._attempt_rebuild: - schema = self._attempt_rebuild() - if schema is not None: - return schema - else: - raise PydanticUserError(self._error_message, code=self._code) - return None +ValSer = TypeVar('ValSer', SchemaValidator, SchemaSerializer) class MockValSer(Generic[ValSer]): @@ -109,120 +56,85 @@ class MockValSer(Generic[ValSer]): return None -def set_type_adapter_mocks(adapter: TypeAdapter) -> None: - """Set `core_schema`, `validator` and `serializer` to mock core types on a type adapter instance. - - Args: - adapter: The type adapter instance to set the mocks on - """ - type_repr = str(adapter._type) - undefined_type_error_message = ( - f'`TypeAdapter[{type_repr}]` is not fully defined; you should define `{type_repr}` and all referenced types,' - f' then call `.rebuild()` on the instance.' - ) - - def attempt_rebuild_fn(attr_fn: Callable[[TypeAdapter], T]) -> Callable[[], T | None]: - def handler() -> T | None: - if adapter.rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: - return attr_fn(adapter) - return None - - return handler - - adapter.core_schema = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] - undefined_type_error_message, - code='class-not-fully-defined', - attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.core_schema), - ) - adapter.validator = MockValSer( # pyright: ignore[reportAttributeAccessIssue] - undefined_type_error_message, - code='class-not-fully-defined', - val_or_ser='validator', - attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.validator), - ) - adapter.serializer = MockValSer( # pyright: ignore[reportAttributeAccessIssue] - undefined_type_error_message, - code='class-not-fully-defined', - val_or_ser='serializer', - attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.serializer), - ) - - -def set_model_mocks(cls: type[BaseModel], undefined_name: str = 'all referenced types') -> None: - """Set `__pydantic_core_schema__`, `__pydantic_validator__` and `__pydantic_serializer__` to mock core types on a model. +def set_model_mocks(cls: type[BaseModel], cls_name: str, undefined_name: str = 'all referenced types') -> None: + """Set `__pydantic_validator__` and `__pydantic_serializer__` to `MockValSer`s on a model. Args: cls: The model class to set the mocks on + cls_name: Name of the model class, used in error messages undefined_name: Name of the undefined thing, used in error messages """ undefined_type_error_message = ( - f'`{cls.__name__}` is not fully defined; you should define {undefined_name},' - f' then call `{cls.__name__}.model_rebuild()`.' + f'`{cls_name}` is not fully defined; you should define {undefined_name},' + f' then call `{cls_name}.model_rebuild()`.' ) - def attempt_rebuild_fn(attr_fn: Callable[[type[BaseModel]], T]) -> Callable[[], T | None]: - def handler() -> T | None: - if cls.model_rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: - return attr_fn(cls) + def attempt_rebuild_validator() -> SchemaValidator | None: + if cls.model_rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: + return cls.__pydantic_validator__ + else: return None - return handler - - cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] - undefined_type_error_message, - code='class-not-fully-defined', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__), - ) - cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + cls.__pydantic_validator__ = MockValSer( # type: ignore[assignment] undefined_type_error_message, code='class-not-fully-defined', val_or_ser='validator', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__), + attempt_rebuild=attempt_rebuild_validator, ) - cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + + def attempt_rebuild_serializer() -> SchemaSerializer | None: + if cls.model_rebuild(raise_errors=False, _parent_namespace_depth=5) is not False: + return cls.__pydantic_serializer__ + else: + return None + + cls.__pydantic_serializer__ = MockValSer( # type: ignore[assignment] undefined_type_error_message, code='class-not-fully-defined', val_or_ser='serializer', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__), + attempt_rebuild=attempt_rebuild_serializer, ) -def set_dataclass_mocks(cls: type[PydanticDataclass], undefined_name: str = 'all referenced types') -> None: +def set_dataclass_mocks( + cls: type[PydanticDataclass], cls_name: str, undefined_name: str = 'all referenced types' +) -> None: """Set `__pydantic_validator__` and `__pydantic_serializer__` to `MockValSer`s on a dataclass. Args: cls: The model class to set the mocks on + cls_name: Name of the model class, used in error messages undefined_name: Name of the undefined thing, used in error messages """ from ..dataclasses import rebuild_dataclass undefined_type_error_message = ( - f'`{cls.__name__}` is not fully defined; you should define {undefined_name},' - f' then call `pydantic.dataclasses.rebuild_dataclass({cls.__name__})`.' + f'`{cls_name}` is not fully defined; you should define {undefined_name},' + f' then call `pydantic.dataclasses.rebuild_dataclass({cls_name})`.' ) - def attempt_rebuild_fn(attr_fn: Callable[[type[PydanticDataclass]], T]) -> Callable[[], T | None]: - def handler() -> T | None: - if rebuild_dataclass(cls, raise_errors=False, _parent_namespace_depth=5) is not False: - return attr_fn(cls) + def attempt_rebuild_validator() -> SchemaValidator | None: + if rebuild_dataclass(cls, raise_errors=False, _parent_namespace_depth=5) is not False: + return cls.__pydantic_validator__ + else: return None - return handler - - cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue] - undefined_type_error_message, - code='class-not-fully-defined', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__), - ) - cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + cls.__pydantic_validator__ = MockValSer( # type: ignore[assignment] undefined_type_error_message, code='class-not-fully-defined', val_or_ser='validator', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__), + attempt_rebuild=attempt_rebuild_validator, ) - cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue] + + def attempt_rebuild_serializer() -> SchemaSerializer | None: + if rebuild_dataclass(cls, raise_errors=False, _parent_namespace_depth=5) is not False: + return cls.__pydantic_serializer__ + else: + return None + + cls.__pydantic_serializer__ = MockValSer( # type: ignore[assignment] undefined_type_error_message, code='class-not-fully-defined', - val_or_ser='serializer', - attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__), + val_or_ser='validator', + attempt_rebuild=attempt_rebuild_serializer, ) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py index 4fe223c9..81159ff1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py @@ -1,49 +1,43 @@ """Private logic for creating models.""" - from __future__ import annotations as _annotations -import operator -import sys import typing import warnings import weakref from abc import ABCMeta -from functools import cache, partial, wraps +from functools import partial from types import FunctionType -from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, cast +from typing import Any, Callable, Generic, Mapping +import typing_extensions from pydantic_core import PydanticUndefined, SchemaSerializer -from typing_extensions import TypeAliasType, dataclass_transform, deprecated, get_args, get_origin -from typing_inspection import typing_objects +from typing_extensions import dataclass_transform, deprecated from ..errors import PydanticUndefinedAnnotation, PydanticUserError from ..plugin._schema_validator import create_schema_validator from ..warnings import GenericBeforeBaseModelWarning, PydanticDeprecatedSince20 from ._config import ConfigWrapper -from ._decorators import DecoratorInfos, PydanticDescriptorProxy, get_attribute_from_bases, unwrap_wrapped_function -from ._fields import collect_model_fields, is_valid_field_name, is_valid_privateattr_name, rebuild_model_fields -from ._generate_schema import GenerateSchema, InvalidSchemaError +from ._decorators import DecoratorInfos, PydanticDescriptorProxy, get_attribute_from_bases +from ._fields import collect_model_fields, is_valid_field_name, is_valid_privateattr_name +from ._generate_schema import GenerateSchema, generate_pydantic_signature from ._generics import PydanticGenericMetadata, get_model_typevars_map -from ._import_utils import import_cached_base_model, import_cached_field_info -from ._mock_val_ser import set_model_mocks -from ._namespace_utils import NsResolver -from ._signature import generate_pydantic_signature -from ._typing_extra import ( - _make_forward_ref, - eval_type_backport, - is_classvar_annotation, - parent_frame_namespace, -) -from ._utils import LazyClassAttribute, SafeGetItemProxy +from ._mock_val_ser import MockValSer, set_model_mocks +from ._schema_generation_shared import CallbackGetCoreSchemaHandler +from ._typing_extra import get_cls_types_namespace, is_annotated, is_classvar, parent_frame_namespace +from ._utils import ClassAttribute +from ._validate_call import ValidateCallWrapper + +if typing.TYPE_CHECKING: + from inspect import Signature -if TYPE_CHECKING: from ..fields import Field as PydanticModelField from ..fields import FieldInfo, ModelPrivateAttr - from ..fields import PrivateAttr as PydanticModelPrivateAttr from ..main import BaseModel else: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 PydanticModelField = object() - PydanticModelPrivateAttr = object() object_setattr = object.__setattr__ @@ -56,29 +50,12 @@ class _ModelNamespaceDict(dict): def __setitem__(self, k: str, v: object) -> None: existing: Any = self.get(k, None) if existing and v is not existing and isinstance(existing, PydanticDescriptorProxy): - warnings.warn( - f'`{k}` overrides an existing Pydantic `{existing.decorator_info.decorator_repr}` decorator', - stacklevel=2, - ) + warnings.warn(f'`{k}` overrides an existing Pydantic `{existing.decorator_info.decorator_repr}` decorator') return super().__setitem__(k, v) -def NoInitField( - *, - init: Literal[False] = False, -) -> Any: - """Only for typing purposes. Used as default value of `__pydantic_fields_set__`, - `__pydantic_extra__`, `__pydantic_private__`, so they could be ignored when - synthesizing the `__init__` signature. - """ - - -# For ModelMetaclass.register(): -_T = TypeVar('_T') - - -@dataclass_transform(kw_only_default=True, field_specifiers=(PydanticModelField, PydanticModelPrivateAttr, NoInitField)) +@dataclass_transform(kw_only_default=True, field_specifiers=(PydanticModelField,)) class ModelMetaclass(ABCMeta): def __new__( mcs, @@ -108,42 +85,24 @@ class ModelMetaclass(ABCMeta): # that `BaseModel` itself won't have any bases, but any subclass of it will, to determine whether the `__new__` # call we're in the middle of is for the `BaseModel` class. if bases: - raw_annotations: dict[str, Any] - if sys.version_info >= (3, 14): - if ( - '__annotations__' in namespace - ): # `from __future__ import annotations` was used in the model's module - raw_annotations = namespace['__annotations__'] - else: - # See https://docs.python.org/3.14/library/annotationlib.html#using-annotations-in-a-metaclass: - from annotationlib import Format, call_annotate_function, get_annotate_from_class_namespace - - if annotate := get_annotate_from_class_namespace(namespace): - raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF) - else: - raw_annotations = {} - else: - raw_annotations = namespace.get('__annotations__', {}) - base_field_names, class_vars, base_private_attributes = mcs._collect_bases_data(bases) - config_wrapper = ConfigWrapper.for_model(bases, namespace, raw_annotations, kwargs) + config_wrapper = ConfigWrapper.for_model(bases, namespace, kwargs) namespace['model_config'] = config_wrapper.config_dict private_attributes = inspect_namespace( - namespace, raw_annotations, config_wrapper.ignored_types, class_vars, base_field_names + namespace, config_wrapper.ignored_types, class_vars, base_field_names ) - if private_attributes or base_private_attributes: + if private_attributes: original_model_post_init = get_model_post_init(namespace, bases) if original_model_post_init is not None: # if there are private_attributes and a model_post_init function, we handle both - @wraps(original_model_post_init) - def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None: + def wrapped_model_post_init(self: BaseModel, __context: Any) -> None: """We need to both initialize private attributes and call the user-defined model_post_init method. """ - init_private_attributes(self, context) - original_model_post_init(self, context) + init_private_attributes(self, __context) + original_model_post_init(self, __context) namespace['model_post_init'] = wrapped_model_post_init else: @@ -152,11 +111,15 @@ class ModelMetaclass(ABCMeta): namespace['__class_vars__'] = class_vars namespace['__private_attributes__'] = {**base_private_attributes, **private_attributes} - cls = cast('type[BaseModel]', super().__new__(mcs, cls_name, bases, namespace, **kwargs)) - BaseModel_ = import_cached_base_model() + if config_wrapper.frozen: + set_default_hash_func(namespace, bases) + + cls: type[BaseModel] = super().__new__(mcs, cls_name, bases, namespace, **kwargs) # type: ignore + + from ..main import BaseModel mro = cls.__mro__ - if Generic in mro and mro.index(Generic) < mro.index(BaseModel_): + if Generic in mro and mro.index(Generic) < mro.index(BaseModel): warnings.warn( GenericBeforeBaseModelWarning( 'Classes should inherit from `BaseModel` before generic classes (e.g. `typing.Generic[T]`) ' @@ -166,14 +129,9 @@ class ModelMetaclass(ABCMeta): ) cls.__pydantic_custom_init__ = not getattr(cls.__init__, '__pydantic_base_init__', False) - cls.__pydantic_post_init__ = ( - None if cls.model_post_init is BaseModel_.model_post_init else 'model_post_init' - ) - - cls.__pydantic_setattr_handlers__ = {} + cls.__pydantic_post_init__ = None if cls.model_post_init is BaseModel.model_post_init else 'model_post_init' cls.__pydantic_decorators__ = DecoratorInfos.build(cls) - cls.__pydantic_decorators__.update_from_config(config_wrapper) # Use the getattr below to grab the __parameters__ from the `typing.Generic` parent class if __pydantic_generic_metadata__: @@ -182,40 +140,22 @@ class ModelMetaclass(ABCMeta): parent_parameters = getattr(cls, '__pydantic_generic_metadata__', {}).get('parameters', ()) parameters = getattr(cls, '__parameters__', None) or parent_parameters if parameters and parent_parameters and not all(x in parameters for x in parent_parameters): - from ..root_model import RootModelRootType - - missing_parameters = tuple(x for x in parameters if x not in parent_parameters) - if RootModelRootType in parent_parameters and RootModelRootType not in parameters: - # This is a special case where the user has subclassed `RootModel`, but has not parametrized - # RootModel with the generic type identifiers being used. Ex: - # class MyModel(RootModel, Generic[T]): - # root: T - # Should instead just be: - # class MyModel(RootModel[T]): - # root: T - parameters_str = ', '.join([x.__name__ for x in missing_parameters]) - error_message = ( - f'{cls.__name__} is a subclass of `RootModel`, but does not include the generic type identifier(s) ' - f'{parameters_str} in its parameters. ' - f'You should parametrize RootModel directly, e.g., `class {cls.__name__}(RootModel[{parameters_str}]): ...`.' + combined_parameters = parent_parameters + tuple(x for x in parameters if x not in parent_parameters) + parameters_str = ', '.join([str(x) for x in combined_parameters]) + generic_type_label = f'typing.Generic[{parameters_str}]' + error_message = ( + f'All parameters must be present on typing.Generic;' + f' you should inherit from {generic_type_label}.' + ) + if Generic not in bases: # pragma: no cover + # We raise an error here not because it is desirable, but because some cases are mishandled. + # It would be nice to remove this error and still have things behave as expected, it's just + # challenging because we are using a custom `__class_getitem__` to parametrize generic models, + # and not returning a typing._GenericAlias from it. + bases_str = ', '.join([x.__name__ for x in bases] + [generic_type_label]) + error_message += ( + f' Note: `typing.Generic` must go last: `class {cls.__name__}({bases_str}): ...`)' ) - else: - combined_parameters = parent_parameters + missing_parameters - parameters_str = ', '.join([str(x) for x in combined_parameters]) - generic_type_label = f'typing.Generic[{parameters_str}]' - error_message = ( - f'All parameters must be present on typing.Generic;' - f' you should inherit from {generic_type_label}.' - ) - if Generic not in bases: # pragma: no cover - # We raise an error here not because it is desirable, but because some cases are mishandled. - # It would be nice to remove this error and still have things behave as expected, it's just - # challenging because we are using a custom `__class_getitem__` to parametrize generic models, - # and not returning a typing._GenericAlias from it. - bases_str = ', '.join([x.__name__ for x in bases] + [generic_type_label]) - error_message += ( - f' Note: `typing.Generic` must go last: `class {cls.__name__}({bases_str}): ...`)' - ) raise TypeError(error_message) cls.__pydantic_generic_metadata__ = { @@ -233,52 +173,30 @@ class ModelMetaclass(ABCMeta): if __pydantic_reset_parent_namespace__: cls.__pydantic_parent_namespace__ = build_lenient_weakvaluedict(parent_frame_namespace()) - parent_namespace: dict[str, Any] | None = getattr(cls, '__pydantic_parent_namespace__', None) + parent_namespace = getattr(cls, '__pydantic_parent_namespace__', None) if isinstance(parent_namespace, dict): parent_namespace = unpack_lenient_weakvaluedict(parent_namespace) - ns_resolver = NsResolver(parent_namespace=parent_namespace) - - set_model_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver) - - # This is also set in `complete_model_class()`, after schema gen because they are recreated. - # We set them here as well for backwards compatibility: - cls.__pydantic_computed_fields__ = { - k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items() - } - - if config_wrapper.defer_build: - set_model_mocks(cls) - else: - # Any operation that requires accessing the field infos instances should be put inside - # `complete_model_class()`: - complete_model_class( - cls, - config_wrapper, - ns_resolver, - raise_errors=False, - create_model_module=_create_model_module, - ) - - if config_wrapper.frozen and '__hash__' not in namespace: - set_default_hash_func(cls, bases) - + types_namespace = get_cls_types_namespace(cls, parent_namespace) + set_model_fields(cls, bases, config_wrapper, types_namespace) + complete_model_class( + cls, + cls_name, + config_wrapper, + raise_errors=False, + types_namespace=types_namespace, + create_model_module=_create_model_module, + ) # using super(cls, cls) on the next line ensures we only call the parent class's __pydantic_init_subclass__ # I believe the `type: ignore` is only necessary because mypy doesn't realize that this code branch is # only hit for _proper_ subclasses of BaseModel super(cls, cls).__pydantic_init_subclass__(**kwargs) # type: ignore[misc] return cls else: - # These are instance variables, but have been assigned to `NoInitField` to trick the type checker. - for instance_slot in '__pydantic_fields_set__', '__pydantic_extra__', '__pydantic_private__': - namespace.pop( - instance_slot, - None, # In case the metaclass is used with a class other than `BaseModel`. - ) - namespace.get('__annotations__', {}).clear() + # this is the BaseModel class itself being created, no logic required return super().__new__(mcs, cls_name, bases, namespace, **kwargs) - if not TYPE_CHECKING: # pragma: no branch + if not typing.TYPE_CHECKING: # pragma: no branch # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access def __getattr__(self, item: str) -> Any: @@ -286,30 +204,30 @@ class ModelMetaclass(ABCMeta): private_attributes = self.__dict__.get('__private_attributes__') if private_attributes and item in private_attributes: return private_attributes[item] + if item == '__pydantic_core_schema__': + # This means the class didn't get a schema generated for it, likely because there was an undefined reference + maybe_mock_validator = getattr(self, '__pydantic_validator__', None) + if isinstance(maybe_mock_validator, MockValSer): + rebuilt_validator = maybe_mock_validator.rebuild() + if rebuilt_validator is not None: + # In this case, a validator was built, and so `__pydantic_core_schema__` should now be set + return getattr(self, '__pydantic_core_schema__') raise AttributeError(item) @classmethod - def __prepare__(cls, *args: Any, **kwargs: Any) -> dict[str, object]: + def __prepare__(cls, *args: Any, **kwargs: Any) -> Mapping[str, object]: return _ModelNamespaceDict() - # Due to performance and memory issues, in the ABCMeta.__subclasscheck__ implementation, we don't support - # registered virtual subclasses. See https://github.com/python/cpython/issues/92810#issuecomment-2762454345. - # This may change once the CPython gets fixed (possibly in 3.15), in which case we should conditionally - # define `register()`. - def register(self, subclass: type[_T]) -> type[_T]: - warnings.warn( - f"For performance reasons, virtual subclasses registered using '{self.__qualname__}.register()' " - "are not supported in 'isinstance()' and 'issubclass()' checks.", - stacklevel=2, - ) - return super().register(subclass) + def __instancecheck__(self, instance: Any) -> bool: + """Avoid calling ABC _abc_subclasscheck unless we're pretty sure. - __instancecheck__ = type.__instancecheck__ # pyright: ignore[reportAssignmentType] - __subclasscheck__ = type.__subclasscheck__ # pyright: ignore[reportAssignmentType] + See #3829 and python/cpython#92810 + """ + return hasattr(instance, '__pydantic_validator__') and super().__instancecheck__(instance) @staticmethod def _collect_bases_data(bases: tuple[type[Any], ...]) -> tuple[set[str], set[str], dict[str, ModelPrivateAttr]]: - BaseModel = import_cached_base_model() + from ..main import BaseModel field_names: set[str] = set() class_vars: set[str] = set() @@ -317,51 +235,28 @@ class ModelMetaclass(ABCMeta): for base in bases: if issubclass(base, BaseModel) and base is not BaseModel: # model_fields might not be defined yet in the case of generics, so we use getattr here: - field_names.update(getattr(base, '__pydantic_fields__', {}).keys()) + field_names.update(getattr(base, 'model_fields', {}).keys()) class_vars.update(base.__class_vars__) private_attributes.update(base.__private_attributes__) return field_names, class_vars, private_attributes @property @deprecated( - 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', category=None + 'The `__fields__` attribute is deprecated, use `model_fields` instead.', category=PydanticDeprecatedSince20 ) def __fields__(self) -> dict[str, FieldInfo]: - warnings.warn( - 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', - PydanticDeprecatedSince20, - stacklevel=2, - ) - return getattr(self, '__pydantic_fields__', {}) - - @property - def __pydantic_fields_complete__(self) -> bool: - """Whether the fields where successfully collected (i.e. type hints were successfully resolves). - - This is a private attribute, not meant to be used outside Pydantic. - """ - if '__pydantic_fields__' not in self.__dict__: - return False - - field_infos = cast('dict[str, FieldInfo]', self.__pydantic_fields__) # pyright: ignore[reportAttributeAccessIssue] - - return all(field_info._complete for field_info in field_infos.values()) - - def __dir__(self) -> list[str]: - attributes = list(super().__dir__()) - if '__fields__' in attributes: - attributes.remove('__fields__') - return attributes + warnings.warn('The `__fields__` attribute is deprecated, use `model_fields` instead.', DeprecationWarning) + return self.model_fields # type: ignore -def init_private_attributes(self: BaseModel, context: Any, /) -> None: +def init_private_attributes(self: BaseModel, __context: Any) -> None: """This function is meant to behave like a BaseModel method to initialise private attributes. It takes context as an argument since that's what pydantic-core passes when calling it. Args: self: The BaseModel instance. - context: The context. + __context: The context. """ if getattr(self, '__pydantic_private__', None) is None: pydantic_private = {} @@ -377,7 +272,7 @@ def get_model_post_init(namespace: dict[str, Any], bases: tuple[type[Any], ...]) if 'model_post_init' in namespace: return namespace['model_post_init'] - BaseModel = import_cached_base_model() + from ..main import BaseModel model_post_init = get_attribute_from_bases(bases, 'model_post_init') if model_post_init is not BaseModel.model_post_init: @@ -386,7 +281,6 @@ def get_model_post_init(namespace: dict[str, Any], bases: tuple[type[Any], ...]) def inspect_namespace( # noqa C901 namespace: dict[str, Any], - raw_annotations: dict[str, Any], ignored_types: tuple[type[Any], ...], base_class_vars: set[str], base_class_fields: set[str], @@ -397,7 +291,6 @@ def inspect_namespace( # noqa C901 Args: namespace: The attribute dictionary of the class to be created. - raw_annotations: The (non-evaluated) annotations of the model. ignored_types: A tuple of ignore types. base_class_vars: A set of base class class variables. base_class_fields: A set of base class fields. @@ -412,26 +305,24 @@ def inspect_namespace( # noqa C901 - If a field does not have a type annotation. - If a field on base class was overridden by a non-annotated attribute. """ - from ..fields import ModelPrivateAttr, PrivateAttr - - FieldInfo = import_cached_field_info() + from ..fields import FieldInfo, ModelPrivateAttr, PrivateAttr all_ignored_types = ignored_types + default_ignored_types() private_attributes: dict[str, ModelPrivateAttr] = {} + raw_annotations = namespace.get('__annotations__', {}) if '__root__' in raw_annotations or '__root__' in namespace: raise TypeError("To define root models, use `pydantic.RootModel` rather than a field called '__root__'") ignored_names: set[str] = set() for var_name, value in list(namespace.items()): - if var_name == 'model_config' or var_name == '__pydantic_extra__': + if var_name == 'model_config': continue elif ( isinstance(value, type) and value.__module__ == namespace['__module__'] - and '__qualname__' in namespace - and value.__qualname__.startswith(f'{namespace["__qualname__"]}.') + and value.__qualname__.startswith(namespace['__qualname__']) ): # `value` is a nested type defined in this namespace; don't error continue @@ -461,8 +352,8 @@ def inspect_namespace( # noqa C901 elif var_name.startswith('__'): continue elif is_valid_privateattr_name(var_name): - if var_name not in raw_annotations or not is_classvar_annotation(raw_annotations[var_name]): - private_attributes[var_name] = cast(ModelPrivateAttr, PrivateAttr(default=value)) + if var_name not in raw_annotations or not is_classvar(raw_annotations[var_name]): + private_attributes[var_name] = PrivateAttr(default=value) del namespace[var_name] elif var_name in base_class_vars: continue @@ -490,28 +381,12 @@ def inspect_namespace( # noqa C901 is_valid_privateattr_name(ann_name) and ann_name not in private_attributes and ann_name not in ignored_names - # This condition can be a false negative when `ann_type` is stringified, - # but it is handled in most cases in `set_model_fields`: - and not is_classvar_annotation(ann_type) + and not is_classvar(ann_type) and ann_type not in all_ignored_types and getattr(ann_type, '__module__', None) != 'functools' ): - if isinstance(ann_type, str): - # Walking up the frames to get the module namespace where the model is defined - # (as the model class wasn't created yet, we unfortunately can't use `cls.__module__`): - frame = sys._getframe(2) - if frame is not None: - try: - ann_type = eval_type_backport( - _make_forward_ref(ann_type, is_argument=False, is_class=True), - globalns=frame.f_globals, - localns=frame.f_locals, - ) - except (NameError, TypeError): - pass - - if typing_objects.is_annotated(get_origin(ann_type)): - _, *metadata = get_args(ann_type) + if is_annotated(ann_type): + _, *metadata = typing_extensions.get_args(ann_type) private_attr = next((v for v in metadata if isinstance(v, ModelPrivateAttr)), None) if private_attr is not None: private_attributes[ann_name] = private_attr @@ -521,51 +396,36 @@ def inspect_namespace( # noqa C901 return private_attributes -def set_default_hash_func(cls: type[BaseModel], bases: tuple[type[Any], ...]) -> None: +def set_default_hash_func(namespace: dict[str, Any], bases: tuple[type[Any], ...]) -> None: + if '__hash__' in namespace: + return + base_hash_func = get_attribute_from_bases(bases, '__hash__') - new_hash_func = make_hash_func(cls) - if base_hash_func in {None, object.__hash__} or getattr(base_hash_func, '__code__', None) == new_hash_func.__code__: - # If `__hash__` is some default, we generate a hash function. - # It will be `None` if not overridden from BaseModel. - # It may be `object.__hash__` if there is another + if base_hash_func in {None, object.__hash__}: + # If `__hash__` is None _or_ `object.__hash__`, we generate a hash function. + # It will be `None` if not overridden from BaseModel, but may be `object.__hash__` if there is another # parent class earlier in the bases which doesn't override `__hash__` (e.g. `typing.Generic`). - # It may be a value set by `set_default_hash_func` if `cls` is a subclass of another frozen model. - # In the last case we still need a new hash function to account for new `model_fields`. - cls.__hash__ = new_hash_func + def hash_func(self: Any) -> int: + return hash(self.__class__) + hash(tuple(self.__dict__.values())) - -def make_hash_func(cls: type[BaseModel]) -> Any: - getter = operator.itemgetter(*cls.__pydantic_fields__.keys()) if cls.__pydantic_fields__ else lambda _: 0 - - def hash_func(self: Any) -> int: - try: - return hash(getter(self.__dict__)) - except KeyError: - # In rare cases (such as when using the deprecated copy method), the __dict__ may not contain - # all model fields, which is how we can get here. - # getter(self.__dict__) is much faster than any 'safe' method that accounts for missing keys, - # and wrapping it in a `try` doesn't slow things down much in the common case. - return hash(getter(SafeGetItemProxy(self.__dict__))) - - return hash_func + namespace['__hash__'] = hash_func def set_model_fields( - cls: type[BaseModel], - config_wrapper: ConfigWrapper, - ns_resolver: NsResolver | None, + cls: type[BaseModel], bases: tuple[type[Any], ...], config_wrapper: ConfigWrapper, types_namespace: dict[str, Any] ) -> None: - """Collect and set `cls.__pydantic_fields__` and `cls.__class_vars__`. + """Collect and set `cls.model_fields` and `cls.__class_vars__`. Args: cls: BaseModel or dataclass. + bases: Parents of the class, generally `cls.__bases__`. config_wrapper: The config wrapper instance. - ns_resolver: Namespace resolver to use when getting model annotations. + types_namespace: Optional extra namespace to look for types in. """ typevars_map = get_model_typevars_map(cls) - fields, class_vars = collect_model_fields(cls, config_wrapper, ns_resolver, typevars_map=typevars_map) + fields, class_vars = collect_model_fields(cls, bases, config_wrapper, types_namespace, typevars_map=typevars_map) - cls.__pydantic_fields__ = fields + cls.model_fields = fields cls.__class_vars__.update(class_vars) for k in class_vars: @@ -583,11 +443,11 @@ def set_model_fields( def complete_model_class( cls: type[BaseModel], + cls_name: str, config_wrapper: ConfigWrapper, - ns_resolver: NsResolver, *, raise_errors: bool = True, - call_on_complete_hook: bool = True, + types_namespace: dict[str, Any] | None, create_model_module: str | None = None, ) -> bool: """Finish building a model class. @@ -597,10 +457,10 @@ def complete_model_class( Args: cls: BaseModel or dataclass. + cls_name: The model or dataclass name. config_wrapper: The config wrapper instance. - ns_resolver: The namespace resolver instance to use during schema building. raise_errors: Whether to raise errors. - call_on_complete_hook: Whether to call the `__pydantic_on_complete__` hook. + types_namespace: Optional extra namespace to look for types in. create_model_module: The module of the class to be created, if created by `create_model`. Returns: @@ -611,61 +471,39 @@ def complete_model_class( and `raise_errors=True`. """ typevars_map = get_model_typevars_map(cls) - - if not cls.__pydantic_fields_complete__: - # Note: when coming from `ModelMetaclass.__new__()`, this results in fields being built twice. - # We do so a second time here so that we can get the `NameError` for the specific undefined annotation. - # Alternatively, we could let `GenerateSchema()` raise the error, but there are cases where incomplete - # fields are inherited in `collect_model_fields()` and can actually have their annotation resolved in the - # generate schema process. As we want to avoid having `__pydantic_fields_complete__` set to `False` - # when `__pydantic_complete__` is `True`, we rebuild here: - try: - cls.__pydantic_fields__ = rebuild_model_fields( - cls, - config_wrapper=config_wrapper, - ns_resolver=ns_resolver, - typevars_map=typevars_map, - ) - except NameError as e: - exc = PydanticUndefinedAnnotation.from_name_error(e) - set_model_mocks(cls, f'`{exc.name}`') - if raise_errors: - raise exc from e - - if not raise_errors and not cls.__pydantic_fields_complete__: - # No need to continue with schema gen, it is guaranteed to fail - return False - - assert cls.__pydantic_fields_complete__ - gen_schema = GenerateSchema( config_wrapper, - ns_resolver, + types_namespace, typevars_map, ) + handler = CallbackGetCoreSchemaHandler( + partial(gen_schema.generate_schema, from_dunder_get_core_schema=False), + gen_schema, + ref_mode='unpack', + ) + + if config_wrapper.defer_build: + set_model_mocks(cls, cls_name) + return False + try: - schema = gen_schema.generate_schema(cls) + schema = cls.__get_pydantic_core_schema__(cls, handler) except PydanticUndefinedAnnotation as e: if raise_errors: raise - set_model_mocks(cls, f'`{e.name}`') + set_model_mocks(cls, cls_name, f'`{e.name}`') return False - core_config = config_wrapper.core_config(title=cls.__name__) + core_config = config_wrapper.core_config(cls) try: schema = gen_schema.clean_schema(schema) - except InvalidSchemaError: - set_model_mocks(cls) + except gen_schema.CollectedInvalid: + set_model_mocks(cls, cls_name) return False - # This needs to happen *after* model schema generation, as the return type - # of the properties are evaluated and the `ComputedFieldInfo` are recreated: - cls.__pydantic_computed_fields__ = {k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items()} - - set_deprecated_descriptors(cls) - + # debug(schema) cls.__pydantic_core_schema__ = schema cls.__pydantic_validator__ = create_schema_validator( @@ -678,83 +516,29 @@ def complete_model_class( config_wrapper.plugin_settings, ) cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config) - - # set __signature__ attr only for model class, but not for its instances - # (because instances can define `__call__`, and `inspect.signature` shouldn't - # use the `__signature__` attribute and instead generate from `__call__`). - cls.__signature__ = LazyClassAttribute( - '__signature__', - partial( - generate_pydantic_signature, - init=cls.__init__, - fields=cls.__pydantic_fields__, - validate_by_name=config_wrapper.validate_by_name, - extra=config_wrapper.extra, - ), - ) - cls.__pydantic_complete__ = True - if call_on_complete_hook: - cls.__pydantic_on_complete__() - + # set __signature__ attr only for model class, but not for its instances + cls.__signature__ = ClassAttribute( + '__signature__', generate_model_signature(cls.__init__, cls.model_fields, config_wrapper) + ) return True -def set_deprecated_descriptors(cls: type[BaseModel]) -> None: - """Set data descriptors on the class for deprecated fields.""" - for field, field_info in cls.__pydantic_fields__.items(): - if (msg := field_info.deprecation_message) is not None: - desc = _DeprecatedFieldDescriptor(msg) - desc.__set_name__(cls, field) - setattr(cls, field, desc) +def generate_model_signature( + init: Callable[..., None], fields: dict[str, FieldInfo], config_wrapper: ConfigWrapper +) -> Signature: + """Generate signature for model based on its fields. - for field, computed_field_info in cls.__pydantic_computed_fields__.items(): - if ( - (msg := computed_field_info.deprecation_message) is not None - # Avoid having two warnings emitted: - and not hasattr(unwrap_wrapped_function(computed_field_info.wrapped_property), '__deprecated__') - ): - desc = _DeprecatedFieldDescriptor(msg, computed_field_info.wrapped_property) - desc.__set_name__(cls, field) - setattr(cls, field, desc) + Args: + init: The class init. + fields: The model fields. + config_wrapper: The config wrapper instance. - -class _DeprecatedFieldDescriptor: - """Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field. - - Attributes: - msg: The deprecation message to be emitted. - wrapped_property: The property instance if the deprecated field is a computed field, or `None`. - field_name: The name of the field being deprecated. + Returns: + The model signature. """ - - field_name: str - - def __init__(self, msg: str, wrapped_property: property | None = None) -> None: - self.msg = msg - self.wrapped_property = wrapped_property - - def __set_name__(self, cls: type[BaseModel], name: str) -> None: - self.field_name = name - - def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any: - if obj is None: - if self.wrapped_property is not None: - return self.wrapped_property.__get__(None, obj_type) - raise AttributeError(self.field_name) - - warnings.warn(self.msg, DeprecationWarning, stacklevel=2) - - if self.wrapped_property is not None: - return self.wrapped_property.__get__(obj, obj_type) - return obj.__dict__[self.field_name] - - # Defined to make it a data descriptor and take precedence over the instance's dictionary. - # Note that it will not be called when setting a value on a model instance - # as `BaseModel.__setattr__` is defined and takes priority. - def __set__(self, obj: Any, value: Any) -> NoReturn: - raise AttributeError(self.field_name) + return generate_pydantic_signature(init, fields, config_wrapper) class _PydanticWeakRef: @@ -828,21 +612,15 @@ def unpack_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | N return result -@cache def default_ignored_types() -> tuple[type[Any], ...]: from ..fields import ComputedFieldInfo - ignored_types = [ + return ( FunctionType, property, classmethod, staticmethod, PydanticDescriptorProxy, ComputedFieldInfo, - TypeAliasType, # from `typing_extensions` - ] - - if sys.version_info >= (3, 12): - ignored_types.append(typing.TypeAliasType) - - return tuple(ignored_types) + ValidateCallWrapper, + ) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_namespace_utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_namespace_utils.py deleted file mode 100644 index af0cddb0..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_namespace_utils.py +++ /dev/null @@ -1,293 +0,0 @@ -from __future__ import annotations - -import sys -from collections.abc import Generator, Iterator, Mapping -from contextlib import contextmanager -from functools import cached_property -from typing import Any, Callable, NamedTuple, TypeVar - -from typing_extensions import ParamSpec, TypeAlias, TypeAliasType, TypeVarTuple - -GlobalsNamespace: TypeAlias = 'dict[str, Any]' -"""A global namespace. - -In most cases, this is a reference to the `__dict__` attribute of a module. -This namespace type is expected as the `globals` argument during annotations evaluation. -""" - -MappingNamespace: TypeAlias = Mapping[str, Any] -"""Any kind of namespace. - -In most cases, this is a local namespace (e.g. the `__dict__` attribute of a class, -the [`f_locals`][frame.f_locals] attribute of a frame object, when dealing with types -defined inside functions). -This namespace type is expected as the `locals` argument during annotations evaluation. -""" - -_TypeVarLike: TypeAlias = 'TypeVar | ParamSpec | TypeVarTuple' - - -class NamespacesTuple(NamedTuple): - """A tuple of globals and locals to be used during annotations evaluation. - - This datastructure is defined as a named tuple so that it can easily be unpacked: - - ```python {lint="skip" test="skip"} - def eval_type(typ: type[Any], ns: NamespacesTuple) -> None: - return eval(typ, *ns) - ``` - """ - - globals: GlobalsNamespace - """The namespace to be used as the `globals` argument during annotations evaluation.""" - - locals: MappingNamespace - """The namespace to be used as the `locals` argument during annotations evaluation.""" - - -def get_module_ns_of(obj: Any) -> dict[str, Any]: - """Get the namespace of the module where the object is defined. - - Caution: this function does not return a copy of the module namespace, so the result - should not be mutated. The burden of enforcing this is on the caller. - """ - module_name = getattr(obj, '__module__', None) - if module_name: - try: - return sys.modules[module_name].__dict__ - except KeyError: - # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 - return {} - return {} - - -# Note that this class is almost identical to `collections.ChainMap`, but need to enforce -# immutable mappings here: -class LazyLocalNamespace(Mapping[str, Any]): - """A lazily evaluated mapping, to be used as the `locals` argument during annotations evaluation. - - While the [`eval`][eval] function expects a mapping as the `locals` argument, it only - performs `__getitem__` calls. The [`Mapping`][collections.abc.Mapping] abstract base class - is fully implemented only for type checking purposes. - - Args: - *namespaces: The namespaces to consider, in ascending order of priority. - - Example: - ```python {lint="skip" test="skip"} - ns = LazyLocalNamespace({'a': 1, 'b': 2}, {'a': 3}) - ns['a'] - #> 3 - ns['b'] - #> 2 - ``` - """ - - def __init__(self, *namespaces: MappingNamespace) -> None: - self._namespaces = namespaces - - @cached_property - def data(self) -> dict[str, Any]: - return {k: v for ns in self._namespaces for k, v in ns.items()} - - def __len__(self) -> int: - return len(self.data) - - def __getitem__(self, key: str) -> Any: - return self.data[key] - - def __contains__(self, key: object) -> bool: - return key in self.data - - def __iter__(self) -> Iterator[str]: - return iter(self.data) - - -def ns_for_function(obj: Callable[..., Any], parent_namespace: MappingNamespace | None = None) -> NamespacesTuple: - """Return the global and local namespaces to be used when evaluating annotations for the provided function. - - The global namespace will be the `__dict__` attribute of the module the function was defined in. - The local namespace will contain the `__type_params__` introduced by PEP 695. - - Args: - obj: The object to use when building namespaces. - parent_namespace: Optional namespace to be added with the lowest priority in the local namespace. - If the passed function is a method, the `parent_namespace` will be the namespace of the class - the method is defined in. Thus, we also fetch type `__type_params__` from there (i.e. the - class-scoped type variables). - """ - locals_list: list[MappingNamespace] = [] - if parent_namespace is not None: - locals_list.append(parent_namespace) - - # Get the `__type_params__` attribute introduced by PEP 695. - # Note that the `typing._eval_type` function expects type params to be - # passed as a separate argument. However, internally, `_eval_type` calls - # `ForwardRef._evaluate` which will merge type params with the localns, - # essentially mimicking what we do here. - type_params: tuple[_TypeVarLike, ...] = getattr(obj, '__type_params__', ()) - if parent_namespace is not None: - # We also fetch type params from the parent namespace. If present, it probably - # means the function was defined in a class. This is to support the following: - # https://github.com/python/cpython/issues/124089. - type_params += parent_namespace.get('__type_params__', ()) - - locals_list.append({t.__name__: t for t in type_params}) - - # What about short-circuiting to `obj.__globals__`? - globalns = get_module_ns_of(obj) - - return NamespacesTuple(globalns, LazyLocalNamespace(*locals_list)) - - -class NsResolver: - """A class responsible for the namespaces resolving logic for annotations evaluation. - - This class handles the namespace logic when evaluating annotations mainly for class objects. - - It holds a stack of classes that are being inspected during the core schema building, - and the `types_namespace` property exposes the globals and locals to be used for - type annotation evaluation. Additionally -- if no class is present in the stack -- a - fallback globals and locals can be provided using the `namespaces_tuple` argument - (this is useful when generating a schema for a simple annotation, e.g. when using - `TypeAdapter`). - - The namespace creation logic is unfortunately flawed in some cases, for backwards - compatibility reasons and to better support valid edge cases. See the description - for the `parent_namespace` argument and the example for more details. - - Args: - namespaces_tuple: The default globals and locals to use if no class is present - on the stack. This can be useful when using the `GenerateSchema` class - with `TypeAdapter`, where the "type" being analyzed is a simple annotation. - parent_namespace: An optional parent namespace that will be added to the locals - with the lowest priority. For a given class defined in a function, the locals - of this function are usually used as the parent namespace: - - ```python {lint="skip" test="skip"} - from pydantic import BaseModel - - def func() -> None: - SomeType = int - - class Model(BaseModel): - f: 'SomeType' - - # when collecting fields, an namespace resolver instance will be created - # this way: - # ns_resolver = NsResolver(parent_namespace={'SomeType': SomeType}) - ``` - - For backwards compatibility reasons and to support valid edge cases, this parent - namespace will be used for *every* type being pushed to the stack. In the future, - we might want to be smarter by only doing so when the type being pushed is defined - in the same module as the parent namespace. - - Example: - ```python {lint="skip" test="skip"} - ns_resolver = NsResolver( - parent_namespace={'fallback': 1}, - ) - - class Sub: - m: 'Model' - - class Model: - some_local = 1 - sub: Sub - - ns_resolver = NsResolver() - - # This is roughly what happens when we build a core schema for `Model`: - with ns_resolver.push(Model): - ns_resolver.types_namespace - #> NamespacesTuple({'Sub': Sub}, {'Model': Model, 'some_local': 1}) - # First thing to notice here, the model being pushed is added to the locals. - # Because `NsResolver` is being used during the model definition, it is not - # yet added to the globals. This is useful when resolving self-referencing annotations. - - with ns_resolver.push(Sub): - ns_resolver.types_namespace - #> NamespacesTuple({'Sub': Sub}, {'Sub': Sub, 'Model': Model}) - # Second thing to notice: `Sub` is present in both the globals and locals. - # This is not an issue, just that as described above, the model being pushed - # is added to the locals, but it happens to be present in the globals as well - # because it is already defined. - # Third thing to notice: `Model` is also added in locals. This is a backwards - # compatibility workaround that allows for `Sub` to be able to resolve `'Model'` - # correctly (as otherwise models would have to be rebuilt even though this - # doesn't look necessary). - ``` - """ - - def __init__( - self, - namespaces_tuple: NamespacesTuple | None = None, - parent_namespace: MappingNamespace | None = None, - ) -> None: - self._base_ns_tuple = namespaces_tuple or NamespacesTuple({}, {}) - self._parent_ns = parent_namespace - self._types_stack: list[type[Any] | TypeAliasType] = [] - - @cached_property - def types_namespace(self) -> NamespacesTuple: - """The current global and local namespaces to be used for annotations evaluation.""" - if not self._types_stack: - # TODO: should we merge the parent namespace here? - # This is relevant for TypeAdapter, where there are no types on the stack, and we might - # need access to the parent_ns. Right now, we sidestep this in `type_adapter.py` by passing - # locals to both parent_ns and the base_ns_tuple, but this is a bit hacky. - # we might consider something like: - # if self._parent_ns is not None: - # # Hacky workarounds, see class docstring: - # # An optional parent namespace that will be added to the locals with the lowest priority - # locals_list: list[MappingNamespace] = [self._parent_ns, self._base_ns_tuple.locals] - # return NamespacesTuple(self._base_ns_tuple.globals, LazyLocalNamespace(*locals_list)) - return self._base_ns_tuple - - typ = self._types_stack[-1] - - globalns = get_module_ns_of(typ) - - locals_list: list[MappingNamespace] = [] - # Hacky workarounds, see class docstring: - # An optional parent namespace that will be added to the locals with the lowest priority - if self._parent_ns is not None: - locals_list.append(self._parent_ns) - if len(self._types_stack) > 1: - first_type = self._types_stack[0] - locals_list.append({first_type.__name__: first_type}) - - # Adding `__type_params__` *before* `vars(typ)`, as the latter takes priority - # (see https://github.com/python/cpython/pull/120272). - # TODO `typ.__type_params__` when we drop support for Python 3.11: - type_params: tuple[_TypeVarLike, ...] = getattr(typ, '__type_params__', ()) - if type_params: - # Adding `__type_params__` is mostly useful for generic classes defined using - # PEP 695 syntax *and* using forward annotations (see the example in - # https://github.com/python/cpython/issues/114053). For TypeAliasType instances, - # it is way less common, but still required if using a string annotation in the alias - # value, e.g. `type A[T] = 'T'` (which is not necessary in most cases). - locals_list.append({t.__name__: t for t in type_params}) - - # TypeAliasType instances don't have a `__dict__` attribute, so the check - # is necessary: - if hasattr(typ, '__dict__'): - locals_list.append(vars(typ)) - - # The `len(self._types_stack) > 1` check above prevents this from being added twice: - locals_list.append({typ.__name__: typ}) - - return NamespacesTuple(globalns, LazyLocalNamespace(*locals_list)) - - @contextmanager - def push(self, typ: type[Any] | TypeAliasType, /) -> Generator[None]: - """Push a type to the stack.""" - self._types_stack.append(typ) - # Reset the cached property: - self.__dict__.pop('types_namespace', None) - try: - yield - finally: - self._types_stack.pop() - self.__dict__.pop('types_namespace', None) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_repr.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_repr.py index 7e80a9c8..c8bb9ec9 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_repr.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_repr.py @@ -1,22 +1,19 @@ """Tools to provide pretty/human-readable display of objects.""" - from __future__ import annotations as _annotations import types -from collections.abc import Callable, Collection, Generator, Iterable -from typing import TYPE_CHECKING, Any, ForwardRef, cast +import typing +from typing import Any import typing_extensions -from typing_extensions import TypeAlias -from typing_inspection import typing_objects -from typing_inspection.introspection import is_union_origin from . import _typing_extra -if TYPE_CHECKING: - # TODO remove type error comments when we drop support for Python 3.9 - ReprArgs: TypeAlias = Iterable[tuple[str | None, Any]] # pyright: ignore[reportGeneralTypeIssues] - RichReprResult: TypeAlias = Iterable[Any | tuple[Any] | tuple[str, Any] | tuple[str, Any, Any]] # pyright: ignore[reportGeneralTypeIssues] +if typing.TYPE_CHECKING: + ReprArgs: typing_extensions.TypeAlias = 'typing.Iterable[tuple[str | None, Any]]' + RichReprResult: typing_extensions.TypeAlias = ( + 'typing.Iterable[Any | tuple[Any] | tuple[str, Any] | tuple[str, Any, Any]]' + ) class PlainRepr(str): @@ -34,7 +31,8 @@ class Representation: # `__rich_repr__` is used by [rich](https://rich.readthedocs.io/en/stable/pretty.html). # (this is not a docstring to avoid adding a docstring to classes which inherit from Representation) - __slots__ = () + # we don't want to use a type annotation here as it can break get_type_hints + __slots__ = tuple() # type: typing.Collection[str] def __repr_args__(self) -> ReprArgs: """Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden. @@ -43,25 +41,20 @@ class Representation: * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]` * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]` """ - attrs_names = cast(Collection[str], self.__slots__) + attrs_names = self.__slots__ if not attrs_names and hasattr(self, '__dict__'): attrs_names = self.__dict__.keys() attrs = ((s, getattr(self, s)) for s in attrs_names) - return [(a, v if v is not self else self.__repr_recursion__(v)) for a, v in attrs if v is not None] + return [(a, v) for a, v in attrs if v is not None] def __repr_name__(self) -> str: """Name of the instance's class, used in __repr__.""" return self.__class__.__name__ - def __repr_recursion__(self, object: Any) -> str: - """Returns the string representation of a recursive object.""" - # This is copied over from the stdlib `pprint` module: - return f'' - def __repr_str__(self, join_str: str) -> str: return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__()) - def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any]: + def __pretty__(self, fmt: typing.Callable[[Any], Any], **kwargs: Any) -> typing.Generator[Any, None, None]: """Used by devtools (https://python-devtools.helpmanual.io/) to pretty print objects.""" yield self.__repr_name__() + '(' yield 1 @@ -94,30 +87,28 @@ def display_as_type(obj: Any) -> str: Takes some logic from `typing._type_repr`. """ - if isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)): + if isinstance(obj, types.FunctionType): return obj.__name__ elif obj is ...: return '...' elif isinstance(obj, Representation): return repr(obj) - elif isinstance(obj, ForwardRef) or typing_objects.is_typealiastype(obj): - return str(obj) if not isinstance(obj, (_typing_extra.typing_base, _typing_extra.WithArgsTypes, type)): obj = obj.__class__ - if is_union_origin(typing_extensions.get_origin(obj)): + if _typing_extra.origin_is_union(typing_extensions.get_origin(obj)): args = ', '.join(map(display_as_type, typing_extensions.get_args(obj))) return f'Union[{args}]' elif isinstance(obj, _typing_extra.WithArgsTypes): - if typing_objects.is_literal(typing_extensions.get_origin(obj)): + if typing_extensions.get_origin(obj) == typing_extensions.Literal: args = ', '.join(map(repr, typing_extensions.get_args(obj))) else: args = ', '.join(map(display_as_type, typing_extensions.get_args(obj))) try: return f'{obj.__qualname__}[{args}]' except AttributeError: - return str(obj).replace('typing.', '').replace('typing_extensions.', '') # handles TypeAliasType in 3.12 + return str(obj) # handles TypeAliasType in 3.12 elif isinstance(obj, type): return obj.__qualname__ else: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_gather.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_gather.py deleted file mode 100644 index fc2d806e..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_gather.py +++ /dev/null @@ -1,209 +0,0 @@ -# pyright: reportTypedDictNotRequiredAccess=false, reportGeneralTypeIssues=false, reportArgumentType=false, reportAttributeAccessIssue=false -from __future__ import annotations - -from dataclasses import dataclass, field -from typing import TypedDict - -from pydantic_core.core_schema import ComputedField, CoreSchema, DefinitionReferenceSchema, SerSchema -from typing_extensions import TypeAlias - -AllSchemas: TypeAlias = 'CoreSchema | SerSchema | ComputedField' - - -class GatherResult(TypedDict): - """Schema traversing result.""" - - collected_references: dict[str, DefinitionReferenceSchema | None] - """The collected definition references. - - If a definition reference schema can be inlined, it means that there is - only one in the whole core schema. As such, it is stored as the value. - Otherwise, the value is set to `None`. - """ - - deferred_discriminator_schemas: list[CoreSchema] - """The list of core schemas having the discriminator application deferred.""" - - -class MissingDefinitionError(LookupError): - """A reference was pointing to a non-existing core schema.""" - - def __init__(self, schema_reference: str, /) -> None: - self.schema_reference = schema_reference - - -@dataclass -class GatherContext: - """The current context used during core schema traversing. - - Context instances should only be used during schema traversing. - """ - - definitions: dict[str, CoreSchema] - """The available definitions.""" - - deferred_discriminator_schemas: list[CoreSchema] = field(init=False, default_factory=list) - """The list of core schemas having the discriminator application deferred. - - Internally, these core schemas have a specific key set in the core metadata dict. - """ - - collected_references: dict[str, DefinitionReferenceSchema | None] = field(init=False, default_factory=dict) - """The collected definition references. - - If a definition reference schema can be inlined, it means that there is - only one in the whole core schema. As such, it is stored as the value. - Otherwise, the value is set to `None`. - - During schema traversing, definition reference schemas can be added as candidates, or removed - (by setting the value to `None`). - """ - - -def traverse_metadata(schema: AllSchemas, ctx: GatherContext) -> None: - meta = schema.get('metadata') - if meta is not None and 'pydantic_internal_union_discriminator' in meta: - ctx.deferred_discriminator_schemas.append(schema) # pyright: ignore[reportArgumentType] - - -def traverse_definition_ref(def_ref_schema: DefinitionReferenceSchema, ctx: GatherContext) -> None: - schema_ref = def_ref_schema['schema_ref'] - - if schema_ref not in ctx.collected_references: - definition = ctx.definitions.get(schema_ref) - if definition is None: - raise MissingDefinitionError(schema_ref) - - # The `'definition-ref'` schema was only encountered once, make it - # a candidate to be inlined: - ctx.collected_references[schema_ref] = def_ref_schema - traverse_schema(definition, ctx) - if 'serialization' in def_ref_schema: - traverse_schema(def_ref_schema['serialization'], ctx) - traverse_metadata(def_ref_schema, ctx) - else: - # The `'definition-ref'` schema was already encountered, meaning - # the previously encountered schema (and this one) can't be inlined: - ctx.collected_references[schema_ref] = None - - -def traverse_schema(schema: AllSchemas, context: GatherContext) -> None: - # TODO When we drop 3.9, use a match statement to get better type checking and remove - # file-level type ignore. - # (the `'type'` could also be fetched in every `if/elif` statement, but this alters performance). - schema_type = schema['type'] - - if schema_type == 'definition-ref': - traverse_definition_ref(schema, context) - # `traverse_definition_ref` handles the possible serialization and metadata schemas: - return - elif schema_type == 'definitions': - traverse_schema(schema['schema'], context) - for definition in schema['definitions']: - traverse_schema(definition, context) - elif schema_type in {'list', 'set', 'frozenset', 'generator'}: - if 'items_schema' in schema: - traverse_schema(schema['items_schema'], context) - elif schema_type == 'tuple': - if 'items_schema' in schema: - for s in schema['items_schema']: - traverse_schema(s, context) - elif schema_type == 'dict': - if 'keys_schema' in schema: - traverse_schema(schema['keys_schema'], context) - if 'values_schema' in schema: - traverse_schema(schema['values_schema'], context) - elif schema_type == 'union': - for choice in schema['choices']: - if isinstance(choice, tuple): - traverse_schema(choice[0], context) - else: - traverse_schema(choice, context) - elif schema_type == 'tagged-union': - for v in schema['choices'].values(): - traverse_schema(v, context) - elif schema_type == 'chain': - for step in schema['steps']: - traverse_schema(step, context) - elif schema_type == 'lax-or-strict': - traverse_schema(schema['lax_schema'], context) - traverse_schema(schema['strict_schema'], context) - elif schema_type == 'json-or-python': - traverse_schema(schema['json_schema'], context) - traverse_schema(schema['python_schema'], context) - elif schema_type in {'model-fields', 'typed-dict'}: - if 'extras_schema' in schema: - traverse_schema(schema['extras_schema'], context) - if 'computed_fields' in schema: - for s in schema['computed_fields']: - traverse_schema(s, context) - for s in schema['fields'].values(): - traverse_schema(s, context) - elif schema_type == 'dataclass-args': - if 'computed_fields' in schema: - for s in schema['computed_fields']: - traverse_schema(s, context) - for s in schema['fields']: - traverse_schema(s, context) - elif schema_type == 'arguments': - for s in schema['arguments_schema']: - traverse_schema(s['schema'], context) - if 'var_args_schema' in schema: - traverse_schema(schema['var_args_schema'], context) - if 'var_kwargs_schema' in schema: - traverse_schema(schema['var_kwargs_schema'], context) - elif schema_type == 'arguments-v3': - for s in schema['arguments_schema']: - traverse_schema(s['schema'], context) - elif schema_type == 'call': - traverse_schema(schema['arguments_schema'], context) - if 'return_schema' in schema: - traverse_schema(schema['return_schema'], context) - elif schema_type == 'computed-field': - traverse_schema(schema['return_schema'], context) - elif schema_type == 'function-before': - if 'schema' in schema: - traverse_schema(schema['schema'], context) - if 'json_schema_input_schema' in schema: - traverse_schema(schema['json_schema_input_schema'], context) - elif schema_type == 'function-plain': - # TODO duplicate schema types for serializers and validators, needs to be deduplicated. - if 'return_schema' in schema: - traverse_schema(schema['return_schema'], context) - if 'json_schema_input_schema' in schema: - traverse_schema(schema['json_schema_input_schema'], context) - elif schema_type == 'function-wrap': - # TODO duplicate schema types for serializers and validators, needs to be deduplicated. - if 'return_schema' in schema: - traverse_schema(schema['return_schema'], context) - if 'schema' in schema: - traverse_schema(schema['schema'], context) - if 'json_schema_input_schema' in schema: - traverse_schema(schema['json_schema_input_schema'], context) - else: - if 'schema' in schema: - traverse_schema(schema['schema'], context) - - if 'serialization' in schema: - traverse_schema(schema['serialization'], context) - traverse_metadata(schema, context) - - -def gather_schemas_for_cleaning(schema: CoreSchema, definitions: dict[str, CoreSchema]) -> GatherResult: - """Traverse the core schema and definitions and return the necessary information for schema cleaning. - - During the core schema traversing, any `'definition-ref'` schema is: - - - Validated: the reference must point to an existing definition. If this is not the case, a - `MissingDefinitionError` exception is raised. - - Stored in the context: the actual reference is stored in the context. Depending on whether - the `'definition-ref'` schema is encountered more that once, the schema itself is also - saved in the context to be inlined (i.e. replaced by the definition it points to). - """ - context = GatherContext(definitions) - traverse_schema(schema, context) - - return { - 'collected_references': context.collected_references, - 'deferred_discriminator_schemas': context.deferred_discriminator_schemas, - } diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py index b231a82e..1a9aa852 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py @@ -1,10 +1,10 @@ """Types and utility functions used by various other internal tools.""" - from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Literal +from typing import TYPE_CHECKING, Any, Callable from pydantic_core import core_schema +from typing_extensions import Literal from ..annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler @@ -12,7 +12,6 @@ if TYPE_CHECKING: from ..json_schema import GenerateJsonSchema, JsonSchemaValue from ._core_utils import CoreSchemaOrField from ._generate_schema import GenerateSchema - from ._namespace_utils import NamespacesTuple GetJsonSchemaFunction = Callable[[CoreSchemaOrField, GetJsonSchemaHandler], JsonSchemaValue] HandlerOverride = Callable[[CoreSchemaOrField], JsonSchemaValue] @@ -33,8 +32,8 @@ class GenerateJsonSchemaHandler(GetJsonSchemaHandler): self.handler = handler_override or generate_json_schema.generate_inner self.mode = generate_json_schema.mode - def __call__(self, core_schema: CoreSchemaOrField, /) -> JsonSchemaValue: - return self.handler(core_schema) + def __call__(self, __core_schema: CoreSchemaOrField) -> JsonSchemaValue: + return self.handler(__core_schema) def resolve_ref_schema(self, maybe_ref_json_schema: JsonSchemaValue) -> JsonSchemaValue: """Resolves `$ref` in the json schema. @@ -79,21 +78,22 @@ class CallbackGetCoreSchemaHandler(GetCoreSchemaHandler): self._generate_schema = generate_schema self._ref_mode = ref_mode - def __call__(self, source_type: Any, /) -> core_schema.CoreSchema: - schema = self._handler(source_type) + def __call__(self, __source_type: Any) -> core_schema.CoreSchema: + schema = self._handler(__source_type) + ref = schema.get('ref') if self._ref_mode == 'to-def': - ref = schema.get('ref') if ref is not None: - return self._generate_schema.defs.create_definition_reference_schema(schema) + self._generate_schema.defs.definitions[ref] = schema + return core_schema.definition_reference_schema(ref) return schema - else: # ref_mode = 'unpack' + else: # ref_mode = 'unpack return self.resolve_ref_schema(schema) - def _get_types_namespace(self) -> NamespacesTuple: + def _get_types_namespace(self) -> dict[str, Any] | None: return self._generate_schema._types_namespace - def generate_schema(self, source_type: Any, /) -> core_schema.CoreSchema: - return self._generate_schema.generate_schema(source_type) + def generate_schema(self, __source_type: Any) -> core_schema.CoreSchema: + return self._generate_schema.generate_schema(__source_type) @property def field_name(self) -> str | None: @@ -113,13 +113,12 @@ class CallbackGetCoreSchemaHandler(GetCoreSchemaHandler): """ if maybe_ref_schema['type'] == 'definition-ref': ref = maybe_ref_schema['schema_ref'] - definition = self._generate_schema.defs.get_schema_from_ref(ref) - if definition is None: + if ref not in self._generate_schema.defs.definitions: raise LookupError( f'Could not find a ref for {ref}.' ' Maybe you tried to call resolve_ref_schema from within a recursive model?' ) - return definition + return self._generate_schema.defs.definitions[ref] elif maybe_ref_schema['type'] == 'definitions': return self.resolve_ref_schema(maybe_ref_schema['schema']) return maybe_ref_schema diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_serializers.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_serializers.py deleted file mode 100644 index a4058e00..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_serializers.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -import collections -import collections.abc -import typing -from typing import Any - -from pydantic_core import PydanticOmit, core_schema - -SEQUENCE_ORIGIN_MAP: dict[Any, Any] = { - typing.Deque: collections.deque, # noqa: UP006 - collections.deque: collections.deque, - list: list, - typing.List: list, # noqa: UP006 - tuple: tuple, - typing.Tuple: tuple, # noqa: UP006 - set: set, - typing.AbstractSet: set, - typing.Set: set, # noqa: UP006 - frozenset: frozenset, - typing.FrozenSet: frozenset, # noqa: UP006 - typing.Sequence: list, - typing.MutableSequence: list, - typing.MutableSet: set, - # this doesn't handle subclasses of these - # parametrized typing.Set creates one of these - collections.abc.MutableSet: set, - collections.abc.Set: frozenset, -} - - -def serialize_sequence_via_list( - v: Any, handler: core_schema.SerializerFunctionWrapHandler, info: core_schema.SerializationInfo -) -> Any: - items: list[Any] = [] - - mapped_origin = SEQUENCE_ORIGIN_MAP.get(type(v), None) - if mapped_origin is None: - # we shouldn't hit this branch, should probably add a serialization error or something - return v - - for index, item in enumerate(v): - try: - v = handler(item, index) - except PydanticOmit: # noqa: PERF203 - pass - else: - items.append(v) - - if info.mode_is_json(): - return items - else: - return mapped_origin(items) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_signature.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_signature.py deleted file mode 100644 index 977e5d29..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_signature.py +++ /dev/null @@ -1,188 +0,0 @@ -from __future__ import annotations - -import dataclasses -from inspect import Parameter, Signature, signature -from typing import TYPE_CHECKING, Any, Callable - -from pydantic_core import PydanticUndefined - -from ._utils import is_valid_identifier - -if TYPE_CHECKING: - from ..config import ExtraValues - from ..fields import FieldInfo - - -# Copied over from stdlib dataclasses -class _HAS_DEFAULT_FACTORY_CLASS: - def __repr__(self): - return '' - - -_HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() - - -def _field_name_for_signature(field_name: str, field_info: FieldInfo) -> str: - """Extract the correct name to use for the field when generating a signature. - - Assuming the field has a valid alias, this will return the alias. Otherwise, it will return the field name. - First priority is given to the alias, then the validation_alias, then the field name. - - Args: - field_name: The name of the field - field_info: The corresponding FieldInfo object. - - Returns: - The correct name to use when generating a signature. - """ - if isinstance(field_info.alias, str) and is_valid_identifier(field_info.alias): - return field_info.alias - if isinstance(field_info.validation_alias, str) and is_valid_identifier(field_info.validation_alias): - return field_info.validation_alias - - return field_name - - -def _process_param_defaults(param: Parameter) -> Parameter: - """Modify the signature for a parameter in a dataclass where the default value is a FieldInfo instance. - - Args: - param (Parameter): The parameter - - Returns: - Parameter: The custom processed parameter - """ - from ..fields import FieldInfo - - param_default = param.default - if isinstance(param_default, FieldInfo): - annotation = param.annotation - # Replace the annotation if appropriate - # inspect does "clever" things to show annotations as strings because we have - # `from __future__ import annotations` in main, we don't want that - if annotation == 'Any': - annotation = Any - - # Replace the field default - default = param_default.default - if default is PydanticUndefined: - if param_default.default_factory is PydanticUndefined: - default = Signature.empty - else: - # this is used by dataclasses to indicate a factory exists: - default = dataclasses._HAS_DEFAULT_FACTORY # type: ignore - return param.replace( - annotation=annotation, name=_field_name_for_signature(param.name, param_default), default=default - ) - return param - - -def _generate_signature_parameters( # noqa: C901 (ignore complexity, could use a refactor) - init: Callable[..., None], - fields: dict[str, FieldInfo], - validate_by_name: bool, - extra: ExtraValues | None, -) -> dict[str, Parameter]: - """Generate a mapping of parameter names to Parameter objects for a pydantic BaseModel or dataclass.""" - from itertools import islice - - present_params = signature(init).parameters.values() - merged_params: dict[str, Parameter] = {} - var_kw = None - use_var_kw = False - - for param in islice(present_params, 1, None): # skip self arg - # inspect does "clever" things to show annotations as strings because we have - # `from __future__ import annotations` in main, we don't want that - if fields.get(param.name): - # exclude params with init=False - if getattr(fields[param.name], 'init', True) is False: - continue - param = param.replace(name=_field_name_for_signature(param.name, fields[param.name])) - if param.annotation == 'Any': - param = param.replace(annotation=Any) - if param.kind is param.VAR_KEYWORD: - var_kw = param - continue - merged_params[param.name] = param - - if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through - allow_names = validate_by_name - for field_name, field in fields.items(): - # when alias is a str it should be used for signature generation - param_name = _field_name_for_signature(field_name, field) - - if field_name in merged_params or param_name in merged_params: - continue - - if not is_valid_identifier(param_name): - if allow_names: - param_name = field_name - else: - use_var_kw = True - continue - - if field.is_required(): - default = Parameter.empty - elif field.default_factory is not None: - # Mimics stdlib dataclasses: - default = _HAS_DEFAULT_FACTORY - else: - default = field.default - merged_params[param_name] = Parameter( - param_name, - Parameter.KEYWORD_ONLY, - annotation=field.rebuild_annotation(), - default=default, - ) - - if extra == 'allow': - use_var_kw = True - - if var_kw and use_var_kw: - # Make sure the parameter for extra kwargs - # does not have the same name as a field - default_model_signature = [ - ('self', Parameter.POSITIONAL_ONLY), - ('data', Parameter.VAR_KEYWORD), - ] - if [(p.name, p.kind) for p in present_params] == default_model_signature: - # if this is the standard model signature, use extra_data as the extra args name - var_kw_name = 'extra_data' - else: - # else start from var_kw - var_kw_name = var_kw.name - - # generate a name that's definitely unique - while var_kw_name in fields: - var_kw_name += '_' - merged_params[var_kw_name] = var_kw.replace(name=var_kw_name) - - return merged_params - - -def generate_pydantic_signature( - init: Callable[..., None], - fields: dict[str, FieldInfo], - validate_by_name: bool, - extra: ExtraValues | None, - is_dataclass: bool = False, -) -> Signature: - """Generate signature for a pydantic BaseModel or dataclass. - - Args: - init: The class init. - fields: The model fields. - validate_by_name: The `validate_by_name` value of the config. - extra: The `extra` value of the config. - is_dataclass: Whether the model is a dataclass. - - Returns: - The dataclass/BaseModel subclass signature. - """ - merged_params = _generate_signature_parameters(init, fields, validate_by_name, extra) - - if is_dataclass: - merged_params = {k: _process_param_defaults(v) for k, v in merged_params.items()} - - return Signature(parameters=list(merged_params.values()), return_annotation=None) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_std_types_schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_std_types_schema.py new file mode 100644 index 00000000..c8523bf4 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_std_types_schema.py @@ -0,0 +1,714 @@ +"""Logic for generating pydantic-core schemas for standard library types. + +Import of this module is deferred since it contains imports of many standard library modules. +""" +from __future__ import annotations as _annotations + +import collections +import collections.abc +import dataclasses +import decimal +import inspect +import os +import typing +from enum import Enum +from functools import partial +from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network +from typing import Any, Callable, Iterable, TypeVar + +import typing_extensions +from pydantic_core import ( + CoreSchema, + MultiHostUrl, + PydanticCustomError, + PydanticOmit, + Url, + core_schema, +) +from typing_extensions import get_args, get_origin + +from pydantic.errors import PydanticSchemaGenerationError +from pydantic.fields import FieldInfo +from pydantic.types import Strict + +from ..config import ConfigDict +from ..json_schema import JsonSchemaValue, update_json_schema +from . import _known_annotated_metadata, _typing_extra, _validators +from ._core_utils import get_type_ref +from ._internal_dataclass import slots_true +from ._schema_generation_shared import GetCoreSchemaHandler, GetJsonSchemaHandler + +if typing.TYPE_CHECKING: + from ._generate_schema import GenerateSchema + + StdSchemaFunction = Callable[[GenerateSchema, type[Any]], core_schema.CoreSchema] + + +@dataclasses.dataclass(**slots_true) +class SchemaTransformer: + get_core_schema: Callable[[Any, GetCoreSchemaHandler], CoreSchema] + get_json_schema: Callable[[CoreSchema, GetJsonSchemaHandler], JsonSchemaValue] + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + return self.get_core_schema(source_type, handler) + + def __get_pydantic_json_schema__(self, schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + return self.get_json_schema(schema, handler) + + +def get_enum_core_schema(enum_type: type[Enum], config: ConfigDict) -> CoreSchema: + cases: list[Any] = list(enum_type.__members__.values()) + + enum_ref = get_type_ref(enum_type) + description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) + if description == 'An enumeration.': # This is the default value provided by enum.EnumMeta.__new__; don't use it + description = None + updates = {'title': enum_type.__name__, 'description': description} + updates = {k: v for k, v in updates.items() if v is not None} + + def get_json_schema(_, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + json_schema = handler(core_schema.literal_schema([x.value for x in cases], ref=enum_ref)) + original_schema = handler.resolve_ref_schema(json_schema) + update_json_schema(original_schema, updates) + return json_schema + + if not cases: + # Use an isinstance check for enums with no cases. + # The most important use case for this is creating TypeVar bounds for generics that should + # be restricted to enums. This is more consistent than it might seem at first, since you can only + # subclass enum.Enum (or subclasses of enum.Enum) if all parent classes have no cases. + # We use the get_json_schema function when an Enum subclass has been declared with no cases + # so that we can still generate a valid json schema. + return core_schema.is_instance_schema(enum_type, metadata={'pydantic_js_functions': [get_json_schema]}) + + use_enum_values = config.get('use_enum_values', False) + + if len(cases) == 1: + expected = repr(cases[0].value) + else: + expected = ', '.join([repr(case.value) for case in cases[:-1]]) + f' or {cases[-1].value!r}' + + def to_enum(__input_value: Any) -> Enum: + try: + enum_field = enum_type(__input_value) + if use_enum_values: + return enum_field.value + return enum_field + except ValueError: + # The type: ignore on the next line is to ignore the requirement of LiteralString + raise PydanticCustomError('enum', f'Input should be {expected}', {'expected': expected}) # type: ignore + + strict_python_schema = core_schema.is_instance_schema(enum_type) + if use_enum_values: + strict_python_schema = core_schema.chain_schema( + [strict_python_schema, core_schema.no_info_plain_validator_function(lambda x: x.value)] + ) + + to_enum_validator = core_schema.no_info_plain_validator_function(to_enum) + if issubclass(enum_type, int): + # this handles `IntEnum`, and also `Foobar(int, Enum)` + updates['type'] = 'integer' + lax = core_schema.chain_schema([core_schema.int_schema(), to_enum_validator]) + # Disallow float from JSON due to strict mode + strict = core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(to_enum, core_schema.int_schema()), + python_schema=strict_python_schema, + ) + elif issubclass(enum_type, str): + # this handles `StrEnum` (3.11 only), and also `Foobar(str, Enum)` + updates['type'] = 'string' + lax = core_schema.chain_schema([core_schema.str_schema(), to_enum_validator]) + strict = core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(to_enum, core_schema.str_schema()), + python_schema=strict_python_schema, + ) + elif issubclass(enum_type, float): + updates['type'] = 'numeric' + lax = core_schema.chain_schema([core_schema.float_schema(), to_enum_validator]) + strict = core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(to_enum, core_schema.float_schema()), + python_schema=strict_python_schema, + ) + else: + lax = to_enum_validator + strict = core_schema.json_or_python_schema(json_schema=to_enum_validator, python_schema=strict_python_schema) + return core_schema.lax_or_strict_schema( + lax_schema=lax, strict_schema=strict, ref=enum_ref, metadata={'pydantic_js_functions': [get_json_schema]} + ) + + +@dataclasses.dataclass(**slots_true) +class InnerSchemaValidator: + """Use a fixed CoreSchema, avoiding interference from outward annotations.""" + + core_schema: CoreSchema + js_schema: JsonSchemaValue | None = None + js_core_schema: CoreSchema | None = None + js_schema_update: JsonSchemaValue | None = None + + def __get_pydantic_json_schema__(self, _schema: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: + if self.js_schema is not None: + return self.js_schema + js_schema = handler(self.js_core_schema or self.core_schema) + if self.js_schema_update is not None: + js_schema.update(self.js_schema_update) + return js_schema + + def __get_pydantic_core_schema__(self, _source_type: Any, _handler: GetCoreSchemaHandler) -> CoreSchema: + return self.core_schema + + +def decimal_prepare_pydantic_annotations( + source: Any, annotations: Iterable[Any], config: ConfigDict +) -> tuple[Any, list[Any]] | None: + if source is not decimal.Decimal: + return None + + metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) + + config_allow_inf_nan = config.get('allow_inf_nan') + if config_allow_inf_nan is not None: + metadata.setdefault('allow_inf_nan', config_allow_inf_nan) + + _known_annotated_metadata.check_metadata( + metadata, {*_known_annotated_metadata.FLOAT_CONSTRAINTS, 'max_digits', 'decimal_places'}, decimal.Decimal + ) + return source, [InnerSchemaValidator(core_schema.decimal_schema(**metadata)), *remaining_annotations] + + +def datetime_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + import datetime + + metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) + if source_type is datetime.date: + sv = InnerSchemaValidator(core_schema.date_schema(**metadata)) + elif source_type is datetime.datetime: + sv = InnerSchemaValidator(core_schema.datetime_schema(**metadata)) + elif source_type is datetime.time: + sv = InnerSchemaValidator(core_schema.time_schema(**metadata)) + elif source_type is datetime.timedelta: + sv = InnerSchemaValidator(core_schema.timedelta_schema(**metadata)) + else: + return None + # check now that we know the source type is correct + _known_annotated_metadata.check_metadata(metadata, _known_annotated_metadata.DATE_TIME_CONSTRAINTS, source_type) + return (source_type, [sv, *remaining_annotations]) + + +def uuid_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + # UUIDs have no constraints - they are fixed length, constructing a UUID instance checks the length + + from uuid import UUID + + if source_type is not UUID: + return None + + return (source_type, [InnerSchemaValidator(core_schema.uuid_schema()), *annotations]) + + +def path_schema_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + import pathlib + + if source_type not in { + os.PathLike, + pathlib.Path, + pathlib.PurePath, + pathlib.PosixPath, + pathlib.PurePosixPath, + pathlib.PureWindowsPath, + }: + return None + + metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) + _known_annotated_metadata.check_metadata(metadata, _known_annotated_metadata.STR_CONSTRAINTS, source_type) + + construct_path = pathlib.PurePath if source_type is os.PathLike else source_type + + def path_validator(input_value: str) -> os.PathLike[Any]: + try: + return construct_path(input_value) + except TypeError as e: + raise PydanticCustomError('path_type', 'Input is not a valid path') from e + + constrained_str_schema = core_schema.str_schema(**metadata) + + instance_schema = core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(path_validator, constrained_str_schema), + python_schema=core_schema.is_instance_schema(source_type), + ) + + strict: bool | None = None + for annotation in annotations: + if isinstance(annotation, Strict): + strict = annotation.strict + + schema = core_schema.lax_or_strict_schema( + lax_schema=core_schema.union_schema( + [ + instance_schema, + core_schema.no_info_after_validator_function(path_validator, constrained_str_schema), + ], + custom_error_type='path_type', + custom_error_message='Input is not a valid path', + strict=True, + ), + strict_schema=instance_schema, + serialization=core_schema.to_string_ser_schema(), + strict=strict, + ) + + return ( + source_type, + [ + InnerSchemaValidator(schema, js_core_schema=constrained_str_schema, js_schema_update={'format': 'path'}), + *remaining_annotations, + ], + ) + + +def dequeue_validator( + input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, maxlen: None | int +) -> collections.deque[Any]: + if isinstance(input_value, collections.deque): + maxlens = [v for v in (input_value.maxlen, maxlen) if v is not None] + if maxlens: + maxlen = min(maxlens) + return collections.deque(handler(input_value), maxlen=maxlen) + else: + return collections.deque(handler(input_value), maxlen=maxlen) + + +@dataclasses.dataclass(**slots_true) +class SequenceValidator: + mapped_origin: type[Any] + item_source_type: type[Any] + min_length: int | None = None + max_length: int | None = None + strict: bool = False + + def serialize_sequence_via_list( + self, v: Any, handler: core_schema.SerializerFunctionWrapHandler, info: core_schema.SerializationInfo + ) -> Any: + items: list[Any] = [] + for index, item in enumerate(v): + try: + v = handler(item, index) + except PydanticOmit: + pass + else: + items.append(v) + + if info.mode_is_json(): + return items + else: + return self.mapped_origin(items) + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + if self.item_source_type is Any: + items_schema = None + else: + items_schema = handler.generate_schema(self.item_source_type) + + metadata = {'min_length': self.min_length, 'max_length': self.max_length, 'strict': self.strict} + + if self.mapped_origin in (list, set, frozenset): + if self.mapped_origin is list: + constrained_schema = core_schema.list_schema(items_schema, **metadata) + elif self.mapped_origin is set: + constrained_schema = core_schema.set_schema(items_schema, **metadata) + else: + assert self.mapped_origin is frozenset # safety check in case we forget to add a case + constrained_schema = core_schema.frozenset_schema(items_schema, **metadata) + + schema = constrained_schema + else: + # safety check in case we forget to add a case + assert self.mapped_origin in (collections.deque, collections.Counter) + + if self.mapped_origin is collections.deque: + # if we have a MaxLen annotation might as well set that as the default maxlen on the deque + # this lets us re-use existing metadata annotations to let users set the maxlen on a dequeue + # that e.g. comes from JSON + coerce_instance_wrap = partial( + core_schema.no_info_wrap_validator_function, + partial(dequeue_validator, maxlen=metadata.get('max_length', None)), + ) + else: + coerce_instance_wrap = partial(core_schema.no_info_after_validator_function, self.mapped_origin) + + constrained_schema = core_schema.list_schema(items_schema, **metadata) + + check_instance = core_schema.json_or_python_schema( + json_schema=core_schema.list_schema(), + python_schema=core_schema.is_instance_schema(self.mapped_origin), + ) + + serialization = core_schema.wrap_serializer_function_ser_schema( + self.serialize_sequence_via_list, schema=items_schema or core_schema.any_schema(), info_arg=True + ) + + strict = core_schema.chain_schema([check_instance, coerce_instance_wrap(constrained_schema)]) + + if metadata.get('strict', False): + schema = strict + else: + lax = coerce_instance_wrap(constrained_schema) + schema = core_schema.lax_or_strict_schema(lax_schema=lax, strict_schema=strict) + schema['serialization'] = serialization + + return schema + + +SEQUENCE_ORIGIN_MAP: dict[Any, Any] = { + typing.Deque: collections.deque, + collections.deque: collections.deque, + list: list, + typing.List: list, + set: set, + typing.AbstractSet: set, + typing.Set: set, + frozenset: frozenset, + typing.FrozenSet: frozenset, + typing.Sequence: list, + typing.MutableSequence: list, + typing.MutableSet: set, + # this doesn't handle subclasses of these + # parametrized typing.Set creates one of these + collections.abc.MutableSet: set, + collections.abc.Set: frozenset, +} + + +def identity(s: CoreSchema) -> CoreSchema: + return s + + +def sequence_like_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + origin: Any = get_origin(source_type) + + mapped_origin = SEQUENCE_ORIGIN_MAP.get(origin, None) if origin else SEQUENCE_ORIGIN_MAP.get(source_type, None) + if mapped_origin is None: + return None + + args = get_args(source_type) + + if not args: + args = (Any,) + elif len(args) != 1: + raise ValueError('Expected sequence to have exactly 1 generic parameter') + + item_source_type = args[0] + + metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) + _known_annotated_metadata.check_metadata(metadata, _known_annotated_metadata.SEQUENCE_CONSTRAINTS, source_type) + + return (source_type, [SequenceValidator(mapped_origin, item_source_type, **metadata), *remaining_annotations]) + + +MAPPING_ORIGIN_MAP: dict[Any, Any] = { + typing.DefaultDict: collections.defaultdict, + collections.defaultdict: collections.defaultdict, + collections.OrderedDict: collections.OrderedDict, + typing_extensions.OrderedDict: collections.OrderedDict, + dict: dict, + typing.Dict: dict, + collections.Counter: collections.Counter, + typing.Counter: collections.Counter, + # this doesn't handle subclasses of these + typing.Mapping: dict, + typing.MutableMapping: dict, + # parametrized typing.{Mutable}Mapping creates one of these + collections.abc.MutableMapping: dict, + collections.abc.Mapping: dict, +} + + +def defaultdict_validator( + input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, default_default_factory: Callable[[], Any] +) -> collections.defaultdict[Any, Any]: + if isinstance(input_value, collections.defaultdict): + default_factory = input_value.default_factory + return collections.defaultdict(default_factory, handler(input_value)) + else: + return collections.defaultdict(default_default_factory, handler(input_value)) + + +def get_defaultdict_default_default_factory(values_source_type: Any) -> Callable[[], Any]: + def infer_default() -> Callable[[], Any]: + allowed_default_types: dict[Any, Any] = { + typing.Tuple: tuple, + tuple: tuple, + collections.abc.Sequence: tuple, + collections.abc.MutableSequence: list, + typing.List: list, + list: list, + typing.Sequence: list, + typing.Set: set, + set: set, + typing.MutableSet: set, + collections.abc.MutableSet: set, + collections.abc.Set: frozenset, + typing.MutableMapping: dict, + typing.Mapping: dict, + collections.abc.Mapping: dict, + collections.abc.MutableMapping: dict, + float: float, + int: int, + str: str, + bool: bool, + } + values_type_origin = get_origin(values_source_type) or values_source_type + instructions = 'set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]`' + if isinstance(values_type_origin, TypeVar): + + def type_var_default_factory() -> None: + raise RuntimeError( + 'Generic defaultdict cannot be used without a concrete value type or an' + ' explicit default factory, ' + instructions + ) + + return type_var_default_factory + elif values_type_origin not in allowed_default_types: + # a somewhat subjective set of types that have reasonable default values + allowed_msg = ', '.join([t.__name__ for t in set(allowed_default_types.values())]) + raise PydanticSchemaGenerationError( + f'Unable to infer a default factory for keys of type {values_source_type}.' + f' Only {allowed_msg} are supported, other types require an explicit default factory' + ' ' + instructions + ) + return allowed_default_types[values_type_origin] + + # Assume Annotated[..., Field(...)] + if _typing_extra.is_annotated(values_source_type): + field_info = next((v for v in get_args(values_source_type) if isinstance(v, FieldInfo)), None) + else: + field_info = None + if field_info and field_info.default_factory: + default_default_factory = field_info.default_factory + else: + default_default_factory = infer_default() + return default_default_factory + + +@dataclasses.dataclass(**slots_true) +class MappingValidator: + mapped_origin: type[Any] + keys_source_type: type[Any] + values_source_type: type[Any] + min_length: int | None = None + max_length: int | None = None + strict: bool = False + + def serialize_mapping_via_dict(self, v: Any, handler: core_schema.SerializerFunctionWrapHandler) -> Any: + return handler(v) + + def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: + if self.keys_source_type is Any: + keys_schema = None + else: + keys_schema = handler.generate_schema(self.keys_source_type) + if self.values_source_type is Any: + values_schema = None + else: + values_schema = handler.generate_schema(self.values_source_type) + + metadata = {'min_length': self.min_length, 'max_length': self.max_length, 'strict': self.strict} + + if self.mapped_origin is dict: + schema = core_schema.dict_schema(keys_schema, values_schema, **metadata) + else: + constrained_schema = core_schema.dict_schema(keys_schema, values_schema, **metadata) + check_instance = core_schema.json_or_python_schema( + json_schema=core_schema.dict_schema(), + python_schema=core_schema.is_instance_schema(self.mapped_origin), + ) + + if self.mapped_origin is collections.defaultdict: + default_default_factory = get_defaultdict_default_default_factory(self.values_source_type) + coerce_instance_wrap = partial( + core_schema.no_info_wrap_validator_function, + partial(defaultdict_validator, default_default_factory=default_default_factory), + ) + else: + coerce_instance_wrap = partial(core_schema.no_info_after_validator_function, self.mapped_origin) + + serialization = core_schema.wrap_serializer_function_ser_schema( + self.serialize_mapping_via_dict, + schema=core_schema.dict_schema( + keys_schema or core_schema.any_schema(), values_schema or core_schema.any_schema() + ), + info_arg=False, + ) + + strict = core_schema.chain_schema([check_instance, coerce_instance_wrap(constrained_schema)]) + + if metadata.get('strict', False): + schema = strict + else: + lax = coerce_instance_wrap(constrained_schema) + schema = core_schema.lax_or_strict_schema(lax_schema=lax, strict_schema=strict) + schema['serialization'] = serialization + + return schema + + +def mapping_like_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + origin: Any = get_origin(source_type) + + mapped_origin = MAPPING_ORIGIN_MAP.get(origin, None) if origin else MAPPING_ORIGIN_MAP.get(source_type, None) + if mapped_origin is None: + return None + + args = get_args(source_type) + + if not args: + args = (Any, Any) + elif mapped_origin is collections.Counter: + # a single generic + if len(args) != 1: + raise ValueError('Expected Counter to have exactly 1 generic parameter') + args = (args[0], int) # keys are always an int + elif len(args) != 2: + raise ValueError('Expected mapping to have exactly 2 generic parameters') + + keys_source_type, values_source_type = args + + metadata, remaining_annotations = _known_annotated_metadata.collect_known_metadata(annotations) + _known_annotated_metadata.check_metadata(metadata, _known_annotated_metadata.SEQUENCE_CONSTRAINTS, source_type) + + return ( + source_type, + [ + MappingValidator(mapped_origin, keys_source_type, values_source_type, **metadata), + *remaining_annotations, + ], + ) + + +def ip_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + def make_strict_ip_schema(tp: type[Any]) -> CoreSchema: + return core_schema.json_or_python_schema( + json_schema=core_schema.no_info_after_validator_function(tp, core_schema.str_schema()), + python_schema=core_schema.is_instance_schema(tp), + ) + + if source_type is IPv4Address: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v4_address_validator), + strict_schema=make_strict_ip_schema(IPv4Address), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv4'}, + ), + *annotations, + ] + if source_type is IPv4Network: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v4_network_validator), + strict_schema=make_strict_ip_schema(IPv4Network), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv4network'}, + ), + *annotations, + ] + if source_type is IPv4Interface: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v4_interface_validator), + strict_schema=make_strict_ip_schema(IPv4Interface), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv4interface'}, + ), + *annotations, + ] + + if source_type is IPv6Address: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v6_address_validator), + strict_schema=make_strict_ip_schema(IPv6Address), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv6'}, + ), + *annotations, + ] + if source_type is IPv6Network: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v6_network_validator), + strict_schema=make_strict_ip_schema(IPv6Network), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv6network'}, + ), + *annotations, + ] + if source_type is IPv6Interface: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.lax_or_strict_schema( + lax_schema=core_schema.no_info_plain_validator_function(_validators.ip_v6_interface_validator), + strict_schema=make_strict_ip_schema(IPv6Interface), + serialization=core_schema.to_string_ser_schema(), + ), + lambda _1, _2: {'type': 'string', 'format': 'ipv6interface'}, + ), + *annotations, + ] + + return None + + +def url_prepare_pydantic_annotations( + source_type: Any, annotations: Iterable[Any], _config: ConfigDict +) -> tuple[Any, list[Any]] | None: + if source_type is Url: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.url_schema(), + lambda cs, handler: handler(cs), + ), + *annotations, + ] + if source_type is MultiHostUrl: + return source_type, [ + SchemaTransformer( + lambda _1, _2: core_schema.multi_host_url_schema(), + lambda cs, handler: handler(cs), + ), + *annotations, + ] + + +PREPARE_METHODS: tuple[Callable[[Any, Iterable[Any], ConfigDict], tuple[Any, list[Any]] | None], ...] = ( + decimal_prepare_pydantic_annotations, + sequence_like_prepare_pydantic_annotations, + datetime_prepare_pydantic_annotations, + uuid_prepare_pydantic_annotations, + path_schema_prepare_pydantic_annotations, + mapping_like_prepare_pydantic_annotations, + ip_prepare_pydantic_annotations, + url_prepare_pydantic_annotations, +) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_typing_extra.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_typing_extra.py index 986ee42e..8b94d472 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_typing_extra.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_typing_extra.py @@ -1,589 +1,244 @@ -"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap Python's typing module.""" +"""Logic for interacting with type annotations, mostly extensions, shims and hacks to wrap python's typing module.""" +from __future__ import annotations as _annotations -from __future__ import annotations - -import collections.abc -import re +import dataclasses import sys import types import typing +from collections.abc import Callable from functools import partial -from typing import TYPE_CHECKING, Any, Callable, cast +from types import GetSetDescriptorType +from typing import TYPE_CHECKING, Any, ForwardRef -import typing_extensions -from typing_extensions import deprecated, get_args, get_origin -from typing_inspection import typing_objects -from typing_inspection.introspection import is_union_origin +from typing_extensions import Annotated, Final, Literal, TypeAliasType, TypeGuard, get_args, get_origin -from pydantic.version import version_short +if TYPE_CHECKING: + from ._dataclasses import StandardDataclass + +try: + from typing import _TypingBase # type: ignore[attr-defined] +except ImportError: + from typing import _Final as _TypingBase # type: ignore[attr-defined] + +typing_base = _TypingBase + + +if sys.version_info < (3, 9): + # python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on) + TypingGenericAlias = () +else: + from typing import GenericAlias as TypingGenericAlias # type: ignore + + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, Required +else: + from typing import NotRequired, Required # noqa: F401 + + +if sys.version_info < (3, 10): + + def origin_is_union(tp: type[Any] | None) -> bool: + return tp is typing.Union + + WithArgsTypes = (TypingGenericAlias,) + +else: + + def origin_is_union(tp: type[Any] | None) -> bool: + return tp is typing.Union or tp is types.UnionType + + WithArgsTypes = typing._GenericAlias, types.GenericAlias, types.UnionType # type: ignore[attr-defined] -from ._namespace_utils import GlobalsNamespace, MappingNamespace, NsResolver, get_module_ns_of if sys.version_info < (3, 10): NoneType = type(None) EllipsisType = type(Ellipsis) else: - from types import EllipsisType as EllipsisType from types import NoneType as NoneType -if sys.version_info >= (3, 14): - import annotationlib -if TYPE_CHECKING: - from pydantic import BaseModel +LITERAL_TYPES: set[Any] = {Literal} +if hasattr(typing, 'Literal'): + LITERAL_TYPES.add(typing.Literal) # type: ignore -# As per https://typing-extensions.readthedocs.io/en/latest/#runtime-use-of-types, -# always check for both `typing` and `typing_extensions` variants of a typing construct. -# (this is implemented differently than the suggested approach in the `typing_extensions` -# docs for performance). +NONE_TYPES: tuple[Any, ...] = (None, NoneType, *(tp[None] for tp in LITERAL_TYPES)) -_t_annotated = typing.Annotated -_te_annotated = typing_extensions.Annotated +TypeVarType = Any # since mypy doesn't allow the use of TypeVar as a type -def is_annotated(tp: Any, /) -> bool: - """Return whether the provided argument is a `Annotated` special form. +def is_none_type(type_: Any) -> bool: + return type_ in NONE_TYPES - ```python {test="skip" lint="skip"} - is_annotated(Annotated[int, ...]) - #> True - ``` + +def is_callable_type(type_: type[Any]) -> bool: + return type_ is Callable or get_origin(type_) is Callable + + +def is_literal_type(type_: type[Any]) -> bool: + return Literal is not None and get_origin(type_) in LITERAL_TYPES + + +def literal_values(type_: type[Any]) -> tuple[Any, ...]: + return get_args(type_) + + +def all_literal_values(type_: type[Any]) -> list[Any]: + """This method is used to retrieve all Literal values as + Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) + e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]`. """ - origin = get_origin(tp) - return origin is _t_annotated or origin is _te_annotated + if not is_literal_type(type_): + return [type_] + + values = literal_values(type_) + return list(x for value in values for x in all_literal_values(value)) -def annotated_type(tp: Any, /) -> Any | None: - """Return the type of the `Annotated` special form, or `None`.""" - return tp.__origin__ if typing_objects.is_annotated(get_origin(tp)) else None +def is_annotated(ann_type: Any) -> bool: + from ._utils import lenient_issubclass + + origin = get_origin(ann_type) + return origin is not None and lenient_issubclass(origin, Annotated) -def unpack_type(tp: Any, /) -> Any | None: - """Return the type wrapped by the `Unpack` special form, or `None`.""" - return get_args(tp)[0] if typing_objects.is_unpack(get_origin(tp)) else None - - -def is_hashable(tp: Any, /) -> bool: - """Return whether the provided argument is the `Hashable` class. - - ```python {test="skip" lint="skip"} - is_hashable(Hashable) - #> True - ``` +def is_namedtuple(type_: type[Any]) -> bool: + """Check if a given class is a named tuple. + It can be either a `typing.NamedTuple` or `collections.namedtuple`. """ - # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, - # hence the second check: - return tp is collections.abc.Hashable or get_origin(tp) is collections.abc.Hashable + from ._utils import lenient_issubclass + + return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') -def is_callable(tp: Any, /) -> bool: - """Return whether the provided argument is a `Callable`, parametrized or not. +test_new_type = typing.NewType('test_new_type', str) - ```python {test="skip" lint="skip"} - is_callable(Callable[[int], str]) - #> True - is_callable(typing.Callable) - #> True - is_callable(collections.abc.Callable) - #> True - ``` + +def is_new_type(type_: type[Any]) -> bool: + """Check whether type_ was created using typing.NewType. + + Can't use isinstance because it fails <3.10. """ - # `get_origin` is documented as normalizing any typing-module aliases to `collections` classes, - # hence the second check: - return tp is collections.abc.Callable or get_origin(tp) is collections.abc.Callable + return isinstance(type_, test_new_type.__class__) and hasattr(type_, '__supertype__') # type: ignore[arg-type] -_classvar_re = re.compile(r'((\w+\.)?Annotated\[)?(\w+\.)?ClassVar\[') +def _check_classvar(v: type[Any] | None) -> bool: + if v is None: + return False + + return v.__class__ == typing.ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar' -def is_classvar_annotation(tp: Any, /) -> bool: - """Return whether the provided argument represents a class variable annotation. - - Although not explicitly stated by the typing specification, `ClassVar` can be used - inside `Annotated` and as such, this function checks for this specific scenario. - - Because this function is used to detect class variables before evaluating forward references - (or because evaluation failed), we also implement a naive regex match implementation. This is - required because class variables are inspected before fields are collected, so we try to be - as accurate as possible. - """ - if typing_objects.is_classvar(tp): +def is_classvar(ann_type: type[Any]) -> bool: + if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)): return True - origin = get_origin(tp) - - if typing_objects.is_classvar(origin): - return True - - if typing_objects.is_annotated(origin): - annotated_type = tp.__origin__ - if typing_objects.is_classvar(annotated_type) or typing_objects.is_classvar(get_origin(annotated_type)): - return True - - str_ann: str | None = None - if isinstance(tp, typing.ForwardRef): - str_ann = tp.__forward_arg__ - if isinstance(tp, str): - str_ann = tp - - if str_ann is not None and _classvar_re.match(str_ann): - # stdlib dataclasses do something similar, although a bit more advanced - # (see `dataclass._is_type`). + # this is an ugly workaround for class vars that contain forward references and are therefore themselves + # forward references, see #3679 + if ann_type.__class__ == typing.ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['): # type: ignore return True return False -_t_final = typing.Final -_te_final = typing_extensions.Final +def _check_finalvar(v: type[Any] | None) -> bool: + """Check if a given type is a `typing.Final` type.""" + if v is None: + return False + + return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final') -# TODO implement `is_finalvar_annotation` as Final can be wrapped with other special forms: -def is_finalvar(tp: Any, /) -> bool: - """Return whether the provided argument is a `Final` special form, parametrized or not. - - ```python {test="skip" lint="skip"} - is_finalvar(Final[int]) - #> True - is_finalvar(Final) - #> True - """ - # Final is not necessarily parametrized: - if tp is _t_final or tp is _te_final: - return True - origin = get_origin(tp) - return origin is _t_final or origin is _te_final +def is_finalvar(ann_type: Any) -> bool: + return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type)) -_NONE_TYPES: tuple[Any, ...] = (None, NoneType, typing.Literal[None], typing_extensions.Literal[None]) +def parent_frame_namespace(*, parent_depth: int = 2) -> dict[str, Any] | None: + """We allow use of items in parent namespace to get around the issue with `get_type_hints` only looking in the + global module namespace. See https://github.com/pydantic/pydantic/issues/2678#issuecomment-1008139014 -> Scope + and suggestion at the end of the next comment by @gvanrossum. + WARNING 1: it matters exactly where this is called. By default, this function will build a namespace from the + parent of where it is called. -def is_none_type(tp: Any, /) -> bool: - """Return whether the argument represents the `None` type as part of an annotation. - - ```python {test="skip" lint="skip"} - is_none_type(None) - #> True - is_none_type(NoneType) - #> True - is_none_type(Literal[None]) - #> True - is_none_type(type[None]) - #> False - """ - return tp in _NONE_TYPES - - -def is_namedtuple(tp: Any, /) -> bool: - """Return whether the provided argument is a named tuple class. - - The class can be created using `typing.NamedTuple` or `collections.namedtuple`. - Parametrized generic classes are *not* assumed to be named tuples. - """ - from ._utils import lenient_issubclass # circ. import - - return lenient_issubclass(tp, tuple) and hasattr(tp, '_fields') - - -# TODO In 2.12, delete this export. It is currently defined only to not break -# pydantic-settings which relies on it: -origin_is_union = is_union_origin - - -def is_generic_alias(tp: Any, /) -> bool: - return isinstance(tp, (types.GenericAlias, typing._GenericAlias)) # pyright: ignore[reportAttributeAccessIssue] - - -# TODO: Ideally, we should avoid relying on the private `typing` constructs: - -if sys.version_info < (3, 10): - WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # pyright: ignore[reportAttributeAccessIssue] -else: - WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias, types.UnionType) # pyright: ignore[reportAttributeAccessIssue] - - -# Similarly, we shouldn't rely on this `_Final` class, which is even more private than `_GenericAlias`: -typing_base: Any = typing._Final # pyright: ignore[reportAttributeAccessIssue] - - -### Annotation evaluations functions: - - -def parent_frame_namespace(*, parent_depth: int = 2, force: bool = False) -> dict[str, Any] | None: - """Fetch the local namespace of the parent frame where this function is called. - - Using this function is mostly useful to resolve forward annotations pointing to members defined in a local namespace, - such as assignments inside a function. Using the standard library tools, it is currently not possible to resolve - such annotations: - - ```python {lint="skip" test="skip"} - from typing import get_type_hints - - def func() -> None: - Alias = int - - class C: - a: 'Alias' - - # Raises a `NameError: 'Alias' is not defined` - get_type_hints(C) - ``` - - Pydantic uses this function when a Pydantic model is being defined to fetch the parent frame locals. However, - this only allows us to fetch the parent frame namespace and not other parents (e.g. a model defined in a function, - itself defined in another function). Inspecting the next outer frames (using `f_back`) is not reliable enough - (see https://discuss.python.org/t/20659). - - Because this function is mostly used to better resolve forward annotations, nothing is returned if the parent frame's - code object is defined at the module level. In this case, the locals of the frame will be the same as the module - globals where the class is defined (see `_namespace_utils.get_module_ns_of`). However, if you still want to fetch - the module globals (e.g. when rebuilding a model, where the frame where the rebuild call is performed might contain - members that you want to use for forward annotations evaluation), you can use the `force` parameter. - - Args: - parent_depth: The depth at which to get the frame. Defaults to 2, meaning the parent frame where this function - is called will be used. - force: Whether to always return the frame locals, even if the frame's code object is defined at the module level. - - Returns: - The locals of the namespace, or `None` if it was skipped as per the described logic. + WARNING 2: this only looks in the parent namespace, not other parents since (AFAIK) there's no way to collect a + dict of exactly what's in scope. Using `f_back` would work sometimes but would be very wrong and confusing in many + other cases. See https://discuss.python.org/t/is-there-a-way-to-access-parent-nested-namespaces/20659. """ frame = sys._getframe(parent_depth) - - if frame.f_code.co_name.startswith('`, - # and we need to skip this frame as it is irrelevant. - frame = cast(types.FrameType, frame.f_back) # guaranteed to not be `None` - - # note, we don't copy frame.f_locals here (or during the last return call), because we don't expect the namespace to be - # modified down the line if this becomes a problem, we could implement some sort of frozen mapping structure to enforce this. - if force: + # if f_back is None, it's the global module namespace and we don't need to include it here + if frame.f_back is None: + return None + else: return frame.f_locals - # If either of the following conditions are true, the class is defined at the top module level. - # To better understand why we need both of these checks, see - # https://github.com/pydantic/pydantic/pull/10113#discussion_r1714981531. - if frame.f_back is None or frame.f_code.co_name == '': - return None - return frame.f_locals +def add_module_globals(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]: + module_name = getattr(obj, '__module__', None) + if module_name: + try: + module_globalns = sys.modules[module_name].__dict__ + except KeyError: + # happens occasionally, see https://github.com/pydantic/pydantic/issues/2363 + pass + else: + if globalns: + return {**module_globalns, **globalns} + else: + # copy module globals to make sure it can't be updated later + return module_globalns.copy() + + return globalns or {} -def _type_convert(arg: Any) -> Any: - """Convert `None` to `NoneType` and strings to `ForwardRef` instances. - - This is a backport of the private `typing._type_convert` function. When - evaluating a type, `ForwardRef._evaluate` ends up being called, and is - responsible for making this conversion. However, we still have to apply - it for the first argument passed to our type evaluation functions, similarly - to the `typing.get_type_hints` function. - """ - if arg is None: - return NoneType - if isinstance(arg, str): - # Like `typing.get_type_hints`, assume the arg can be in any context, - # hence the proper `is_argument` and `is_class` args: - return _make_forward_ref(arg, is_argument=False, is_class=True) - return arg +def get_cls_types_namespace(cls: type[Any], parent_namespace: dict[str, Any] | None = None) -> dict[str, Any]: + ns = add_module_globals(cls, parent_namespace) + ns[cls.__name__] = cls + return ns -def safe_get_annotations(cls: type[Any]) -> dict[str, Any]: - """Get the annotations for the provided class, accounting for potential deferred forward references. - - Starting with Python 3.14, accessing the `__annotations__` attribute might raise a `NameError` if - a referenced symbol isn't defined yet. In this case, we return the annotation in the *forward ref* - format. - """ - if sys.version_info >= (3, 14): - return annotationlib.get_annotations(cls, format=annotationlib.Format.FORWARDREF) - else: - return cls.__dict__.get('__annotations__', {}) - - -def get_model_type_hints( - obj: type[BaseModel], - *, - ns_resolver: NsResolver | None = None, -) -> dict[str, tuple[Any, bool]]: - """Collect annotations from a Pydantic model class, including those from parent classes. - - Args: - obj: The Pydantic model to inspect. - ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. - - Returns: - A dictionary mapping annotation names to a two-tuple: the first element is the evaluated - type or the original annotation if a `NameError` occurred, the second element is a boolean - indicating if whether the evaluation succeeded. - """ - hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} - ns_resolver = ns_resolver or NsResolver() - - for base in reversed(obj.__mro__): - # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals - # from the ns_resolver, but we want to be able to know which specific field failed - # to evaluate: - ann = safe_get_annotations(base) - - if not ann: - continue - - with ns_resolver.push(base): - globalns, localns = ns_resolver.types_namespace - for name, value in ann.items(): - if name.startswith('_'): - # For private attributes, we only need the annotation to detect the `ClassVar` special form. - # For this reason, we still try to evaluate it, but we also catch any possible exception (on - # top of the `NameError`s caught in `try_eval_type`) that could happen so that users are free - # to use any kind of forward annotation for private fields (e.g. circular imports, new typing - # syntax, etc). - try: - hints[name] = try_eval_type(value, globalns, localns) - except Exception: - hints[name] = (value, False) - else: - hints[name] = try_eval_type(value, globalns, localns) - return hints - - -def get_cls_type_hints( - obj: type[Any], - *, - ns_resolver: NsResolver | None = None, -) -> dict[str, Any]: +def get_cls_type_hints_lenient(obj: Any, globalns: dict[str, Any] | None = None) -> dict[str, Any]: """Collect annotations from a class, including those from parent classes. - Args: - obj: The class to inspect. - ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. + Unlike `typing.get_type_hints`, this function will not error if a forward reference is not resolvable. """ - hints: dict[str, Any] = {} - ns_resolver = ns_resolver or NsResolver() - + hints = {} for base in reversed(obj.__mro__): - # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals - # from the ns_resolver, but we want to be able to know which specific field failed - # to evaluate: - ann = safe_get_annotations(base) - - if not ann: - continue - - with ns_resolver.push(base): - globalns, localns = ns_resolver.types_namespace + ann = base.__dict__.get('__annotations__') + localns = dict(vars(base)) + if ann is not None and ann is not GetSetDescriptorType: for name, value in ann.items(): - hints[name] = eval_type(value, globalns, localns) + hints[name] = eval_type_lenient(value, globalns, localns) return hints -def try_eval_type( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, -) -> tuple[Any, bool]: - """Try evaluating the annotation using the provided namespaces. - - Args: - value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance - of `str`, it will be converted to a `ForwardRef`. - localns: The global namespace to use during annotation evaluation. - globalns: The local namespace to use during annotation evaluation. - - Returns: - A two-tuple containing the possibly evaluated type and a boolean indicating - whether the evaluation succeeded or not. - """ - value = _type_convert(value) +def eval_type_lenient(value: Any, globalns: dict[str, Any] | None, localns: dict[str, Any] | None) -> Any: + """Behaves like typing._eval_type, except it won't raise an error if a forward reference can't be resolved.""" + if value is None: + value = NoneType + elif isinstance(value, str): + value = _make_forward_ref(value, is_argument=False, is_class=True) try: - return eval_type_backport(value, globalns, localns), True + return typing._eval_type(value, globalns, localns) # type: ignore except NameError: - return value, False - - -def eval_type( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, -) -> Any: - """Evaluate the annotation using the provided namespaces. - - Args: - value: The value to evaluate. If `None`, it will be replaced by `type[None]`. If an instance - of `str`, it will be converted to a `ForwardRef`. - localns: The global namespace to use during annotation evaluation. - globalns: The local namespace to use during annotation evaluation. - """ - value = _type_convert(value) - return eval_type_backport(value, globalns, localns) - - -@deprecated( - '`eval_type_lenient` is deprecated, use `try_eval_type` instead.', - category=None, -) -def eval_type_lenient( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, -) -> Any: - ev, _ = try_eval_type(value, globalns, localns) - return ev - - -def eval_type_backport( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, - type_params: tuple[Any, ...] | None = None, -) -> Any: - """An enhanced version of `typing._eval_type` which will fall back to using the `eval_type_backport` - package if it's installed to let older Python versions use newer typing constructs. - - Specifically, this transforms `X | Y` into `typing.Union[X, Y]` and `list[X]` into `typing.List[X]` - (as well as all the types made generic in PEP 585) if the original syntax is not supported in the - current Python version. - - This function will also display a helpful error if the value passed fails to evaluate. - """ - try: - return _eval_type_backport(value, globalns, localns, type_params) - except TypeError as e: - if 'Unable to evaluate type annotation' in str(e): - raise - - # If it is a `TypeError` and value isn't a `ForwardRef`, it would have failed during annotation definition. - # Thus we assert here for type checking purposes: - assert isinstance(value, typing.ForwardRef) - - message = f'Unable to evaluate type annotation {value.__forward_arg__!r}.' - if sys.version_info >= (3, 11): - e.add_note(message) - raise - else: - raise TypeError(message) from e - except RecursionError as e: - # TODO ideally recursion errors should be checked in `eval_type` above, but `eval_type_backport` - # is used directly in some places. - message = ( - "If you made use of an implicit recursive type alias (e.g. `MyType = list['MyType']), " - 'consider using PEP 695 type aliases instead. For more details, refer to the documentation: ' - f'https://docs.pydantic.dev/{version_short()}/concepts/types/#named-recursive-types' - ) - if sys.version_info >= (3, 11): - e.add_note(message) - raise - else: - raise RecursionError(f'{e.args[0]}\n{message}') - - -def _eval_type_backport( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, - type_params: tuple[Any, ...] | None = None, -) -> Any: - try: - return _eval_type(value, globalns, localns, type_params) - except TypeError as e: - if not (isinstance(value, typing.ForwardRef) and is_backport_fixable_error(e)): - raise - - try: - from eval_type_backport import eval_type_backport - except ImportError: - raise TypeError( - f'Unable to evaluate type annotation {value.__forward_arg__!r}. If you are making use ' - 'of the new typing syntax (unions using `|` since Python 3.10 or builtins subscripting ' - 'since Python 3.9), you should either replace the use of new syntax with the existing ' - '`typing` constructs or install the `eval_type_backport` package.' - ) from e - - return eval_type_backport( - value, - globalns, - localns, # pyright: ignore[reportArgumentType], waiting on a new `eval_type_backport` release. - try_default=False, - ) - - -def _eval_type( - value: Any, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, - type_params: tuple[Any, ...] | None = None, -) -> Any: - if sys.version_info >= (3, 14): - # Starting in 3.14, `_eval_type()` does *not* apply `_type_convert()` - # anymore. This means the `None` -> `type(None)` conversion does not apply: - evaluated = typing._eval_type( # type: ignore - value, - globalns, - localns, - type_params=type_params, - # This is relevant when evaluating types from `TypedDict` classes, where string annotations - # are automatically converted to `ForwardRef` instances with a module set. In this case, - # Our `globalns` is irrelevant and we need to indicate `typing._eval_type()` that it should - # infer it from the `ForwardRef.__forward_module__` attribute instead (`typing.get_type_hints()` - # does the same). Note that this would probably be unnecessary if we properly iterated over the - # `__orig_bases__` for TypedDicts in `get_cls_type_hints()`: - prefer_fwd_module=True, - ) - if evaluated is None: - evaluated = type(None) - return evaluated - elif sys.version_info >= (3, 13): - return typing._eval_type( # type: ignore - value, globalns, localns, type_params=type_params - ) - else: - return typing._eval_type( # type: ignore - value, globalns, localns - ) - - -def is_backport_fixable_error(e: TypeError) -> bool: - msg = str(e) - - return sys.version_info < (3, 10) and msg.startswith('unsupported operand type(s) for |: ') + # the point of this function is to be tolerant to this case + return value def get_function_type_hints( - function: Callable[..., Any], - *, - include_keys: set[str] | None = None, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, + function: Callable[..., Any], *, include_keys: set[str] | None = None, types_namespace: dict[str, Any] | None = None ) -> dict[str, Any]: - """Return type hints for a function. - - This is similar to the `typing.get_type_hints` function, with a few differences: - - Support `functools.partial` by using the underlying `func` attribute. - - Do not wrap type annotation of a parameter with `Optional` if it has a default value of `None` - (related bug: https://github.com/python/cpython/issues/90353, only fixed in 3.11+). + """Like `typing.get_type_hints`, but doesn't convert `X` to `Optional[X]` if the default value is `None`, also + copes with `partial`. """ - try: - if isinstance(function, partial): - annotations = function.func.__annotations__ - else: - annotations = function.__annotations__ - except AttributeError: - # Some functions (e.g. builtins) don't have annotations: - return {} - - if globalns is None: - globalns = get_module_ns_of(function) - type_params: tuple[Any, ...] | None = None - if localns is None: - # If localns was specified, it is assumed to already contain type params. This is because - # Pydantic has more advanced logic to do so (see `_namespace_utils.ns_for_function`). - type_params = getattr(function, '__type_params__', ()) + if isinstance(function, partial): + annotations = function.func.__annotations__ + else: + annotations = function.__annotations__ + globalns = add_module_globals(function) type_hints = {} for name, value in annotations.items(): if include_keys is not None and name not in include_keys: @@ -593,12 +248,11 @@ def get_function_type_hints( elif isinstance(value, str): value = _make_forward_ref(value) - type_hints[name] = eval_type_backport(value, globalns, localns, type_params) + type_hints[name] = typing._eval_type(value, globalns, types_namespace) # type: ignore return type_hints -# TODO use typing.ForwardRef directly when we stop supporting 3.9: if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): def _make_forward_ref( @@ -618,10 +272,10 @@ if sys.version_info < (3, 9, 8) or (3, 10) <= sys.version_info < (3, 10, 1): Implemented as EAFP with memory. """ - return typing.ForwardRef(arg, is_argument) # pyright: ignore[reportCallIssue] + return typing.ForwardRef(arg, is_argument) else: - _make_forward_ref = typing.ForwardRef # pyright: ignore[reportAssignmentType] + _make_forward_ref = typing.ForwardRef if sys.version_info >= (3, 10): @@ -709,15 +363,11 @@ else: if isinstance(value, str): value = _make_forward_ref(value, is_argument=False, is_class=True) - value = eval_type_backport(value, base_globals, base_locals) + value = typing._eval_type(value, base_globals, base_locals) # type: ignore hints[name] = value - if not include_extras and hasattr(typing, '_strip_annotations'): - return { - k: typing._strip_annotations(t) # type: ignore - for k, t in hints.items() - } - else: - return hints + return ( + hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore + ) if globalns is None: if isinstance(obj, types.ModuleType): @@ -738,7 +388,7 @@ else: if isinstance(obj, typing._allowed_types): # type: ignore return {} else: - raise TypeError(f'{obj!r} is not a module, class, method, or function.') + raise TypeError(f'{obj!r} is not a module, class, method, ' 'or function.') defaults = typing._get_defaults(obj) # type: ignore hints = dict(hints) for name, value in hints.items(): @@ -753,8 +403,44 @@ else: is_argument=not isinstance(obj, types.ModuleType), is_class=False, ) - value = eval_type_backport(value, globalns, localns) + value = typing._eval_type(value, globalns, localns) # type: ignore if name in defaults and defaults[name] is None: value = typing.Optional[value] hints[name] = value return hints if include_extras else {k: typing._strip_annotations(t) for k, t in hints.items()} # type: ignore + + +if sys.version_info < (3, 9): + + def evaluate_fwd_ref( + ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None + ) -> Any: + return ref._evaluate(globalns=globalns, localns=localns) + +else: + + def evaluate_fwd_ref( + ref: ForwardRef, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None + ) -> Any: + return ref._evaluate(globalns=globalns, localns=localns, recursive_guard=frozenset()) + + +def is_dataclass(_cls: type[Any]) -> TypeGuard[type[StandardDataclass]]: + # The dataclasses.is_dataclass function doesn't seem to provide TypeGuard functionality, + # so I created this convenience function + return dataclasses.is_dataclass(_cls) + + +def origin_is_type_alias_type(origin: Any) -> TypeGuard[TypeAliasType]: + return isinstance(origin, TypeAliasType) + + +if sys.version_info >= (3, 10): + + def is_generic_alias(type_: type[Any]) -> bool: + return isinstance(type_, (types.GenericAlias, typing._GenericAlias)) # type: ignore[attr-defined] + +else: + + def is_generic_alias(type_: type[Any]) -> bool: + return isinstance(type_, typing._GenericAlias) # type: ignore diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py index 7eae1b77..fa92711d 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py @@ -2,36 +2,24 @@ This should be reduced as much as possible with functions only used in one place, moved to that place. """ - from __future__ import annotations as _annotations -import dataclasses import keyword -import sys -import warnings +import typing import weakref from collections import OrderedDict, defaultdict, deque -from collections.abc import Callable, Iterable, Mapping -from collections.abc import Set as AbstractSet from copy import deepcopy -from functools import cached_property -from inspect import Parameter from itertools import zip_longest from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, overload +from typing import Any, TypeVar -from pydantic_core import MISSING -from typing_extensions import TypeAlias, TypeGuard, deprecated - -from pydantic import PydanticDeprecatedSince211 +from typing_extensions import TypeAlias, TypeGuard from . import _repr, _typing_extra -from ._import_utils import import_cached_base_model -if TYPE_CHECKING: - # TODO remove type error comments when we drop support for Python 3.9 - MappingIntStrAny: TypeAlias = Mapping[int, Any] | Mapping[str, Any] # pyright: ignore[reportGeneralTypeIssues] - AbstractSetIntStr: TypeAlias = AbstractSet[int] | AbstractSet[str] # pyright: ignore[reportGeneralTypeIssues] +if typing.TYPE_CHECKING: + MappingIntStrAny: TypeAlias = 'typing.Mapping[int, Any] | typing.Mapping[str, Any]' + AbstractSetIntStr: TypeAlias = 'typing.AbstractSet[int] | typing.AbstractSet[str]' from ..main import BaseModel @@ -71,25 +59,6 @@ BUILTIN_COLLECTIONS: set[type[Any]] = { } -def can_be_positional(param: Parameter) -> bool: - """Return whether the parameter accepts a positional argument. - - ```python {test="skip" lint="skip"} - def func(a, /, b, *, c): - pass - - params = inspect.signature(func).parameters - can_be_positional(params['a']) - #> True - can_be_positional(params['b']) - #> True - can_be_positional(params['c']) - #> False - ``` - """ - return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) - - def sequence_like(v: Any) -> bool: return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) @@ -114,7 +83,7 @@ def is_model_class(cls: Any) -> TypeGuard[type[BaseModel]]: """Returns true if cls is a _proper_ subclass of BaseModel, and provides proper type-checking, unlike raw calls to lenient_issubclass. """ - BaseModel = import_cached_base_model() + from ..main import BaseModel return lenient_issubclass(cls, BaseModel) and cls is not BaseModel @@ -151,7 +120,7 @@ T = TypeVar('T') def unique_list( input_list: list[T] | tuple[T, ...], *, - name_factory: Callable[[T], str] = str, + name_factory: typing.Callable[[T], str] = str, ) -> list[T]: """Make a list unique while maintaining order. We update the list if another one with the same name is set @@ -216,7 +185,7 @@ class ValueItems(_repr.Representation): normalized_items: dict[int | str, Any] = {} all_items = None for i, v in items.items(): - if not (isinstance(v, Mapping) or isinstance(v, AbstractSet) or self.is_true(v)): + if not (isinstance(v, typing.Mapping) or isinstance(v, typing.AbstractSet) or self.is_true(v)): raise TypeError(f'Unexpected type of exclude value for index "{i}" {v.__class__}') if i == '__all__': all_items = self._coerce_value(v) @@ -281,9 +250,9 @@ class ValueItems(_repr.Representation): @staticmethod def _coerce_items(items: AbstractSetIntStr | MappingIntStrAny) -> MappingIntStrAny: - if isinstance(items, Mapping): + if isinstance(items, typing.Mapping): pass - elif isinstance(items, AbstractSet): + elif isinstance(items, typing.AbstractSet): items = dict.fromkeys(items, ...) # type: ignore else: class_name = getattr(items, '__class__', '???') @@ -304,25 +273,21 @@ class ValueItems(_repr.Representation): return [(None, self._items)] -if TYPE_CHECKING: +if typing.TYPE_CHECKING: - def LazyClassAttribute(name: str, get_value: Callable[[], T]) -> T: ... + def ClassAttribute(name: str, value: T) -> T: + ... else: - class LazyClassAttribute: - """A descriptor exposing an attribute only accessible on a class (hidden from instances). + class ClassAttribute: + """Hide class attribute from its instances.""" - The attribute is lazily computed and cached during the first access. - """ + __slots__ = 'name', 'value' - def __init__(self, name: str, get_value: Callable[[], Any]) -> None: + def __init__(self, name: str, value: Any) -> None: self.name = name - self.get_value = get_value - - @cached_property - def value(self) -> Any: - return self.get_value() + self.value = value def __get__(self, instance: Any, owner: type[Any]) -> None: if instance is None: @@ -338,8 +303,6 @@ def smart_deepcopy(obj: Obj) -> Obj: Use obj.copy() for built-in empty collections Use copy.deepcopy() for non-empty collections and unknown objects. """ - if obj is MISSING: - return obj # pyright: ignore[reportReturnType] obj_type = obj.__class__ if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES: return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway @@ -354,10 +317,10 @@ def smart_deepcopy(obj: Obj) -> Obj: return deepcopy(obj) # slowest way when we actually might need a deepcopy -_SENTINEL = object() +_EMPTY = object() -def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool: +def all_identical(left: typing.Iterable[Any], right: typing.Iterable[Any]) -> bool: """Check that the items of `left` are the same objects as those in `right`. >>> a, b = object(), object() @@ -366,81 +329,7 @@ def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool: >>> all_identical([a, b, [a]], [a, b, [a]]) # new list object, while "equal" is not "identical" False """ - for left_item, right_item in zip_longest(left, right, fillvalue=_SENTINEL): + for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY): if left_item is not right_item: return False return True - - -def get_first_not_none(a: Any, b: Any) -> Any: - """Return the first argument if it is not `None`, otherwise return the second argument.""" - return a if a is not None else b - - -@dataclasses.dataclass(frozen=True) -class SafeGetItemProxy: - """Wrapper redirecting `__getitem__` to `get` with a sentinel value as default - - This makes is safe to use in `operator.itemgetter` when some keys may be missing - """ - - # Define __slots__manually for performances - # @dataclasses.dataclass() only support slots=True in python>=3.10 - __slots__ = ('wrapped',) - - wrapped: Mapping[str, Any] - - def __getitem__(self, key: str, /) -> Any: - return self.wrapped.get(key, _SENTINEL) - - # required to pass the object to operator.itemgetter() instances due to a quirk of typeshed - # https://github.com/python/mypy/issues/13713 - # https://github.com/python/typeshed/pull/8785 - # Since this is typing-only, hide it in a typing.TYPE_CHECKING block - if TYPE_CHECKING: - - def __contains__(self, key: str, /) -> bool: - return self.wrapped.__contains__(key) - - -_ModelT = TypeVar('_ModelT', bound='BaseModel') -_RT = TypeVar('_RT') - - -class deprecated_instance_property(Generic[_ModelT, _RT]): - """A decorator exposing the decorated class method as a property, with a warning on instance access. - - This decorator takes a class method defined on the `BaseModel` class and transforms it into - an attribute. The attribute can be accessed on both the class and instances of the class. If accessed - via an instance, a deprecation warning is emitted stating that instance access will be removed in V3. - """ - - def __init__(self, fget: Callable[[type[_ModelT]], _RT], /) -> None: - # Note: fget should be a classmethod: - self.fget = fget - - @overload - def __get__(self, instance: None, objtype: type[_ModelT]) -> _RT: ... - @overload - @deprecated( - 'Accessing this attribute on the instance is deprecated, and will be removed in Pydantic V3. ' - 'Instead, you should access this attribute from the model class.', - category=None, - ) - def __get__(self, instance: _ModelT, objtype: type[_ModelT]) -> _RT: ... - def __get__(self, instance: _ModelT | None, objtype: type[_ModelT]) -> _RT: - if instance is not None: - # fmt: off - attr_name = ( - self.fget.__name__ - if sys.version_info >= (3, 10) - else self.fget.__func__.__name__ # pyright: ignore[reportFunctionMemberAccess] - ) - # fmt: on - warnings.warn( - f'Accessing the {attr_name!r} attribute on the instance is deprecated. ' - 'Instead, you should access this attribute from the model class.', - category=PydanticDeprecatedSince211, - stacklevel=2, - ) - return self.fget.__get__(instance, objtype)() diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py index ab82832f..543b064b 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py @@ -1,122 +1,101 @@ from __future__ import annotations as _annotations -import functools import inspect -from collections.abc import Awaitable +from dataclasses import dataclass from functools import partial -from typing import Any, Callable +from typing import Any, Awaitable, Callable import pydantic_core from ..config import ConfigDict from ..plugin._schema_validator import create_schema_validator +from . import _generate_schema, _typing_extra from ._config import ConfigWrapper -from ._generate_schema import GenerateSchema, ValidateCallSupportedTypes -from ._namespace_utils import MappingNamespace, NsResolver, ns_for_function -def extract_function_name(func: ValidateCallSupportedTypes) -> str: - """Extract the name of a `ValidateCallSupportedTypes` object.""" - return f'partial({func.func.__name__})' if isinstance(func, functools.partial) else func.__name__ - - -def extract_function_qualname(func: ValidateCallSupportedTypes) -> str: - """Extract the qualname of a `ValidateCallSupportedTypes` object.""" - return f'partial({func.func.__qualname__})' if isinstance(func, functools.partial) else func.__qualname__ - - -def update_wrapper_attributes(wrapped: ValidateCallSupportedTypes, wrapper: Callable[..., Any]): - """Update the `wrapper` function with the attributes of the `wrapped` function. Return the updated function.""" - if inspect.iscoroutinefunction(wrapped): - - @functools.wraps(wrapped) - async def wrapper_function(*args, **kwargs): # type: ignore - return await wrapper(*args, **kwargs) - else: - - @functools.wraps(wrapped) - def wrapper_function(*args, **kwargs): - return wrapper(*args, **kwargs) - - # We need to manually update this because `partial` object has no `__name__` and `__qualname__`. - wrapper_function.__name__ = extract_function_name(wrapped) - wrapper_function.__qualname__ = extract_function_qualname(wrapped) - wrapper_function.raw_function = wrapped # type: ignore - - return wrapper_function +@dataclass +class CallMarker: + function: Callable[..., Any] + validate_return: bool class ValidateCallWrapper: - """This is a wrapper around a function that validates the arguments passed to it, and optionally the return value.""" + """This is a wrapper around a function that validates the arguments passed to it, and optionally the return value. + + It's partially inspired by `wraps` which in turn uses `partial`, but extended to be a descriptor so + these functions can be applied to instance methods, class methods, static methods, as well as normal functions. + """ __slots__ = ( - 'function', - 'validate_return', - 'schema_type', - 'module', - 'qualname', - 'ns_resolver', - 'config_wrapper', - '__pydantic_complete__', + 'raw_function', + '_config', + '_validate_return', + '__pydantic_core_schema__', '__pydantic_validator__', - '__return_pydantic_validator__', + '__signature__', + '__name__', + '__qualname__', + '__annotations__', + '__dict__', # required for __module__ ) - def __init__( - self, - function: ValidateCallSupportedTypes, - config: ConfigDict | None, - validate_return: bool, - parent_namespace: MappingNamespace | None, - ) -> None: - self.function = function - self.validate_return = validate_return + def __init__(self, function: Callable[..., Any], config: ConfigDict | None, validate_return: bool): + self.raw_function = function + self._config = config + self._validate_return = validate_return + self.__signature__ = inspect.signature(function) if isinstance(function, partial): - self.schema_type = function.func - self.module = function.func.__module__ + func = function.func + schema_type = func + self.__name__ = f'partial({func.__name__})' + self.__qualname__ = f'partial({func.__qualname__})' + self.__annotations__ = func.__annotations__ + self.__module__ = func.__module__ + self.__doc__ = func.__doc__ else: - self.schema_type = function - self.module = function.__module__ - self.qualname = extract_function_qualname(function) + schema_type = function + self.__name__ = function.__name__ + self.__qualname__ = function.__qualname__ + self.__annotations__ = function.__annotations__ + self.__module__ = function.__module__ + self.__doc__ = function.__doc__ - self.ns_resolver = NsResolver( - namespaces_tuple=ns_for_function(self.schema_type, parent_namespace=parent_namespace) - ) - self.config_wrapper = ConfigWrapper(config) - if not self.config_wrapper.defer_build: - self._create_validators() - else: - self.__pydantic_complete__ = False - - def _create_validators(self) -> None: - gen_schema = GenerateSchema(self.config_wrapper, self.ns_resolver) - schema = gen_schema.clean_schema(gen_schema.generate_schema(self.function)) - core_config = self.config_wrapper.core_config(title=self.qualname) + namespace = _typing_extra.add_module_globals(function, None) + config_wrapper = ConfigWrapper(config) + gen_schema = _generate_schema.GenerateSchema(config_wrapper, namespace) + schema = gen_schema.clean_schema(gen_schema.generate_schema(function)) + self.__pydantic_core_schema__ = schema + core_config = config_wrapper.core_config(self) self.__pydantic_validator__ = create_schema_validator( schema, - self.schema_type, - self.module, - self.qualname, + schema_type, + self.__module__, + self.__qualname__, 'validate_call', core_config, - self.config_wrapper.plugin_settings, + config_wrapper.plugin_settings, ) - if self.validate_return: - signature = inspect.signature(self.function) - return_type = signature.return_annotation if signature.return_annotation is not signature.empty else Any - gen_schema = GenerateSchema(self.config_wrapper, self.ns_resolver) + + if self._validate_return: + return_type = ( + self.__signature__.return_annotation + if self.__signature__.return_annotation is not self.__signature__.empty + else Any + ) + gen_schema = _generate_schema.GenerateSchema(config_wrapper, namespace) schema = gen_schema.clean_schema(gen_schema.generate_schema(return_type)) + self.__return_pydantic_core_schema__ = schema validator = create_schema_validator( schema, - self.schema_type, - self.module, - self.qualname, + schema_type, + self.__module__, + self.__qualname__, 'validate_call', core_config, - self.config_wrapper.plugin_settings, + config_wrapper.plugin_settings, ) - if inspect.iscoroutinefunction(self.function): + if inspect.iscoroutinefunction(self.raw_function): async def return_val_wrapper(aw: Awaitable[Any]) -> None: return validator.validate_python(await aw) @@ -125,16 +104,46 @@ class ValidateCallWrapper: else: self.__return_pydantic_validator__ = validator.validate_python else: + self.__return_pydantic_core_schema__ = None self.__return_pydantic_validator__ = None - self.__pydantic_complete__ = True + self._name: str | None = None # set by __get__, used to set the instance attribute when decorating methods def __call__(self, *args: Any, **kwargs: Any) -> Any: - if not self.__pydantic_complete__: - self._create_validators() - res = self.__pydantic_validator__.validate_python(pydantic_core.ArgsKwargs(args, kwargs)) if self.__return_pydantic_validator__: return self.__return_pydantic_validator__(res) - else: - return res + return res + + def __get__(self, obj: Any, objtype: type[Any] | None = None) -> ValidateCallWrapper: + """Bind the raw function and return another ValidateCallWrapper wrapping that.""" + if obj is None: + try: + # Handle the case where a method is accessed as a class attribute + return objtype.__getattribute__(objtype, self._name) # type: ignore + except AttributeError: + # This will happen the first time the attribute is accessed + pass + + bound_function = self.raw_function.__get__(obj, objtype) + result = self.__class__(bound_function, self._config, self._validate_return) + + # skip binding to instance when obj or objtype has __slots__ attribute + if hasattr(obj, '__slots__') or hasattr(objtype, '__slots__'): + return result + + if self._name is not None: + if obj is not None: + object.__setattr__(obj, self._name, result) + else: + object.__setattr__(objtype, self._name, result) + return result + + def __set_name__(self, owner: Any, name: str) -> None: + self._name = name + + def __repr__(self) -> str: + return f'ValidateCallWrapper({self.raw_function})' + + def __eq__(self, other): + return self.raw_function == other.raw_function diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validators.py index 2c7fab66..7193fe5c 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_internal/_validators.py @@ -5,33 +5,22 @@ Import of this module is deferred since it contains imports of many standard lib from __future__ import annotations as _annotations -import collections.abc import math import re import typing -from collections.abc import Sequence -from decimal import Decimal -from fractions import Fraction from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network -from typing import Any, Callable, TypeVar, Union, cast -from zoneinfo import ZoneInfo, ZoneInfoNotFoundError +from typing import Any -import typing_extensions -from pydantic_core import PydanticCustomError, PydanticKnownError, core_schema -from typing_extensions import get_args, get_origin -from typing_inspection import typing_objects - -from pydantic._internal._import_utils import import_cached_field_info -from pydantic.errors import PydanticSchemaGenerationError +from pydantic_core import PydanticCustomError, core_schema +from pydantic_core._pydantic_core import PydanticKnownError def sequence_validator( - input_value: Sequence[Any], - /, + __input_value: typing.Sequence[Any], validator: core_schema.ValidatorFunctionWrapHandler, -) -> Sequence[Any]: +) -> typing.Sequence[Any]: """Validator for `Sequence` types, isinstance(v, Sequence) has already been called.""" - value_type = type(input_value) + value_type = type(__input_value) # We don't accept any plain string as a sequence # Relevant issue: https://github.com/pydantic/pydantic/issues/5595 @@ -42,24 +31,14 @@ def sequence_validator( {'type_name': value_type.__name__}, ) - # TODO: refactor sequence validation to validate with either a list or a tuple - # schema, depending on the type of the value. - # Additionally, we should be able to remove one of either this validator or the - # SequenceValidator in _std_types_schema.py (preferably this one, while porting over some logic). - # Effectively, a refactor for sequence validation is needed. - if value_type is tuple: - input_value = list(input_value) - - v_list = validator(input_value) + v_list = validator(__input_value) # the rest of the logic is just re-creating the original type from `v_list` - if value_type is list: + if value_type == list: return v_list elif issubclass(value_type, range): # return the list as we probably can't re-create the range return v_list - elif value_type is tuple: - return tuple(v_list) else: # best guess at how to re-create the original type, more custom construction logic might be required return value_type(v_list) # type: ignore[call-arg] @@ -127,407 +106,173 @@ def _import_string_logic(dotted_path: str) -> Any: return module -def pattern_either_validator(input_value: Any, /) -> re.Pattern[Any]: - if isinstance(input_value, re.Pattern): - return input_value - elif isinstance(input_value, (str, bytes)): +def pattern_either_validator(__input_value: Any) -> typing.Pattern[Any]: + if isinstance(__input_value, typing.Pattern): + return __input_value + elif isinstance(__input_value, (str, bytes)): # todo strict mode - return compile_pattern(input_value) # type: ignore + return compile_pattern(__input_value) # type: ignore else: raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') -def pattern_str_validator(input_value: Any, /) -> re.Pattern[str]: - if isinstance(input_value, re.Pattern): - if isinstance(input_value.pattern, str): - return input_value +def pattern_str_validator(__input_value: Any) -> typing.Pattern[str]: + if isinstance(__input_value, typing.Pattern): + if isinstance(__input_value.pattern, str): + return __input_value else: raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') - elif isinstance(input_value, str): - return compile_pattern(input_value) - elif isinstance(input_value, bytes): + elif isinstance(__input_value, str): + return compile_pattern(__input_value) + elif isinstance(__input_value, bytes): raise PydanticCustomError('pattern_str_type', 'Input should be a string pattern') else: raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') -def pattern_bytes_validator(input_value: Any, /) -> re.Pattern[bytes]: - if isinstance(input_value, re.Pattern): - if isinstance(input_value.pattern, bytes): - return input_value +def pattern_bytes_validator(__input_value: Any) -> typing.Pattern[bytes]: + if isinstance(__input_value, typing.Pattern): + if isinstance(__input_value.pattern, bytes): + return __input_value else: raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') - elif isinstance(input_value, bytes): - return compile_pattern(input_value) - elif isinstance(input_value, str): + elif isinstance(__input_value, bytes): + return compile_pattern(__input_value) + elif isinstance(__input_value, str): raise PydanticCustomError('pattern_bytes_type', 'Input should be a bytes pattern') else: raise PydanticCustomError('pattern_type', 'Input should be a valid pattern') -PatternType = TypeVar('PatternType', str, bytes) +PatternType = typing.TypeVar('PatternType', str, bytes) -def compile_pattern(pattern: PatternType) -> re.Pattern[PatternType]: +def compile_pattern(pattern: PatternType) -> typing.Pattern[PatternType]: try: return re.compile(pattern) except re.error: raise PydanticCustomError('pattern_regex', 'Input should be a valid regular expression') -def ip_v4_address_validator(input_value: Any, /) -> IPv4Address: - if isinstance(input_value, IPv4Address): - return input_value +def ip_v4_address_validator(__input_value: Any) -> IPv4Address: + if isinstance(__input_value, IPv4Address): + return __input_value try: - return IPv4Address(input_value) + return IPv4Address(__input_value) except ValueError: raise PydanticCustomError('ip_v4_address', 'Input is not a valid IPv4 address') -def ip_v6_address_validator(input_value: Any, /) -> IPv6Address: - if isinstance(input_value, IPv6Address): - return input_value +def ip_v6_address_validator(__input_value: Any) -> IPv6Address: + if isinstance(__input_value, IPv6Address): + return __input_value try: - return IPv6Address(input_value) + return IPv6Address(__input_value) except ValueError: raise PydanticCustomError('ip_v6_address', 'Input is not a valid IPv6 address') -def ip_v4_network_validator(input_value: Any, /) -> IPv4Network: +def ip_v4_network_validator(__input_value: Any) -> IPv4Network: """Assume IPv4Network initialised with a default `strict` argument. See more: https://docs.python.org/library/ipaddress.html#ipaddress.IPv4Network """ - if isinstance(input_value, IPv4Network): - return input_value + if isinstance(__input_value, IPv4Network): + return __input_value try: - return IPv4Network(input_value) + return IPv4Network(__input_value) except ValueError: raise PydanticCustomError('ip_v4_network', 'Input is not a valid IPv4 network') -def ip_v6_network_validator(input_value: Any, /) -> IPv6Network: +def ip_v6_network_validator(__input_value: Any) -> IPv6Network: """Assume IPv6Network initialised with a default `strict` argument. See more: https://docs.python.org/library/ipaddress.html#ipaddress.IPv6Network """ - if isinstance(input_value, IPv6Network): - return input_value + if isinstance(__input_value, IPv6Network): + return __input_value try: - return IPv6Network(input_value) + return IPv6Network(__input_value) except ValueError: raise PydanticCustomError('ip_v6_network', 'Input is not a valid IPv6 network') -def ip_v4_interface_validator(input_value: Any, /) -> IPv4Interface: - if isinstance(input_value, IPv4Interface): - return input_value +def ip_v4_interface_validator(__input_value: Any) -> IPv4Interface: + if isinstance(__input_value, IPv4Interface): + return __input_value try: - return IPv4Interface(input_value) + return IPv4Interface(__input_value) except ValueError: raise PydanticCustomError('ip_v4_interface', 'Input is not a valid IPv4 interface') -def ip_v6_interface_validator(input_value: Any, /) -> IPv6Interface: - if isinstance(input_value, IPv6Interface): - return input_value +def ip_v6_interface_validator(__input_value: Any) -> IPv6Interface: + if isinstance(__input_value, IPv6Interface): + return __input_value try: - return IPv6Interface(input_value) + return IPv6Interface(__input_value) except ValueError: raise PydanticCustomError('ip_v6_interface', 'Input is not a valid IPv6 interface') -def fraction_validator(input_value: Any, /) -> Fraction: - if isinstance(input_value, Fraction): - return input_value +def greater_than_validator(x: Any, gt: Any) -> Any: + if not (x > gt): + raise PydanticKnownError('greater_than', {'gt': gt}) + return x - try: - return Fraction(input_value) - except ValueError: - raise PydanticCustomError('fraction_parsing', 'Input is not a valid fraction') + +def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: + if not (x >= ge): + raise PydanticKnownError('greater_than_equal', {'ge': ge}) + return x + + +def less_than_validator(x: Any, lt: Any) -> Any: + if not (x < lt): + raise PydanticKnownError('less_than', {'lt': lt}) + return x + + +def less_than_or_equal_validator(x: Any, le: Any) -> Any: + if not (x <= le): + raise PydanticKnownError('less_than_equal', {'le': le}) + return x + + +def multiple_of_validator(x: Any, multiple_of: Any) -> Any: + if not (x % multiple_of == 0): + raise PydanticKnownError('multiple_of', {'multiple_of': multiple_of}) + return x + + +def min_length_validator(x: Any, min_length: Any) -> Any: + if not (len(x) >= min_length): + raise PydanticKnownError( + 'too_short', + {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)}, + ) + return x + + +def max_length_validator(x: Any, max_length: Any) -> Any: + if len(x) > max_length: + raise PydanticKnownError( + 'too_long', + {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)}, + ) + return x def forbid_inf_nan_check(x: Any) -> Any: if not math.isfinite(x): raise PydanticKnownError('finite_number') return x - - -def _safe_repr(v: Any) -> int | float | str: - """The context argument for `PydanticKnownError` requires a number or str type, so we do a simple repr() coercion for types like timedelta. - - See tests/test_types.py::test_annotated_metadata_any_order for some context. - """ - if isinstance(v, (int, float, str)): - return v - return repr(v) - - -def greater_than_validator(x: Any, gt: Any) -> Any: - try: - if not (x > gt): - raise PydanticKnownError('greater_than', {'gt': _safe_repr(gt)}) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'gt' to supplied value {x}") - - -def greater_than_or_equal_validator(x: Any, ge: Any) -> Any: - try: - if not (x >= ge): - raise PydanticKnownError('greater_than_equal', {'ge': _safe_repr(ge)}) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'ge' to supplied value {x}") - - -def less_than_validator(x: Any, lt: Any) -> Any: - try: - if not (x < lt): - raise PydanticKnownError('less_than', {'lt': _safe_repr(lt)}) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'lt' to supplied value {x}") - - -def less_than_or_equal_validator(x: Any, le: Any) -> Any: - try: - if not (x <= le): - raise PydanticKnownError('less_than_equal', {'le': _safe_repr(le)}) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'le' to supplied value {x}") - - -def multiple_of_validator(x: Any, multiple_of: Any) -> Any: - try: - if x % multiple_of: - raise PydanticKnownError('multiple_of', {'multiple_of': _safe_repr(multiple_of)}) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'multiple_of' to supplied value {x}") - - -def min_length_validator(x: Any, min_length: Any) -> Any: - try: - if not (len(x) >= min_length): - raise PydanticKnownError( - 'too_short', {'field_type': 'Value', 'min_length': min_length, 'actual_length': len(x)} - ) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'min_length' to supplied value {x}") - - -def max_length_validator(x: Any, max_length: Any) -> Any: - try: - if len(x) > max_length: - raise PydanticKnownError( - 'too_long', - {'field_type': 'Value', 'max_length': max_length, 'actual_length': len(x)}, - ) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'max_length' to supplied value {x}") - - -def _extract_decimal_digits_info(decimal: Decimal) -> tuple[int, int]: - """Compute the total number of digits and decimal places for a given [`Decimal`][decimal.Decimal] instance. - - This function handles both normalized and non-normalized Decimal instances. - Example: Decimal('1.230') -> 4 digits, 3 decimal places - - Args: - decimal (Decimal): The decimal number to analyze. - - Returns: - tuple[int, int]: A tuple containing the number of decimal places and total digits. - - Though this could be divided into two separate functions, the logic is easier to follow if we couple the computation - of the number of decimals and digits together. - """ - try: - decimal_tuple = decimal.as_tuple() - - assert isinstance(decimal_tuple.exponent, int) - - exponent = decimal_tuple.exponent - num_digits = len(decimal_tuple.digits) - - if exponent >= 0: - # A positive exponent adds that many trailing zeros - # Ex: digit_tuple=(1, 2, 3), exponent=2 -> 12300 -> 0 decimal places, 5 digits - num_digits += exponent - decimal_places = 0 - else: - # If the absolute value of the negative exponent is larger than the - # number of digits, then it's the same as the number of digits, - # because it'll consume all the digits in digit_tuple and then - # add abs(exponent) - len(digit_tuple) leading zeros after the decimal point. - # Ex: digit_tuple=(1, 2, 3), exponent=-2 -> 1.23 -> 2 decimal places, 3 digits - # Ex: digit_tuple=(1, 2, 3), exponent=-4 -> 0.0123 -> 4 decimal places, 4 digits - decimal_places = abs(exponent) - num_digits = max(num_digits, decimal_places) - - return decimal_places, num_digits - except (AssertionError, AttributeError): - raise TypeError(f'Unable to extract decimal digits info from supplied value {decimal}') - - -def max_digits_validator(x: Any, max_digits: Any) -> Any: - try: - _, num_digits = _extract_decimal_digits_info(x) - _, normalized_num_digits = _extract_decimal_digits_info(x.normalize()) - if (num_digits > max_digits) and (normalized_num_digits > max_digits): - raise PydanticKnownError( - 'decimal_max_digits', - {'max_digits': max_digits}, - ) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'max_digits' to supplied value {x}") - - -def decimal_places_validator(x: Any, decimal_places: Any) -> Any: - try: - decimal_places_, _ = _extract_decimal_digits_info(x) - if decimal_places_ > decimal_places: - normalized_decimal_places, _ = _extract_decimal_digits_info(x.normalize()) - if normalized_decimal_places > decimal_places: - raise PydanticKnownError( - 'decimal_max_places', - {'decimal_places': decimal_places}, - ) - return x - except TypeError: - raise TypeError(f"Unable to apply constraint 'decimal_places' to supplied value {x}") - - -def deque_validator(input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> collections.deque[Any]: - return collections.deque(handler(input_value), maxlen=getattr(input_value, 'maxlen', None)) - - -def defaultdict_validator( - input_value: Any, handler: core_schema.ValidatorFunctionWrapHandler, default_default_factory: Callable[[], Any] -) -> collections.defaultdict[Any, Any]: - if isinstance(input_value, collections.defaultdict): - default_factory = input_value.default_factory - return collections.defaultdict(default_factory, handler(input_value)) - else: - return collections.defaultdict(default_default_factory, handler(input_value)) - - -def get_defaultdict_default_default_factory(values_source_type: Any) -> Callable[[], Any]: - FieldInfo = import_cached_field_info() - - values_type_origin = get_origin(values_source_type) - - def infer_default() -> Callable[[], Any]: - allowed_default_types: dict[Any, Any] = { - tuple: tuple, - collections.abc.Sequence: tuple, - collections.abc.MutableSequence: list, - list: list, - typing.Sequence: list, - set: set, - typing.MutableSet: set, - collections.abc.MutableSet: set, - collections.abc.Set: frozenset, - typing.MutableMapping: dict, - typing.Mapping: dict, - collections.abc.Mapping: dict, - collections.abc.MutableMapping: dict, - float: float, - int: int, - str: str, - bool: bool, - } - values_type = values_type_origin or values_source_type - instructions = 'set using `DefaultDict[..., Annotated[..., Field(default_factory=...)]]`' - if typing_objects.is_typevar(values_type): - - def type_var_default_factory() -> None: - raise RuntimeError( - 'Generic defaultdict cannot be used without a concrete value type or an' - ' explicit default factory, ' + instructions - ) - - return type_var_default_factory - elif values_type not in allowed_default_types: - # a somewhat subjective set of types that have reasonable default values - allowed_msg = ', '.join([t.__name__ for t in set(allowed_default_types.values())]) - raise PydanticSchemaGenerationError( - f'Unable to infer a default factory for keys of type {values_source_type}.' - f' Only {allowed_msg} are supported, other types require an explicit default factory' - ' ' + instructions - ) - return allowed_default_types[values_type] - - # Assume Annotated[..., Field(...)] - if typing_objects.is_annotated(values_type_origin): - field_info = next((v for v in get_args(values_source_type) if isinstance(v, FieldInfo)), None) - else: - field_info = None - if field_info and field_info.default_factory: - # Assume the default factory does not take any argument: - default_default_factory = cast(Callable[[], Any], field_info.default_factory) - else: - default_default_factory = infer_default() - return default_default_factory - - -def validate_str_is_valid_iana_tz(value: Any, /) -> ZoneInfo: - if isinstance(value, ZoneInfo): - return value - try: - return ZoneInfo(value) - except (ZoneInfoNotFoundError, ValueError, TypeError): - raise PydanticCustomError('zoneinfo_str', 'invalid timezone: {value}', {'value': value}) - - -NUMERIC_VALIDATOR_LOOKUP: dict[str, Callable] = { - 'gt': greater_than_validator, - 'ge': greater_than_or_equal_validator, - 'lt': less_than_validator, - 'le': less_than_or_equal_validator, - 'multiple_of': multiple_of_validator, - 'min_length': min_length_validator, - 'max_length': max_length_validator, - 'max_digits': max_digits_validator, - 'decimal_places': decimal_places_validator, -} - -IpType = Union[IPv4Address, IPv6Address, IPv4Network, IPv6Network, IPv4Interface, IPv6Interface] - -IP_VALIDATOR_LOOKUP: dict[type[IpType], Callable] = { - IPv4Address: ip_v4_address_validator, - IPv6Address: ip_v6_address_validator, - IPv4Network: ip_v4_network_validator, - IPv6Network: ip_v6_network_validator, - IPv4Interface: ip_v4_interface_validator, - IPv6Interface: ip_v6_interface_validator, -} - -MAPPING_ORIGIN_MAP: dict[Any, Any] = { - typing.DefaultDict: collections.defaultdict, # noqa: UP006 - collections.defaultdict: collections.defaultdict, - typing.OrderedDict: collections.OrderedDict, # noqa: UP006 - collections.OrderedDict: collections.OrderedDict, - typing_extensions.OrderedDict: collections.OrderedDict, - typing.Counter: collections.Counter, - collections.Counter: collections.Counter, - # this doesn't handle subclasses of these - typing.Mapping: dict, - typing.MutableMapping: dict, - # parametrized typing.{Mutable}Mapping creates one of these - collections.abc.Mapping: dict, - collections.abc.MutableMapping: dict, -} diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/_migration.py b/Backend/venv/lib/python3.12/site-packages/pydantic/_migration.py index b4ecd283..c8478a62 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/_migration.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/_migration.py @@ -1,7 +1,5 @@ import sys -from typing import Any, Callable - -from pydantic.warnings import PydanticDeprecatedSince20 +from typing import Any, Callable, Dict from .version import version_short @@ -282,11 +280,7 @@ def getattr_migration(module: str) -> Callable[[str], Any]: import_path = f'{module}:{name}' if import_path in MOVED_IN_V2.keys(): new_location = MOVED_IN_V2[import_path] - warnings.warn( - f'`{import_path}` has been moved to `{new_location}`.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + warnings.warn(f'`{import_path}` has been moved to `{new_location}`.') return import_string(MOVED_IN_V2[import_path]) if import_path in DEPRECATED_MOVED_IN_V2: # skip the warning here because a deprecation warning will be raised elsewhere @@ -295,9 +289,7 @@ def getattr_migration(module: str) -> Callable[[str], Any]: new_location = REDIRECT_TO_V1[import_path] warnings.warn( f'`{import_path}` has been removed. We are importing from `{new_location}` instead.' - 'See the migration guide for more details: https://docs.pydantic.dev/latest/migration/', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'See the migration guide for more details: https://docs.pydantic.dev/latest/migration/' ) return import_string(REDIRECT_TO_V1[import_path]) if import_path == 'pydantic:BaseSettings': @@ -308,7 +300,7 @@ def getattr_migration(module: str) -> Callable[[str], Any]: ) if import_path in REMOVED_IN_V2: raise PydanticImportError(f'`{import_path}` has been removed in V2.') - globals: dict[str, Any] = sys.modules[module].__dict__ + globals: Dict[str, Any] = sys.modules[module].__dict__ if name in globals: return globals[name] raise AttributeError(f'module {module!r} has no attribute {name!r}') diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/alias_generators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/alias_generators.py index 0b7653f5..bbdaaaf1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/alias_generators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/alias_generators.py @@ -1,13 +1,8 @@ """Alias generators for converting between different capitalization conventions.""" - import re __all__ = ('to_pascal', 'to_camel', 'to_snake') -# TODO: in V3, change the argument names to be more descriptive -# Generally, don't only convert from snake_case, or name the functions -# more specifically like snake_to_camel. - def to_pascal(snake: str) -> str: """Convert a snake_case string to PascalCase. @@ -31,17 +26,12 @@ def to_camel(snake: str) -> str: Returns: The converted camelCase string. """ - # If the string is already in camelCase and does not contain a digit followed - # by a lowercase letter, return it as it is - if re.match('^[a-z]+[A-Za-z0-9]*$', snake) and not re.search(r'\d[a-z]', snake): - return snake - camel = to_pascal(snake) return re.sub('(^_*[A-Z])', lambda m: m.group(1).lower(), camel) def to_snake(camel: str) -> str: - """Convert a PascalCase, camelCase, or kebab-case string to snake_case. + """Convert a PascalCase or camelCase string to snake_case. Args: camel: The string to convert. @@ -49,14 +39,6 @@ def to_snake(camel: str) -> str: Returns: The converted string in snake_case. """ - # Handle the sequence of uppercase letters followed by a lowercase letter - snake = re.sub(r'([A-Z]+)([A-Z][a-z])', lambda m: f'{m.group(1)}_{m.group(2)}', camel) - # Insert an underscore between a lowercase letter and an uppercase letter - snake = re.sub(r'([a-z])([A-Z])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) - # Insert an underscore between a digit and an uppercase letter - snake = re.sub(r'([0-9])([A-Z])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) - # Insert an underscore between a lowercase letter and a digit - snake = re.sub(r'([a-z])([0-9])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) - # Replace hyphens with underscores to handle kebab-case - snake = snake.replace('-', '_') + snake = re.sub(r'([a-zA-Z])([0-9])', lambda m: f'{m.group(1)}_{m.group(2)}', camel) + snake = re.sub(r'([a-z0-9])([A-Z])', lambda m: f'{m.group(1)}_{m.group(2)}', snake) return snake.lower() diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/aliases.py b/Backend/venv/lib/python3.12/site-packages/pydantic/aliases.py deleted file mode 100644 index ac227370..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/aliases.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Support for alias configurations.""" - -from __future__ import annotations - -import dataclasses -from typing import Any, Callable, Literal - -from pydantic_core import PydanticUndefined - -from ._internal import _internal_dataclass - -__all__ = ('AliasGenerator', 'AliasPath', 'AliasChoices') - - -@dataclasses.dataclass(**_internal_dataclass.slots_true) -class AliasPath: - """!!! abstract "Usage Documentation" - [`AliasPath` and `AliasChoices`](../concepts/alias.md#aliaspath-and-aliaschoices) - - A data class used by `validation_alias` as a convenience to create aliases. - - Attributes: - path: A list of string or integer aliases. - """ - - path: list[int | str] - - def __init__(self, first_arg: str, *args: str | int) -> None: - self.path = [first_arg] + list(args) - - def convert_to_aliases(self) -> list[str | int]: - """Converts arguments to a list of string or integer aliases. - - Returns: - The list of aliases. - """ - return self.path - - def search_dict_for_path(self, d: dict) -> Any: - """Searches a dictionary for the path specified by the alias. - - Returns: - The value at the specified path, or `PydanticUndefined` if the path is not found. - """ - v = d - for k in self.path: - if isinstance(v, str): - # disallow indexing into a str, like for AliasPath('x', 0) and x='abc' - return PydanticUndefined - try: - v = v[k] - except (KeyError, IndexError, TypeError): - return PydanticUndefined - return v - - -@dataclasses.dataclass(**_internal_dataclass.slots_true) -class AliasChoices: - """!!! abstract "Usage Documentation" - [`AliasPath` and `AliasChoices`](../concepts/alias.md#aliaspath-and-aliaschoices) - - A data class used by `validation_alias` as a convenience to create aliases. - - Attributes: - choices: A list containing a string or `AliasPath`. - """ - - choices: list[str | AliasPath] - - def __init__(self, first_choice: str | AliasPath, *choices: str | AliasPath) -> None: - self.choices = [first_choice] + list(choices) - - def convert_to_aliases(self) -> list[list[str | int]]: - """Converts arguments to a list of lists containing string or integer aliases. - - Returns: - The list of aliases. - """ - aliases: list[list[str | int]] = [] - for c in self.choices: - if isinstance(c, AliasPath): - aliases.append(c.convert_to_aliases()) - else: - aliases.append([c]) - return aliases - - -@dataclasses.dataclass(**_internal_dataclass.slots_true) -class AliasGenerator: - """!!! abstract "Usage Documentation" - [Using an `AliasGenerator`](../concepts/alias.md#using-an-aliasgenerator) - - A data class used by `alias_generator` as a convenience to create various aliases. - - Attributes: - alias: A callable that takes a field name and returns an alias for it. - validation_alias: A callable that takes a field name and returns a validation alias for it. - serialization_alias: A callable that takes a field name and returns a serialization alias for it. - """ - - alias: Callable[[str], str] | None = None - validation_alias: Callable[[str], str | AliasPath | AliasChoices] | None = None - serialization_alias: Callable[[str], str] | None = None - - def _generate_alias( - self, - alias_kind: Literal['alias', 'validation_alias', 'serialization_alias'], - allowed_types: tuple[type[str] | type[AliasPath] | type[AliasChoices], ...], - field_name: str, - ) -> str | AliasPath | AliasChoices | None: - """Generate an alias of the specified kind. Returns None if the alias generator is None. - - Raises: - TypeError: If the alias generator produces an invalid type. - """ - alias = None - if alias_generator := getattr(self, alias_kind): - alias = alias_generator(field_name) - if alias and not isinstance(alias, allowed_types): - raise TypeError( - f'Invalid `{alias_kind}` type. `{alias_kind}` generator must produce one of `{allowed_types}`' - ) - return alias - - def generate_aliases(self, field_name: str) -> tuple[str | None, str | AliasPath | AliasChoices | None, str | None]: - """Generate `alias`, `validation_alias`, and `serialization_alias` for a field. - - Returns: - A tuple of three aliases - validation, alias, and serialization. - """ - alias = self._generate_alias('alias', (str,), field_name) - validation_alias = self._generate_alias('validation_alias', (str, AliasChoices, AliasPath), field_name) - serialization_alias = self._generate_alias('serialization_alias', (str,), field_name) - - return alias, validation_alias, serialization_alias # type: ignore diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/annotated_handlers.py b/Backend/venv/lib/python3.12/site-packages/pydantic/annotated_handlers.py index d0cb5d3d..59adabfd 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/annotated_handlers.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/annotated_handlers.py @@ -1,5 +1,4 @@ """Type annotations to use with `__get_pydantic_core_schema__` and `__get_pydantic_json_schema__`.""" - from __future__ import annotations as _annotations from typing import TYPE_CHECKING, Any, Union @@ -7,7 +6,6 @@ from typing import TYPE_CHECKING, Any, Union from pydantic_core import core_schema if TYPE_CHECKING: - from ._internal._namespace_utils import NamespacesTuple from .json_schema import JsonSchemaMode, JsonSchemaValue CoreSchemaOrField = Union[ @@ -30,7 +28,7 @@ class GetJsonSchemaHandler: mode: JsonSchemaMode - def __call__(self, core_schema: CoreSchemaOrField, /) -> JsonSchemaValue: + def __call__(self, __core_schema: CoreSchemaOrField) -> JsonSchemaValue: """Call the inner handler and get the JsonSchemaValue it returns. This will call the next JSON schema modifying function up until it calls into `pydantic.json_schema.GenerateJsonSchema`, which will raise a @@ -38,7 +36,7 @@ class GetJsonSchemaHandler: a JSON schema. Args: - core_schema: A `pydantic_core.core_schema.CoreSchema`. + __core_schema: A `pydantic_core.core_schema.CoreSchema`. Returns: JsonSchemaValue: The JSON schema generated by the inner JSON schema modify @@ -46,13 +44,13 @@ class GetJsonSchemaHandler: """ raise NotImplementedError - def resolve_ref_schema(self, maybe_ref_json_schema: JsonSchemaValue, /) -> JsonSchemaValue: + def resolve_ref_schema(self, __maybe_ref_json_schema: JsonSchemaValue) -> JsonSchemaValue: """Get the real schema for a `{"$ref": ...}` schema. If the schema given is not a `$ref` schema, it will be returned as is. This means you don't have to check before calling this function. Args: - maybe_ref_json_schema: A JsonSchemaValue which may be a `$ref` schema. + __maybe_ref_json_schema: A JsonSchemaValue, ref based or not. Raises: LookupError: If the ref is not found. @@ -66,7 +64,7 @@ class GetJsonSchemaHandler: class GetCoreSchemaHandler: """Handler to call into the next CoreSchema schema generation function.""" - def __call__(self, source_type: Any, /) -> core_schema.CoreSchema: + def __call__(self, __source_type: Any) -> core_schema.CoreSchema: """Call the inner handler and get the CoreSchema it returns. This will call the next CoreSchema modifying function up until it calls into Pydantic's internal schema generation machinery, which will raise a @@ -74,14 +72,14 @@ class GetCoreSchemaHandler: a CoreSchema for the given source type. Args: - source_type: The input type. + __source_type: The input type. Returns: CoreSchema: The `pydantic-core` CoreSchema generated. """ raise NotImplementedError - def generate_schema(self, source_type: Any, /) -> core_schema.CoreSchema: + def generate_schema(self, __source_type: Any) -> core_schema.CoreSchema: """Generate a schema unrelated to the current context. Use this function if e.g. you are handling schema generation for a sequence and want to generate a schema for its items. @@ -89,20 +87,20 @@ class GetCoreSchemaHandler: that was intended for the sequence itself to its items! Args: - source_type: The input type. + __source_type: The input type. Returns: CoreSchema: The `pydantic-core` CoreSchema generated. """ raise NotImplementedError - def resolve_ref_schema(self, maybe_ref_schema: core_schema.CoreSchema, /) -> core_schema.CoreSchema: + def resolve_ref_schema(self, __maybe_ref_schema: core_schema.CoreSchema) -> core_schema.CoreSchema: """Get the real schema for a `definition-ref` schema. If the schema given is not a `definition-ref` schema, it will be returned as is. This means you don't have to check before calling this function. Args: - maybe_ref_schema: A `CoreSchema`, `ref`-based or not. + __maybe_ref_schema: A `CoreSchema`, `ref`-based or not. Raises: LookupError: If the `ref` is not found. @@ -117,6 +115,6 @@ class GetCoreSchemaHandler: """Get the name of the closest field to this validator.""" raise NotImplementedError - def _get_types_namespace(self) -> NamespacesTuple: + def _get_types_namespace(self) -> dict[str, Any] | None: """Internal method used during type resolution for serializer annotations.""" raise NotImplementedError diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/class_validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/class_validators.py index 9977150c..2ff72ae5 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/class_validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/class_validators.py @@ -1,5 +1,4 @@ """`class_validators` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/color.py b/Backend/venv/lib/python3.12/site-packages/pydantic/color.py index 9a42d586..108bb8fa 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/color.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/color.py @@ -11,11 +11,10 @@ Warning: Deprecated See [`pydantic-extra-types.Color`](../usage/types/extra_types/color_types.md) for more information. """ - import math import re from colorsys import hls_to_rgb, rgb_to_hls -from typing import Any, Callable, Optional, Union, cast +from typing import Any, Callable, Optional, Tuple, Type, Union, cast from pydantic_core import CoreSchema, PydanticCustomError, core_schema from typing_extensions import deprecated @@ -25,9 +24,9 @@ from ._internal._schema_generation_shared import GetJsonSchemaHandler as _GetJso from .json_schema import JsonSchemaValue from .warnings import PydanticDeprecatedSince20 -ColorTuple = Union[tuple[int, int, int], tuple[int, int, int, float]] +ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]] ColorType = Union[ColorTuple, str] -HslColorTuple = Union[tuple[float, float, float], tuple[float, float, float, float]] +HslColorTuple = Union[Tuple[float, float, float], Tuple[float, float, float, float]] class RGBA: @@ -41,7 +40,7 @@ class RGBA: self.b = b self.alpha = alpha - self._tuple: tuple[float, float, float, Optional[float]] = (r, g, b, alpha) + self._tuple: Tuple[float, float, float, Optional[float]] = (r, g, b, alpha) def __getitem__(self, item: Any) -> Any: return self._tuple[item] @@ -124,7 +123,7 @@ class Color(_repr.Representation): ValueError: When no named color is found and fallback is `False`. """ if self._rgba.alpha is None: - rgb = cast(tuple[int, int, int], self.as_rgb_tuple()) + rgb = cast(Tuple[int, int, int], self.as_rgb_tuple()) try: return COLORS_BY_VALUE[rgb] except KeyError as e: @@ -232,7 +231,7 @@ class Color(_repr.Representation): @classmethod def __get_pydantic_core_schema__( - cls, source: type[Any], handler: Callable[[Any], CoreSchema] + cls, source: Type[Any], handler: Callable[[Any], CoreSchema] ) -> core_schema.CoreSchema: return core_schema.with_info_plain_validator_function( cls._validate, serialization=core_schema.to_string_ser_schema() @@ -255,7 +254,7 @@ class Color(_repr.Representation): return hash(self.as_rgb_tuple()) -def parse_tuple(value: tuple[Any, ...]) -> RGBA: +def parse_tuple(value: Tuple[Any, ...]) -> RGBA: """Parse a tuple or list to get RGBA values. Args: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/config.py b/Backend/venv/lib/python3.12/site-packages/pydantic/config.py index bbf57aa4..976fa06c 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/config.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/config.py @@ -1,33 +1,26 @@ """Configuration for Pydantic models.""" - from __future__ import annotations as _annotations -import warnings -from re import Pattern -from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, Union, cast, overload +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Type, Union -from typing_extensions import TypeAlias, TypedDict, Unpack, deprecated +from typing_extensions import Literal, TypeAlias, TypedDict from ._migration import getattr_migration -from .aliases import AliasGenerator -from .errors import PydanticUserError -from .warnings import PydanticDeprecatedSince211 if TYPE_CHECKING: from ._internal._generate_schema import GenerateSchema as _GenerateSchema - from .fields import ComputedFieldInfo, FieldInfo -__all__ = ('ConfigDict', 'with_config') +__all__ = ('ConfigDict',) -JsonValue: TypeAlias = Union[int, float, str, bool, None, list['JsonValue'], 'JsonDict'] -JsonDict: TypeAlias = dict[str, JsonValue] +JsonValue: TypeAlias = Union[int, float, str, bool, None, List['JsonValue'], 'JsonDict'] +JsonDict: TypeAlias = Dict[str, JsonValue] JsonEncoder = Callable[[Any], Any] JsonSchemaExtraCallable: TypeAlias = Union[ Callable[[JsonDict], None], - Callable[[JsonDict, type[Any]], None], + Callable[[JsonDict, Type[Any]], None], ] ExtraValues = Literal['allow', 'ignore', 'forbid'] @@ -39,18 +32,11 @@ class ConfigDict(TypedDict, total=False): title: str | None """The title for the generated JSON schema, defaults to the model's name""" - model_title_generator: Callable[[type], str] | None - """A callable that takes a model class and returns the title for it. Defaults to `None`.""" - - field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None - """A callable that takes a field's name and info and returns title for it. Defaults to `None`.""" - str_to_lower: bool """Whether to convert all characters to lowercase for str types. Defaults to `False`.""" str_to_upper: bool """Whether to convert all characters to uppercase for str types. Defaults to `False`.""" - str_strip_whitespace: bool """Whether to strip leading and trailing whitespace for str types.""" @@ -61,129 +47,84 @@ class ConfigDict(TypedDict, total=False): """The maximum length for str types. Defaults to `None`.""" extra: ExtraValues | None - ''' - Whether to ignore, allow, or forbid extra data during model initialization. Defaults to `'ignore'`. + """ + Whether to ignore, allow, or forbid extra attributes during model initialization. Defaults to `'ignore'`. - Three configuration values are available: + You can configure how pydantic handles the attributes that are not defined in the model: - - `'ignore'`: Providing extra data is ignored (the default): - ```python - from pydantic import BaseModel, ConfigDict + * `allow` - Allow any extra attributes. + * `forbid` - Forbid any extra attributes. + * `ignore` - Ignore any extra attributes. - class User(BaseModel): - model_config = ConfigDict(extra='ignore') # (1)! - - name: str - - user = User(name='John Doe', age=20) # (2)! - print(user) - #> name='John Doe' - ``` - - 1. This is the default behaviour. - 2. The `age` argument is ignored. - - - `'forbid'`: Providing extra data is not permitted, and a [`ValidationError`][pydantic_core.ValidationError] - will be raised if this is the case: - ```python - from pydantic import BaseModel, ConfigDict, ValidationError + ```py + from pydantic import BaseModel, ConfigDict - class Model(BaseModel): - x: int + class User(BaseModel): + model_config = ConfigDict(extra='ignore') # (1)! - model_config = ConfigDict(extra='forbid') + name: str - try: - Model(x=1, y='a') - except ValidationError as exc: - print(exc) - """ - 1 validation error for Model - y - Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str] - """ - ``` + user = User(name='John Doe', age=20) # (2)! + print(user) + #> name='John Doe' + ``` - - `'allow'`: Providing extra data is allowed and stored in the `__pydantic_extra__` dictionary attribute: - ```python - from pydantic import BaseModel, ConfigDict + 1. This is the default behaviour. + 2. The `age` argument is ignored. + + Instead, with `extra='allow'`, the `age` argument is included: + + ```py + from pydantic import BaseModel, ConfigDict - class Model(BaseModel): - x: int + class User(BaseModel): + model_config = ConfigDict(extra='allow') - model_config = ConfigDict(extra='allow') + name: str - m = Model(x=1, y='a') - assert m.__pydantic_extra__ == {'y': 'a'} - ``` - By default, no validation will be applied to these extra items, but you can set a type for the values by overriding - the type annotation for `__pydantic_extra__`: - ```python - from pydantic import BaseModel, ConfigDict, Field, ValidationError + user = User(name='John Doe', age=20) # (1)! + print(user) + #> name='John Doe' age=20 + ``` + 1. The `age` argument is included. - class Model(BaseModel): - __pydantic_extra__: dict[str, int] = Field(init=False) # (1)! + With `extra='forbid'`, an error is raised: - x: int - - model_config = ConfigDict(extra='allow') - - - try: - Model(x=1, y='a') - except ValidationError as exc: - print(exc) - """ - 1 validation error for Model - y - Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] - """ - - m = Model(x=1, y='2') - assert m.x == 1 - assert m.y == 2 - assert m.model_dump() == {'x': 1, 'y': 2} - assert m.__pydantic_extra__ == {'y': 2} - ``` - - 1. The `= Field(init=False)` does not have any effect at runtime, but prevents the `__pydantic_extra__` field from - being included as a parameter to the model's `__init__` method by type checkers. - - As well as specifying an `extra` configuration value on the model, you can also provide it as an argument to the validation methods. - This will override any `extra` configuration value set on the model: - ```python + ```py from pydantic import BaseModel, ConfigDict, ValidationError - class Model(BaseModel): - x: int - model_config = ConfigDict(extra="allow") + + class User(BaseModel): + model_config = ConfigDict(extra='forbid') + + name: str + try: - # Override model config and forbid extra fields just this time - Model.model_validate({"x": 1, "y": 2}, extra="forbid") - except ValidationError as exc: - print(exc) - """ - 1 validation error for Model - y - Extra inputs are not permitted [type=extra_forbidden, input_value=2, input_type=int] - """ + User(name='John Doe', age=20) + except ValidationError as e: + print(e) + ''' + 1 validation error for User + age + Extra inputs are not permitted [type=extra_forbidden, input_value=20, input_type=int] + ''' ``` - ''' + """ frozen: bool """ - Whether models are faux-immutable, i.e. whether `__setattr__` is allowed, and also generates + Whether or not models are faux-immutable, i.e. whether `__setattr__` is allowed, and also generates a `__hash__()` method for the model. This makes instances of the model potentially hashable if all the attributes are hashable. Defaults to `False`. Note: - On V1, the inverse of this setting was called `allow_mutation`, and was `True` by default. + On V1, this setting was called `allow_mutation`, and was `True` by default. """ populate_by_name: bool @@ -191,38 +132,32 @@ class ConfigDict(TypedDict, total=False): Whether an aliased field may be populated by its name as given by the model attribute, as well as the alias. Defaults to `False`. - !!! warning - `populate_by_name` usage is not recommended in v2.11+ and will be deprecated in v3. - Instead, you should use the [`validate_by_name`][pydantic.config.ConfigDict.validate_by_name] configuration setting. + Note: + The name of this configuration setting was changed in **v2.0** from + `allow_population_by_field_name` to `populate_by_name`. - When `validate_by_name=True` and `validate_by_alias=True`, this is strictly equivalent to the - previous behavior of `populate_by_name=True`. + ```py + from pydantic import BaseModel, ConfigDict, Field - In v2.11, we also introduced a [`validate_by_alias`][pydantic.config.ConfigDict.validate_by_alias] setting that introduces more fine grained - control for validation behavior. - Here's how you might go about using the new settings to achieve the same behavior: + class User(BaseModel): + model_config = ConfigDict(populate_by_name=True) - ```python - from pydantic import BaseModel, ConfigDict, Field + name: str = Field(alias='full_name') # (1)! + age: int - class Model(BaseModel): - model_config = ConfigDict(validate_by_name=True, validate_by_alias=True) - my_field: str = Field(alias='my_alias') # (1)! + user = User(full_name='John Doe', age=20) # (2)! + print(user) + #> name='John Doe' age=20 + user = User(name='John Doe', age=20) # (3)! + print(user) + #> name='John Doe' age=20 + ``` - m = Model(my_alias='foo') # (2)! - print(m) - #> my_field='foo' - - m = Model(my_field='foo') # (3)! - print(m) - #> my_field='foo' - ``` - - 1. The field `'my_field'` has an alias `'my_alias'`. - 2. The model is populated by the alias `'my_alias'`. - 3. The model is populated by the attribute name `'my_field'`. + 1. The field `'name'` has an alias `'full_name'`. + 2. The model is populated by the alias `'full_name'`. + 3. The model is populated by the field name `'name'`. """ use_enum_values: bool @@ -235,28 +170,29 @@ class ConfigDict(TypedDict, total=False): for said Field to ensure that the `use_enum_values` flag takes effect on the default, as extracting an enum's value occurs during validation, not serialization. - ```python + ```py from enum import Enum from typing import Optional from pydantic import BaseModel, ConfigDict, Field + class SomeEnum(Enum): FOO = 'foo' BAR = 'bar' BAZ = 'baz' + class SomeModel(BaseModel): model_config = ConfigDict(use_enum_values=True) some_enum: SomeEnum - another_enum: Optional[SomeEnum] = Field( - default=SomeEnum.FOO, validate_default=True - ) + another_enum: Optional[SomeEnum] = Field(default=SomeEnum.FOO, validate_default=True) + model1 = SomeModel(some_enum=SomeEnum.BAR) print(model1.model_dump()) - #> {'some_enum': 'bar', 'another_enum': 'foo'} + # {'some_enum': 'bar', 'another_enum': 'foo'} model2 = SomeModel(some_enum=SomeEnum.BAR, another_enum=SomeEnum.BAZ) print(model2.model_dump()) @@ -272,7 +208,7 @@ class ConfigDict(TypedDict, total=False): In case the user changes the data after the model is created, the model is _not_ revalidated. - ```python + ```py from pydantic import BaseModel class User(BaseModel): @@ -291,7 +227,7 @@ class ConfigDict(TypedDict, total=False): In case you want to revalidate the model when the data is changed, you can use `validate_assignment=True`: - ```python + ```py from pydantic import BaseModel, ValidationError class User(BaseModel, validate_assignment=True): # (1)! @@ -320,7 +256,7 @@ class ConfigDict(TypedDict, total=False): """ Whether arbitrary types are allowed for field types. Defaults to `False`. - ```python + ```py from pydantic import BaseModel, ConfigDict, ValidationError # This is not a pydantic model, it's an arbitrary class @@ -379,20 +315,14 @@ class ConfigDict(TypedDict, total=False): loc_by_alias: bool """Whether to use the actual key provided in the data (e.g. alias) for error `loc`s rather than the field's name. Defaults to `True`.""" - alias_generator: Callable[[str], str] | AliasGenerator | None + alias_generator: Callable[[str], str] | None """ - A callable that takes a field name and returns an alias for it - or an instance of [`AliasGenerator`][pydantic.aliases.AliasGenerator]. Defaults to `None`. + A callable that takes a field name and returns an alias for it. - When using a callable, the alias generator is used for both validation and serialization. - If you want to use different alias generators for validation and serialization, you can use - [`AliasGenerator`][pydantic.aliases.AliasGenerator] instead. + If data source field names do not match your code style (e. g. CamelCase fields), + you can automatically generate aliases using `alias_generator`: - If data source field names do not match your code style (e.g. CamelCase fields), - you can automatically generate aliases using `alias_generator`. Here's an example with - a basic callable: - - ```python + ```py from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_pascal @@ -409,30 +339,6 @@ class ConfigDict(TypedDict, total=False): #> {'Name': 'Filiz', 'LanguageCode': 'tr-TR'} ``` - If you want to use different alias generators for validation and serialization, you can use - [`AliasGenerator`][pydantic.aliases.AliasGenerator]. - - ```python - from pydantic import AliasGenerator, BaseModel, ConfigDict - from pydantic.alias_generators import to_camel, to_pascal - - class Athlete(BaseModel): - first_name: str - last_name: str - sport: str - - model_config = ConfigDict( - alias_generator=AliasGenerator( - validation_alias=to_camel, - serialization_alias=to_pascal, - ) - ) - - athlete = Athlete(firstName='John', lastName='Doe', sport='track') - print(athlete.model_dump(by_alias=True)) - #> {'FirstName': 'John', 'LastName': 'Doe', 'Sport': 'track'} - ``` - Note: Pydantic offers three built-in alias generators: [`to_pascal`][pydantic.alias_generators.to_pascal], [`to_camel`][pydantic.alias_generators.to_camel], and [`to_snake`][pydantic.alias_generators.to_snake]. @@ -446,7 +352,7 @@ class ConfigDict(TypedDict, total=False): """ allow_inf_nan: bool - """Whether to allow infinity (`+inf` an `-inf`) and NaN values to float and decimal fields. Defaults to `True`.""" + """Whether to allow infinity (`+inf` an `-inf`) and NaN values to float fields. Defaults to `True`.""" json_schema_extra: JsonDict | JsonSchemaExtraCallable | None """A dict or callable to provide extra JSON schema properties. Defaults to `None`.""" @@ -455,16 +361,16 @@ class ConfigDict(TypedDict, total=False): """ A `dict` of custom JSON encoders for specific types. Defaults to `None`. - /// version-deprecated | v2 - This configuration option is a carryover from v1. We originally planned to remove it in v2 but didn't have a 1:1 replacement - so we are keeping it for now. It is still deprecated and will likely be removed in the future. - /// + !!! warning "Deprecated" + This config option is a carryover from v1. + We originally planned to remove it in v2 but didn't have a 1:1 replacement so we are keeping it for now. + It is still deprecated and will likely be removed in the future. """ # new in V2 strict: bool """ - Whether strict validation is applied to all fields on the model. + _(new in V2)_ If `True`, strict validation is applied to all fields on the model. By default, Pydantic attempts to coerce values to the correct type, when possible. @@ -473,7 +379,7 @@ class ConfigDict(TypedDict, total=False): To configure strict mode for all fields on a model, you can set `strict=True` on the model. - ```python + ```py from pydantic import BaseModel, ConfigDict class Model(BaseModel): @@ -487,81 +393,133 @@ class ConfigDict(TypedDict, total=False): See the [Conversion Table](../concepts/conversion_table.md) for more details on how Pydantic converts data in both strict and lax modes. - - /// version-added | v2 - /// """ # whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never' revalidate_instances: Literal['always', 'never', 'subclass-instances'] """ - When and how to revalidate models and dataclasses during validation. Can be one of: + When and how to revalidate models and dataclasses during validation. Accepts the string + values of `'never'`, `'always'` and `'subclass-instances'`. Defaults to `'never'`. - - `'never'`: will *not* revalidate models and dataclasses during validation - - `'always'`: will revalidate models and dataclasses during validation - - `'subclass-instances'`: will revalidate models and dataclasses during validation if the instance is a + - `'never'` will not revalidate models and dataclasses during validation + - `'always'` will revalidate models and dataclasses during validation + - `'subclass-instances'` will revalidate models and dataclasses during validation if the instance is a subclass of the model or dataclass - The default is `'never'` (no revalidation). + By default, model and dataclass instances are not revalidated during validation. - This configuration only affects *the current model* it is applied on, and does *not* populate to the models - referenced in fields. + ```py + from typing import List - ```python from pydantic import BaseModel class User(BaseModel, revalidate_instances='never'): # (1)! - name: str - - class Transaction(BaseModel): - user: User - - my_user = User(name='John') - t = Transaction(user=my_user) - - my_user.name = 1 # (2)! - t = Transaction(user=my_user) # (3)! - print(t) - #> user=User(name=1) - ``` - - 1. This is the default behavior. - 2. The assignment is *not* validated, unless you set [`validate_assignment`][pydantic.ConfigDict.validate_assignment] in the configuration. - 3. Since `revalidate_instances` is set to `'never'`, the user instance is not revalidated. - - Here is an example demonstrating the behavior of `'subclass-instances'`: - - ```python - from pydantic import BaseModel - - class User(BaseModel, revalidate_instances='subclass-instances'): - name: str + hobbies: List[str] class SubUser(User): - age: int + sins: List[str] class Transaction(BaseModel): user: User - my_user = User(name='John') - my_user.name = 1 # (1)! - t = Transaction(user=my_user) # (2)! + my_user = User(hobbies=['reading']) + t = Transaction(user=my_user) print(t) - #> user=User(name=1) + #> user=User(hobbies=['reading']) - my_sub_user = SubUser(name='John', age=20) + my_user.hobbies = [1] # (2)! + t = Transaction(user=my_user) # (3)! + print(t) + #> user=User(hobbies=[1]) + + my_sub_user = SubUser(hobbies=['scuba diving'], sins=['lying']) t = Transaction(user=my_sub_user) - print(t) # (3)! - #> user=User(name='John') + print(t) + #> user=SubUser(hobbies=['scuba diving'], sins=['lying']) ``` - 1. The assignment is *not* validated, unless you set [`validate_assignment`][pydantic.ConfigDict.validate_assignment] in the configuration. - 2. Because `my_user` is a "direct" instance of `User`, it is *not* being revalidated. It would have been the case if - `revalidate_instances` was set to `'always'`. - 3. Because `my_sub_user` is an instance of a `User` subclass, it is being revalidated. In this case, Pydantic coerces `my_sub_user` to the defined - `User` class defined on `Transaction`. If one of its fields had an invalid value, a validation error would have been raised. + 1. `revalidate_instances` is set to `'never'` by **default. + 2. The assignment is not validated, unless you set `validate_assignment` to `True` in the model's config. + 3. Since `revalidate_instances` is set to `never`, this is not revalidated. - /// version-added | v2 - /// + If you want to revalidate instances during validation, you can set `revalidate_instances` to `'always'` + in the model's config. + + ```py + from typing import List + + from pydantic import BaseModel, ValidationError + + class User(BaseModel, revalidate_instances='always'): # (1)! + hobbies: List[str] + + class SubUser(User): + sins: List[str] + + class Transaction(BaseModel): + user: User + + my_user = User(hobbies=['reading']) + t = Transaction(user=my_user) + print(t) + #> user=User(hobbies=['reading']) + + my_user.hobbies = [1] + try: + t = Transaction(user=my_user) # (2)! + except ValidationError as e: + print(e) + ''' + 1 validation error for Transaction + user.hobbies.0 + Input should be a valid string [type=string_type, input_value=1, input_type=int] + ''' + + my_sub_user = SubUser(hobbies=['scuba diving'], sins=['lying']) + t = Transaction(user=my_sub_user) + print(t) # (3)! + #> user=User(hobbies=['scuba diving']) + ``` + + 1. `revalidate_instances` is set to `'always'`. + 2. The model is revalidated, since `revalidate_instances` is set to `'always'`. + 3. Using `'never'` we would have gotten `user=SubUser(hobbies=['scuba diving'], sins=['lying'])`. + + It's also possible to set `revalidate_instances` to `'subclass-instances'` to only revalidate instances + of subclasses of the model. + + ```py + from typing import List + + from pydantic import BaseModel + + class User(BaseModel, revalidate_instances='subclass-instances'): # (1)! + hobbies: List[str] + + class SubUser(User): + sins: List[str] + + class Transaction(BaseModel): + user: User + + my_user = User(hobbies=['reading']) + t = Transaction(user=my_user) + print(t) + #> user=User(hobbies=['reading']) + + my_user.hobbies = [1] + t = Transaction(user=my_user) # (2)! + print(t) + #> user=User(hobbies=[1]) + + my_sub_user = SubUser(hobbies=['scuba diving'], sins=['lying']) + t = Transaction(user=my_sub_user) + print(t) # (3)! + #> user=User(hobbies=['scuba diving']) + ``` + + 1. `revalidate_instances` is set to `'subclass-instances'`. + 2. This is not revalidated, since `my_user` is not a subclass of `User`. + 3. Using `'never'` we would have gotten `user=SubUser(hobbies=['scuba diving'], sins=['lying'])`. """ ser_json_timedelta: Literal['iso8601', 'float'] @@ -569,84 +527,17 @@ class ConfigDict(TypedDict, total=False): The format of JSON serialized timedeltas. Accepts the string values of `'iso8601'` and `'float'`. Defaults to `'iso8601'`. - - `'iso8601'` will serialize timedeltas to [ISO 8601 text format](https://en.wikipedia.org/wiki/ISO_8601#Durations). + - `'iso8601'` will serialize timedeltas to ISO 8601 durations. - `'float'` will serialize timedeltas to the total number of seconds. - - /// version-changed | v2.12 - It is now recommended to use the [`ser_json_temporal`][pydantic.config.ConfigDict.ser_json_temporal] - setting. `ser_json_timedelta` will be deprecated in v3. - /// """ - ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] + ser_json_bytes: Literal['utf8', 'base64'] """ - The format of JSON serialized temporal types from the [`datetime`][] module. This includes: - - - [`datetime.datetime`][] - - [`datetime.date`][] - - [`datetime.time`][] - - [`datetime.timedelta`][] - - Can be one of: - - - `'iso8601'` will serialize date-like types to [ISO 8601 text format](https://en.wikipedia.org/wiki/ISO_8601#Durations). - - `'milliseconds'` will serialize date-like types to a floating point number of milliseconds since the epoch. - - `'seconds'` will serialize date-like types to a floating point number of seconds since the epoch. - - Defaults to `'iso8601'`. - - /// version-added | v2.12 - This setting replaces [`ser_json_timedelta`][pydantic.config.ConfigDict.ser_json_timedelta], - which will be deprecated in v3. `ser_json_temporal` adds more configurability for the other temporal types. - /// - """ - - val_temporal_unit: Literal['seconds', 'milliseconds', 'infer'] - """ - The unit to assume for validating numeric input for datetime-like types ([`datetime.datetime`][] and [`datetime.date`][]). Can be one of: - - - `'seconds'` will validate date or time numeric inputs as seconds since the [epoch]. - - `'milliseconds'` will validate date or time numeric inputs as milliseconds since the [epoch]. - - `'infer'` will infer the unit from the string numeric input on unix time as: - - * seconds since the [epoch] if $-2^{10} <= v <= 2^{10}$ - * milliseconds since the [epoch] (if $v < -2^{10}$ or $v > 2^{10}$). - - Defaults to `'infer'`. - - /// version-added | v2.12 - /// - - [epoch]: https://en.wikipedia.org/wiki/Unix_time - """ - - ser_json_bytes: Literal['utf8', 'base64', 'hex'] - """ - The encoding of JSON serialized bytes. Defaults to `'utf8'`. - Set equal to `val_json_bytes` to get back an equal value after serialization round trip. + The encoding of JSON serialized bytes. Accepts the string values of `'utf8'` and `'base64'`. + Defaults to `'utf8'`. - `'utf8'` will serialize bytes to UTF-8 strings. - `'base64'` will serialize bytes to URL safe base64 strings. - - `'hex'` will serialize bytes to hexadecimal strings. - """ - - val_json_bytes: Literal['utf8', 'base64', 'hex'] - """ - The encoding of JSON serialized bytes to decode. Defaults to `'utf8'`. - Set equal to `ser_json_bytes` to get back an equal value after serialization round trip. - - - `'utf8'` will deserialize UTF-8 strings to bytes. - - `'base64'` will deserialize URL safe base64 strings to bytes. - - `'hex'` will deserialize hexadecimal strings to bytes. - """ - - ser_json_inf_nan: Literal['null', 'constants', 'strings'] - """ - The encoding of JSON serialized infinity and NaN float values. Defaults to `'null'`. - - - `'null'` will serialize infinity and NaN values as `null`. - - `'constants'` will serialize infinity and NaN values as `Infinity` and `NaN`. - - `'strings'` will serialize infinity as string `"Infinity"` and NaN as string `"NaN"`. """ # whether to validate default values during validation, default False @@ -654,21 +545,17 @@ class ConfigDict(TypedDict, total=False): """Whether to validate default values during validation. Defaults to `False`.""" validate_return: bool - """Whether to validate the return value from call validators. Defaults to `False`.""" + """whether to validate the return value from call validators. Defaults to `False`.""" - protected_namespaces: tuple[str | Pattern[str], ...] + protected_namespaces: tuple[str, ...] """ - A tuple of strings and/or regex patterns that prevent models from having fields with names that conflict with its existing members/methods. + A `tuple` of strings that prevent model to have field which conflict with them. + Defaults to `('model_', )`). - Strings are matched on a prefix basis. For instance, with `'dog'`, having a field named `'dog_name'` will be disallowed. + Pydantic prevents collisions between model attributes and `BaseModel`'s own methods by + namespacing them with the prefix `model_`. - Regex patterns are matched on the entire field name. For instance, with the pattern `'^dog$'`, having a field named `'dog'` will be disallowed, - but `'dog_name'` will be accepted. - - Defaults to `('model_validate', 'model_dump')`. This default is used to prevent collisions with the existing (and possibly future) - [validation](../concepts/models.md#validating-data) and [serialization](../concepts/serialization.md#serializing-data) methods. - - ```python + ```py import warnings from pydantic import BaseModel @@ -678,76 +565,62 @@ class ConfigDict(TypedDict, total=False): try: class Model(BaseModel): - model_dump_something: str + model_prefixed_field: str except UserWarning as e: print(e) ''' - Field 'model_dump_something' in 'Model' conflicts with protected namespace 'model_dump'. + Field "model_prefixed_field" has conflict with protected namespace "model_". - You may be able to solve this by setting the 'protected_namespaces' configuration to ('model_validate',). + You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ()`. ''' ``` You can customize this behavior using the `protected_namespaces` setting: - ```python {test="skip"} - import re + ```py import warnings from pydantic import BaseModel, ConfigDict - with warnings.catch_warnings(record=True) as caught_warnings: - warnings.simplefilter('always') # Catch all warnings + warnings.filterwarnings('error') # Raise warnings as errors + + try: class Model(BaseModel): - safe_field: str + model_prefixed_field: str also_protect_field: str - protect_this: str model_config = ConfigDict( - protected_namespaces=( - 'protect_me_', - 'also_protect_', - re.compile('^protect_this$'), - ) + protected_namespaces=('protect_me_', 'also_protect_') ) - for warning in caught_warnings: - print(f'{warning.message}') + except UserWarning as e: + print(e) ''' - Field 'also_protect_field' in 'Model' conflicts with protected namespace 'also_protect_'. - You may be able to solve this by setting the 'protected_namespaces' configuration to ('protect_me_', re.compile('^protect_this$'))`. + Field "also_protect_field" has conflict with protected namespace "also_protect_". - Field 'protect_this' in 'Model' conflicts with protected namespace 're.compile('^protect_this$')'. - You may be able to solve this by setting the 'protected_namespaces' configuration to ('protect_me_', 'also_protect_')`. + You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_',)`. ''' ``` While Pydantic will only emit a warning when an item is in a protected namespace but does not actually have a collision, an error _is_ raised if there is an actual collision with an existing attribute: - ```python - from pydantic import BaseModel, ConfigDict + ```py + from pydantic import BaseModel try: class Model(BaseModel): model_validate: str - model_config = ConfigDict(protected_namespaces=('model_',)) - - except ValueError as e: + except NameError as e: print(e) ''' - Field 'model_validate' conflicts with member > of protected namespace 'model_'. + Field "model_validate" conflicts with member > of protected namespace "model_". ''' ``` - - /// version-changed | v2.10 - The default protected namespaces was changed from `('model_',)` to `('model_validate', 'model_dump')`, to allow - for fields like `model_id`, `model_name` to be used. - /// """ hide_input_in_errors: bool @@ -756,7 +629,7 @@ class ConfigDict(TypedDict, total=False): Pydantic shows the input value and type when it raises `ValidationError` during the validation. - ```python + ```py from pydantic import BaseModel, ValidationError class Model(BaseModel): @@ -775,7 +648,7 @@ class ConfigDict(TypedDict, total=False): You can hide the input value and type by setting the `hide_input_in_errors` config to `True`. - ```python + ```py from pydantic import BaseModel, ConfigDict, ValidationError class Model(BaseModel): @@ -796,27 +669,27 @@ class ConfigDict(TypedDict, total=False): defer_build: bool """ - Whether to defer model validator and serializer construction until the first model validation. Defaults to False. + Whether to defer model validator and serializer construction until the first model validation. This can be useful to avoid the overhead of building models which are only used nested within other models, or when you want to manually define type namespace via - [`Model.model_rebuild(_types_namespace=...)`][pydantic.BaseModel.model_rebuild]. - - /// version-changed | v2.10 - The setting also applies to [Pydantic dataclasses](../concepts/dataclasses.md) and [type adapters](../concepts/type_adapter.md). - /// + [`Model.model_rebuild(_types_namespace=...)`][pydantic.BaseModel.model_rebuild]. Defaults to False. """ plugin_settings: dict[str, object] | None - """A `dict` of settings for plugins. Defaults to `None`.""" + """A `dict` of settings for plugins. Defaults to `None`. + + See [Pydantic Plugins](../concepts/plugins.md) for details. + """ schema_generator: type[_GenerateSchema] | None """ - The `GenerateSchema` class to use during core schema generation. + A custom core schema generator class to use when generating JSON schemas. + Useful if you want to change the way types are validated across an entire model/schema. Defaults to `None`. - /// version-deprecated | v2.10 - The `GenerateSchema` class is private and highly subject to change. - /// + The `GenerateSchema` interface is subject to change, currently only the `string_schema` method is public. + + See [#6737](https://github.com/pydantic/pydantic/pull/6737) for details. """ json_schema_serialization_defaults_required: bool @@ -830,7 +703,7 @@ class ConfigDict(TypedDict, total=False): between validation and serialization, and don't mind fields with defaults being marked as not required during serialization. See [#7209](https://github.com/pydantic/pydantic/issues/7209) for more details. - ```python + ```py from pydantic import BaseModel, ConfigDict class Model(BaseModel): @@ -856,9 +729,6 @@ class ConfigDict(TypedDict, total=False): } ''' ``` - - /// version-added | v2.4 - /// """ json_schema_mode_override: Literal['validation', 'serialization', None] @@ -876,7 +746,7 @@ class ConfigDict(TypedDict, total=False): the validation and serialization schemas (since both will use the specified schema), and so prevents the suffixes from being added to the definition references. - ```python + ```py from pydantic import BaseModel, ConfigDict, Json class Model(BaseModel): @@ -914,9 +784,6 @@ class ConfigDict(TypedDict, total=False): } ''' ``` - - /// version-added | v2.4 - /// """ coerce_numbers_to_str: bool @@ -925,7 +792,7 @@ class ConfigDict(TypedDict, total=False): Pydantic doesn't allow number types (`int`, `float`, `Decimal`) to be coerced as type `str` by default. - ```python + ```py from decimal import Decimal from pydantic import BaseModel, ConfigDict, ValidationError @@ -959,18 +826,15 @@ class ConfigDict(TypedDict, total=False): regex_engine: Literal['rust-regex', 'python-re'] """ - The regex engine to be used for pattern validation. + The regex engine to used for pattern validation Defaults to `'rust-regex'`. - - `'rust-regex'` uses the [`regex`](https://docs.rs/regex) Rust crate, + - `rust-regex` uses the [`regex`](https://docs.rs/regex) Rust crate, which is non-backtracking and therefore more DDoS resistant, but does not support all regex features. - - `'python-re'` use the [`re`][] module, which supports all regex features, but may be slower. + - `python-re` use the [`re`](https://docs.python.org/3/library/re.html) module, + which supports all regex features, but may be slower. - !!! note - If you use a compiled regex pattern, the `'python-re'` engine will be used regardless of this setting. - This is so that flags such as [`re.IGNORECASE`][] are respected. - - ```python + ```py from pydantic import BaseModel, ConfigDict, Field, ValidationError class Model(BaseModel): @@ -991,298 +855,18 @@ class ConfigDict(TypedDict, total=False): String should match pattern '^abc(?=def)' [type=string_pattern_mismatch, input_value='abxyzcdef', input_type=str] ''' ``` - - /// version-added | v2.5 - /// """ validation_error_cause: bool """ - If `True`, Python exceptions that were part of a validation failure will be shown as an exception group as a cause. Can be useful for debugging. Defaults to `False`. + If `True`, python exceptions that were part of a validation failure will be shown as an exception group as a cause. Can be useful for debugging. Defaults to `False`. Note: Python 3.10 and older don't support exception groups natively. <=3.10, backport must be installed: `pip install exceptiongroup`. Note: - The structure of validation errors are likely to change in future Pydantic versions. Pydantic offers no guarantees about their structure. Should be used for visual traceback debugging only. - - /// version-added | v2.5 - /// + The structure of validation errors are likely to change in future pydantic versions. Pydantic offers no guarantees about the structure of validation errors. Should be used for visual traceback debugging only. """ - use_attribute_docstrings: bool - ''' - Whether docstrings of attributes (bare string literals immediately following the attribute declaration) - should be used for field descriptions. Defaults to `False`. - - ```python - from pydantic import BaseModel, ConfigDict, Field - - - class Model(BaseModel): - model_config = ConfigDict(use_attribute_docstrings=True) - - x: str - """ - Example of an attribute docstring - """ - - y: int = Field(description="Description in Field") - """ - Description in Field overrides attribute docstring - """ - - - print(Model.model_fields["x"].description) - # > Example of an attribute docstring - print(Model.model_fields["y"].description) - # > Description in Field - ``` - This requires the source code of the class to be available at runtime. - - !!! warning "Usage with `TypedDict` and stdlib dataclasses" - Due to current limitations, attribute docstrings detection may not work as expected when using - [`TypedDict`][typing.TypedDict] and stdlib dataclasses, in particular when: - - - inheritance is being used. - - multiple classes have the same name in the same source file (unless Python 3.13 or greater is used). - - /// version-added | v2.7 - /// - ''' - - cache_strings: bool | Literal['all', 'keys', 'none'] - """ - Whether to cache strings to avoid constructing new Python objects. Defaults to True. - - Enabling this setting should significantly improve validation performance while increasing memory usage slightly. - - - `True` or `'all'` (the default): cache all strings - - `'keys'`: cache only dictionary keys - - `False` or `'none'`: no caching - - !!! note - `True` or `'all'` is required to cache strings during general validation because - validators don't know if they're in a key or a value. - - !!! tip - If repeated strings are rare, it's recommended to use `'keys'` or `'none'` to reduce memory usage, - as the performance difference is minimal if repeated strings are rare. - - /// version-added | v2.7 - /// - """ - - validate_by_alias: bool - """ - Whether an aliased field may be populated by its alias. Defaults to `True`. - - Here's an example of disabling validation by alias: - - ```py - from pydantic import BaseModel, ConfigDict, Field - - class Model(BaseModel): - model_config = ConfigDict(validate_by_name=True, validate_by_alias=False) - - my_field: str = Field(validation_alias='my_alias') # (1)! - - m = Model(my_field='foo') # (2)! - print(m) - #> my_field='foo' - ``` - - 1. The field `'my_field'` has an alias `'my_alias'`. - 2. The model can only be populated by the attribute name `'my_field'`. - - !!! warning - You cannot set both `validate_by_alias` and `validate_by_name` to `False`. - This would make it impossible to populate an attribute. - - See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example. - - If you set `validate_by_alias` to `False`, under the hood, Pydantic dynamically sets - `validate_by_name` to `True` to ensure that validation can still occur. - - /// version-added | v2.11 - This setting was introduced in conjunction with [`validate_by_name`][pydantic.ConfigDict.validate_by_name] - to empower users with more fine grained validation control. - /// - """ - - validate_by_name: bool - """ - Whether an aliased field may be populated by its name as given by the model - attribute. Defaults to `False`. - - ```python - from pydantic import BaseModel, ConfigDict, Field - - class Model(BaseModel): - model_config = ConfigDict(validate_by_name=True, validate_by_alias=True) - - my_field: str = Field(validation_alias='my_alias') # (1)! - - m = Model(my_alias='foo') # (2)! - print(m) - #> my_field='foo' - - m = Model(my_field='foo') # (3)! - print(m) - #> my_field='foo' - ``` - - 1. The field `'my_field'` has an alias `'my_alias'`. - 2. The model is populated by the alias `'my_alias'`. - 3. The model is populated by the attribute name `'my_field'`. - - !!! warning - You cannot set both `validate_by_alias` and `validate_by_name` to `False`. - This would make it impossible to populate an attribute. - - See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example. - - /// version-added | v2.11 - This setting was introduced in conjunction with [`validate_by_alias`][pydantic.ConfigDict.validate_by_alias] - to empower users with more fine grained validation control. It is an alternative to [`populate_by_name`][pydantic.ConfigDict.populate_by_name], - that enables validation by name **and** by alias. - /// - """ - - serialize_by_alias: bool - """ - Whether an aliased field should be serialized by its alias. Defaults to `False`. - - Note: In v2.11, `serialize_by_alias` was introduced to address the - [popular request](https://github.com/pydantic/pydantic/issues/8379) - for consistency with alias behavior for validation and serialization settings. - In v3, the default value is expected to change to `True` for consistency with the validation default. - - ```python - from pydantic import BaseModel, ConfigDict, Field - - class Model(BaseModel): - model_config = ConfigDict(serialize_by_alias=True) - - my_field: str = Field(serialization_alias='my_alias') # (1)! - - m = Model(my_field='foo') - print(m.model_dump()) # (2)! - #> {'my_alias': 'foo'} - ``` - - 1. The field `'my_field'` has an alias `'my_alias'`. - 2. The model is serialized using the alias `'my_alias'` for the `'my_field'` attribute. - - - /// version-added | v2.11 - This setting was introduced to address the [popular request](https://github.com/pydantic/pydantic/issues/8379) - for consistency with alias behavior for validation and serialization. - - In v3, the default value is expected to change to `True` for consistency with the validation default. - /// - """ - - url_preserve_empty_path: bool - """ - Whether to preserve empty URL paths when validating values for a URL type. Defaults to `False`. - - ```python - from pydantic import AnyUrl, BaseModel, ConfigDict - - class Model(BaseModel): - model_config = ConfigDict(url_preserve_empty_path=True) - - url: AnyUrl - - m = Model(url='http://example.com') - print(m.url) - #> http://example.com - ``` - - /// version-added | v2.12 - /// - """ - - -_TypeT = TypeVar('_TypeT', bound=type) - - -@overload -@deprecated('Passing `config` as a keyword argument is deprecated. Pass `config` as a positional argument instead.') -def with_config(*, config: ConfigDict) -> Callable[[_TypeT], _TypeT]: ... - - -@overload -def with_config(config: ConfigDict, /) -> Callable[[_TypeT], _TypeT]: ... - - -@overload -def with_config(**config: Unpack[ConfigDict]) -> Callable[[_TypeT], _TypeT]: ... - - -def with_config(config: ConfigDict | None = None, /, **kwargs: Any) -> Callable[[_TypeT], _TypeT]: - """!!! abstract "Usage Documentation" - [Configuration with other types](../concepts/config.md#configuration-on-other-supported-types) - - A convenience decorator to set a [Pydantic configuration](config.md) on a `TypedDict` or a `dataclass` from the standard library. - - Although the configuration can be set using the `__pydantic_config__` attribute, it does not play well with type checkers, - especially with `TypedDict`. - - !!! example "Usage" - - ```python - from typing_extensions import TypedDict - - from pydantic import ConfigDict, TypeAdapter, with_config - - @with_config(ConfigDict(str_to_lower=True)) - class TD(TypedDict): - x: str - - ta = TypeAdapter(TD) - - print(ta.validate_python({'x': 'ABC'})) - #> {'x': 'abc'} - ``` - - /// deprecated-removed | v2.11 v3 - Passing `config` as a keyword argument. - /// - - /// version-changed | v2.11 - Keyword arguments can be provided directly instead of a config dictionary. - /// - """ - if config is not None and kwargs: - raise ValueError('Cannot specify both `config` and keyword arguments') - - if len(kwargs) == 1 and (kwargs_conf := kwargs.get('config')) is not None: - warnings.warn( - 'Passing `config` as a keyword argument is deprecated. Pass `config` as a positional argument instead', - category=PydanticDeprecatedSince211, - stacklevel=2, - ) - final_config = cast(ConfigDict, kwargs_conf) - else: - final_config = config if config is not None else cast(ConfigDict, kwargs) - - def inner(class_: _TypeT, /) -> _TypeT: - # Ideally, we would check for `class_` to either be a `TypedDict` or a stdlib dataclass. - # However, the `@with_config` decorator can be applied *after* `@dataclass`. To avoid - # common mistakes, we at least check for `class_` to not be a Pydantic model. - from ._internal._utils import is_model_class - - if is_model_class(class_): - raise PydanticUserError( - f'Cannot use `with_config` on {class_.__name__} as it is a Pydantic model', - code='with-config-on-model', - ) - class_.__pydantic_config__ = final_config - return class_ - - return inner - __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/dataclasses.py b/Backend/venv/lib/python3.12/site-packages/pydantic/dataclasses.py index cecd5402..736762d6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/dataclasses.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/dataclasses.py @@ -1,26 +1,21 @@ """Provide an enhanced dataclass that performs validation.""" - from __future__ import annotations as _annotations import dataclasses -import functools import sys import types -from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, overload -from warnings import warn +from typing import TYPE_CHECKING, Any, Callable, Generic, NoReturn, TypeVar, overload -from typing_extensions import TypeGuard, dataclass_transform +from typing_extensions import Literal, TypeGuard, dataclass_transform -from ._internal import _config, _decorators, _mock_val_ser, _namespace_utils, _typing_extra +from ._internal import _config, _decorators, _typing_extra from ._internal import _dataclasses as _pydantic_dataclasses from ._migration import getattr_migration from .config import ConfigDict -from .errors import PydanticUserError -from .fields import Field, FieldInfo, PrivateAttr +from .fields import Field, FieldInfo if TYPE_CHECKING: from ._internal._dataclasses import PydanticDataclass - from ._internal._namespace_utils import MappingNamespace __all__ = 'dataclass', 'rebuild_dataclass' @@ -28,7 +23,7 @@ _T = TypeVar('_T') if sys.version_info >= (3, 10): - @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) @overload def dataclass( *, @@ -45,7 +40,7 @@ if sys.version_info >= (3, 10): ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore ... - @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) @overload def dataclass( _cls: type[_T], # type: ignore @@ -55,16 +50,17 @@ if sys.version_info >= (3, 10): eq: bool = True, order: bool = False, unsafe_hash: bool = False, - frozen: bool | None = None, + frozen: bool = False, config: ConfigDict | type[object] | None = None, validate_on_init: bool | None = None, kw_only: bool = ..., slots: bool = ..., - ) -> type[PydanticDataclass]: ... + ) -> type[PydanticDataclass]: + ... else: - @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) @overload def dataclass( *, @@ -73,13 +69,13 @@ else: eq: bool = True, order: bool = False, unsafe_hash: bool = False, - frozen: bool | None = None, + frozen: bool = False, config: ConfigDict | type[object] | None = None, validate_on_init: bool | None = None, ) -> Callable[[type[_T]], type[PydanticDataclass]]: # type: ignore ... - @dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) + @dataclass_transform(field_specifiers=(dataclasses.field, Field)) @overload def dataclass( _cls: type[_T], # type: ignore @@ -89,13 +85,14 @@ else: eq: bool = True, order: bool = False, unsafe_hash: bool = False, - frozen: bool | None = None, + frozen: bool = False, config: ConfigDict | type[object] | None = None, validate_on_init: bool | None = None, - ) -> type[PydanticDataclass]: ... + ) -> type[PydanticDataclass]: + ... -@dataclass_transform(field_specifiers=(dataclasses.field, Field, PrivateAttr)) +@dataclass_transform(field_specifiers=(dataclasses.field, Field)) def dataclass( _cls: type[_T] | None = None, *, @@ -104,14 +101,13 @@ def dataclass( eq: bool = True, order: bool = False, unsafe_hash: bool = False, - frozen: bool | None = None, + frozen: bool = False, config: ConfigDict | type[object] | None = None, validate_on_init: bool | None = None, kw_only: bool = False, slots: bool = False, ) -> Callable[[type[_T]], type[PydanticDataclass]] | type[PydanticDataclass]: - """!!! abstract "Usage Documentation" - [`dataclasses`](../concepts/dataclasses.md) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/dataclasses/ A decorator used to create a Pydantic-enhanced dataclass, similar to the standard Python `dataclass`, but with added validation. @@ -123,13 +119,13 @@ def dataclass( init: Included for signature compatibility with `dataclasses.dataclass`, and is passed through to `dataclasses.dataclass` when appropriate. If specified, must be set to `False`, as pydantic inserts its own `__init__` function. - repr: A boolean indicating whether to include the field in the `__repr__` output. - eq: Determines if a `__eq__` method should be generated for the class. + repr: A boolean indicating whether or not to include the field in the `__repr__` output. + eq: Determines if a `__eq__` should be generated for the class. order: Determines if comparison magic methods should be generated, such as `__lt__`, but not `__eq__`. - unsafe_hash: Determines if a `__hash__` method should be included in the class, as in `dataclasses.dataclass`. + unsafe_hash: Determines if an unsafe hashing function should be included in the class. frozen: Determines if the generated class should be a 'frozen' `dataclass`, which does not allow its - attributes to be modified after it has been initialized. If not set, the value from the provided `config` argument will be used (and will default to `False` otherwise). - config: The Pydantic config to use for the `dataclass`. + attributes to be modified from its constructor. + config: A configuration for the `dataclass` generation. validate_on_init: A deprecated parameter included for backwards compatibility; in V2, all Pydantic dataclasses are validated on init. kw_only: Determines if `__init__` method parameters must be specified by keyword only. Defaults to `False`. @@ -146,10 +142,30 @@ def dataclass( assert validate_on_init is not False, 'validate_on_init=False is no longer supported' if sys.version_info >= (3, 10): - kwargs = {'kw_only': kw_only, 'slots': slots} + kwargs = dict(kw_only=kw_only, slots=slots) + + def make_pydantic_fields_compatible(cls: type[Any]) -> None: + """Make sure that stdlib `dataclasses` understands `Field` kwargs like `kw_only` + To do that, we simply change + `x: int = pydantic.Field(..., kw_only=True)` + into + `x: int = dataclasses.field(default=pydantic.Field(..., kw_only=True), kw_only=True)` + """ + for field_name in cls.__annotations__: + try: + field_value = getattr(cls, field_name) + except AttributeError: + # no default value has been set for this field + continue + if isinstance(field_value, FieldInfo) and field_value.kw_only: + setattr(cls, field_name, dataclasses.field(default=field_value, kw_only=True)) + else: kwargs = {} + def make_pydantic_fields_compatible(_) -> None: + return None + def create_dataclass(cls: type[Any]) -> type[PydanticDataclass]: """Create a Pydantic dataclass from a regular dataclass. @@ -159,41 +175,25 @@ def dataclass( Returns: A Pydantic dataclass. """ - from ._internal._utils import is_model_class - - if is_model_class(cls): - raise PydanticUserError( - f'Cannot create a Pydantic dataclass from {cls.__name__} as it is already a Pydantic model', - code='dataclass-on-model', - ) - original_cls = cls - # we warn on conflicting config specifications, but only if the class doesn't have a dataclass base - # because a dataclass base might provide a __pydantic_config__ attribute that we don't want to warn about - has_dataclass_base = any(dataclasses.is_dataclass(base) for base in cls.__bases__) - if not has_dataclass_base and config is not None and hasattr(cls, '__pydantic_config__'): - warn( - f'`config` is set via both the `dataclass` decorator and `__pydantic_config__` for dataclass {cls.__name__}. ' - f'The `config` specification from `dataclass` decorator will take priority.', - category=UserWarning, - stacklevel=2, - ) - - # if config is not explicitly provided, try to read it from the type - config_dict = config if config is not None else getattr(cls, '__pydantic_config__', None) + config_dict = config + if config_dict is None: + # if not explicitly provided, read from the type + cls_config = getattr(cls, '__pydantic_config__', None) + if cls_config is not None: + config_dict = cls_config config_wrapper = _config.ConfigWrapper(config_dict) decorators = _decorators.DecoratorInfos.build(cls) - decorators.update_from_config(config_wrapper) # Keep track of the original __doc__ so that we can restore it after applying the dataclasses decorator # Otherwise, classes with no __doc__ will have their signature added into the JSON schema description, # since dataclasses.dataclass will set this as the __doc__ original_doc = cls.__doc__ - if _pydantic_dataclasses.is_stdlib_dataclass(cls): - # Vanilla dataclasses include a default docstring (representing the class signature), - # which we don't want to preserve. + if _pydantic_dataclasses.is_builtin_dataclass(cls): + # Don't preserve the docstring for vanilla dataclasses, as it may include the signature + # This matches v1 behavior, and there was an explicit test for it original_doc = None # We don't want to add validation to the existing std lib dataclass, so we will subclass it @@ -205,125 +205,39 @@ def dataclass( bases = bases + (generic_base,) cls = types.new_class(cls.__name__, bases) - # Respect frozen setting from dataclass constructor and fallback to config setting if not provided - if frozen is not None: - frozen_ = frozen - if config_wrapper.frozen: - # It's not recommended to define both, as the setting from the dataclass decorator will take priority. - warn( - f'`frozen` is set via both the `dataclass` decorator and `config` for dataclass {cls.__name__!r}.' - 'This is not recommended. The `frozen` specification on `dataclass` will take priority.', - category=UserWarning, - stacklevel=2, - ) - else: - frozen_ = config_wrapper.frozen or False + make_pydantic_fields_compatible(cls) - # Make Pydantic's `Field()` function compatible with stdlib dataclasses. As we'll decorate - # `cls` with the stdlib `@dataclass` decorator first, there are two attributes, `kw_only` and - # `repr` that need to be understood *during* the stdlib creation. We do so in two steps: + cls = dataclasses.dataclass( # type: ignore[call-overload] + cls, + # the value of init here doesn't affect anything except that it makes it easier to generate a signature + init=True, + repr=repr, + eq=eq, + order=order, + unsafe_hash=unsafe_hash, + frozen=frozen, + **kwargs, + ) - # 1. On the decorated class, wrap `Field()` assignment with `dataclass.field()`, with the - # two attributes set (done in `as_dataclass_field()`) - cls_anns = _typing_extra.safe_get_annotations(cls) - for field_name in cls_anns: - # We should look for assignments in `__dict__` instead, but for now we follow - # the same behavior as stdlib dataclasses (see https://github.com/python/cpython/issues/88609) - field_value = getattr(cls, field_name, None) - if isinstance(field_value, FieldInfo): - setattr(cls, field_name, _pydantic_dataclasses.as_dataclass_field(field_value)) - - # 2. For bases of `cls` that are stdlib dataclasses, we temporarily patch their fields - # (see the docstring of the context manager): - with _pydantic_dataclasses.patch_base_fields(cls): - cls = dataclasses.dataclass( # pyright: ignore[reportCallIssue] - cls, - # the value of init here doesn't affect anything except that it makes it easier to generate a signature - init=True, - repr=repr, - eq=eq, - order=order, - unsafe_hash=unsafe_hash, - frozen=frozen_, - **kwargs, - ) - - if config_wrapper.validate_assignment: - original_setattr = cls.__setattr__ - - @functools.wraps(cls.__setattr__) - def validated_setattr(instance: PydanticDataclass, name: str, value: Any, /) -> None: - if frozen_: - return original_setattr(instance, name, value) # pyright: ignore[reportCallIssue] - inst_cls = type(instance) - attr = getattr(inst_cls, name, None) - - if isinstance(attr, property): - attr.__set__(instance, value) - elif isinstance(attr, functools.cached_property): - instance.__dict__.__setitem__(name, value) - else: - inst_cls.__pydantic_validator__.validate_assignment(instance, name, value) - - cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore - - if slots and not hasattr(cls, '__setstate__'): - # If slots is set, `pickle` (relied on by `copy.copy()`) will use - # `__setattr__()` to reconstruct the dataclass. However, the custom - # `__setattr__()` set above relies on `validate_assignment()`, which - # in turn expects all the field values to be already present on the - # instance, resulting in attribute errors. - # As such, we make use of `object.__setattr__()` instead. - # Note that we do so only if `__setstate__()` isn't already set (this is the - # case if on top of `slots`, `frozen` is used). - - # Taken from `dataclasses._dataclass_get/setstate()`: - def _dataclass_getstate(self: Any) -> list[Any]: - return [getattr(self, f.name) for f in dataclasses.fields(self)] - - def _dataclass_setstate(self: Any, state: list[Any]) -> None: - for field, value in zip(dataclasses.fields(self), state): - object.__setattr__(self, field.name, value) - - cls.__getstate__ = _dataclass_getstate # pyright: ignore[reportAttributeAccessIssue] - cls.__setstate__ = _dataclass_setstate # pyright: ignore[reportAttributeAccessIssue] - - # This is an undocumented attribute to distinguish stdlib/Pydantic dataclasses. - # It should be set as early as possible: - cls.__is_pydantic_dataclass__ = True cls.__pydantic_decorators__ = decorators # type: ignore cls.__doc__ = original_doc - # Can be non-existent for dynamically created classes: - firstlineno = getattr(original_cls, '__firstlineno__', None) cls.__module__ = original_cls.__module__ - if sys.version_info >= (3, 13) and firstlineno is not None: - # As per https://docs.python.org/3/reference/datamodel.html#type.__firstlineno__: - # Setting the `__module__` attribute removes the `__firstlineno__` item from the type’s dictionary. - original_cls.__firstlineno__ = firstlineno - cls.__firstlineno__ = firstlineno cls.__qualname__ = original_cls.__qualname__ - cls.__pydantic_fields_complete__ = classmethod(_pydantic_fields_complete) - cls.__pydantic_complete__ = False # `complete_dataclass` will set it to `True` if successful. - # TODO `parent_namespace` is currently None, but we could do the same thing as Pydantic models: - # fetch the parent ns using `parent_frame_namespace` (if the dataclass was defined in a function), - # and possibly cache it (see the `__pydantic_parent_namespace__` logic for models). - _pydantic_dataclasses.complete_dataclass(cls, config_wrapper, raise_errors=False) + pydantic_complete = _pydantic_dataclasses.complete_dataclass( + cls, config_wrapper, raise_errors=False, types_namespace=None + ) + cls.__pydantic_complete__ = pydantic_complete # type: ignore return cls - return create_dataclass if _cls is None else create_dataclass(_cls) + if _cls is None: + return create_dataclass - -def _pydantic_fields_complete(cls: type[PydanticDataclass]) -> bool: - """Return whether the fields where successfully collected (i.e. type hints were successfully resolves). - - This is a private property, not meant to be used outside Pydantic. - """ - return all(field_info._complete for field_info in cls.__pydantic_fields__.values()) + return create_dataclass(_cls) __getattr__ = getattr_migration(__name__) -if sys.version_info < (3, 11): +if (3, 8) <= sys.version_info < (3, 11): # Monkeypatch dataclasses.InitVar so that typing doesn't error if it occurs as a type when evaluating type hints # Starting in 3.11, typing.get_type_hints will not raise an error if the retrieved type hints are not callable. @@ -343,7 +257,7 @@ def rebuild_dataclass( force: bool = False, raise_errors: bool = True, _parent_namespace_depth: int = 2, - _types_namespace: MappingNamespace | None = None, + _types_namespace: dict[str, Any] | None = None, ) -> bool | None: """Try to rebuild the pydantic-core schema for the dataclass. @@ -353,8 +267,8 @@ def rebuild_dataclass( This is analogous to `BaseModel.model_rebuild`. Args: - cls: The class to rebuild the pydantic-core schema for. - force: Whether to force the rebuilding of the schema, defaults to `False`. + cls: The class to build the dataclass core schema for. + force: Whether to force the rebuilding of the model schema, defaults to `False`. raise_errors: Whether to raise errors, defaults to `True`. _parent_namespace_depth: The depth level of the parent namespace, defaults to 2. _types_namespace: The types namespace, defaults to `None`. @@ -365,49 +279,34 @@ def rebuild_dataclass( """ if not force and cls.__pydantic_complete__: return None - - for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): - if attr in cls.__dict__ and not isinstance(getattr(cls, attr), _mock_val_ser.MockValSer): - # Deleting the validator/serializer is necessary as otherwise they can get reused in - # pydantic-core. Same applies for the core schema that can be reused in schema generation. - delattr(cls, attr) - - cls.__pydantic_complete__ = False - - if _types_namespace is not None: - rebuild_ns = _types_namespace - elif _parent_namespace_depth > 0: - rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} else: - rebuild_ns = {} + if _types_namespace is not None: + types_namespace: dict[str, Any] | None = _types_namespace.copy() + else: + if _parent_namespace_depth > 0: + frame_parent_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth) or {} + # Note: we may need to add something similar to cls.__pydantic_parent_namespace__ from BaseModel + # here when implementing handling of recursive generics. See BaseModel.model_rebuild for reference. + types_namespace = frame_parent_ns + else: + types_namespace = {} - ns_resolver = _namespace_utils.NsResolver( - parent_namespace=rebuild_ns, - ) - - return _pydantic_dataclasses.complete_dataclass( - cls, - _config.ConfigWrapper(cls.__pydantic_config__, check=False), - raise_errors=raise_errors, - ns_resolver=ns_resolver, - # We could provide a different config instead (with `'defer_build'` set to `True`) - # of this explicit `_force_build` argument, but because config can come from the - # decorator parameter or the `__pydantic_config__` attribute, `complete_dataclass` - # will overwrite `__pydantic_config__` with the provided config above: - _force_build=True, - ) + types_namespace = _typing_extra.get_cls_types_namespace(cls, types_namespace) + return _pydantic_dataclasses.complete_dataclass( + cls, + _config.ConfigWrapper(cls.__pydantic_config__, check=False), + raise_errors=raise_errors, + types_namespace=types_namespace, + ) -def is_pydantic_dataclass(class_: type[Any], /) -> TypeGuard[type[PydanticDataclass]]: +def is_pydantic_dataclass(__cls: type[Any]) -> TypeGuard[type[PydanticDataclass]]: """Whether a class is a pydantic dataclass. Args: - class_: The class. + __cls: The class. Returns: `True` if the class is a pydantic dataclass, `False` otherwise. """ - try: - return '__is_pydantic_dataclass__' in class_.__dict__ and dataclasses.is_dataclass(class_) - except AttributeError: - return False + return dataclasses.is_dataclass(__cls) and '__pydantic_validator__' in __cls.__dict__ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/datetime_parse.py b/Backend/venv/lib/python3.12/site-packages/pydantic/datetime_parse.py index 53d52649..902219df 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/datetime_parse.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/datetime_parse.py @@ -1,5 +1,4 @@ """The `datetime_parse` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/decorator.py b/Backend/venv/lib/python3.12/site-packages/pydantic/decorator.py index 0d97560c..c3643468 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/decorator.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/decorator.py @@ -1,5 +1,4 @@ """The `decorator` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc index a56b38ed..c0e0f93d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc index f8de8e72..3a32cfd8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc index 8b36f9fd..8b5e6203 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc index 7f043c31..9e952ca2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc index c50c5d5e..c03bb26f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc index 001d8af6..5cae3f4a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc index f3d1b596..88c353d0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc index 3bed269b..1e49cf19 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/class_validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/class_validators.py index f1a331dd..43db6e26 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/class_validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/class_validators.py @@ -4,10 +4,10 @@ from __future__ import annotations as _annotations from functools import partial, partialmethod from types import FunctionType -from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar, Union, overload +from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, overload from warnings import warn -from typing_extensions import Protocol, TypeAlias, deprecated +from typing_extensions import Literal, Protocol, TypeAlias from .._internal import _decorators, _decorators_v1 from ..errors import PydanticUserError @@ -19,24 +19,30 @@ _ALLOW_REUSE_WARNING_MESSAGE = '`allow_reuse` is deprecated and will be ignored; if TYPE_CHECKING: class _OnlyValueValidatorClsMethod(Protocol): - def __call__(self, __cls: Any, __value: Any) -> Any: ... + def __call__(self, __cls: Any, __value: Any) -> Any: + ... class _V1ValidatorWithValuesClsMethod(Protocol): - def __call__(self, __cls: Any, __value: Any, values: dict[str, Any]) -> Any: ... + def __call__(self, __cls: Any, __value: Any, values: dict[str, Any]) -> Any: + ... class _V1ValidatorWithValuesKwOnlyClsMethod(Protocol): - def __call__(self, __cls: Any, __value: Any, *, values: dict[str, Any]) -> Any: ... + def __call__(self, __cls: Any, __value: Any, *, values: dict[str, Any]) -> Any: + ... class _V1ValidatorWithKwargsClsMethod(Protocol): - def __call__(self, __cls: Any, **kwargs: Any) -> Any: ... + def __call__(self, __cls: Any, **kwargs: Any) -> Any: + ... class _V1ValidatorWithValuesAndKwargsClsMethod(Protocol): - def __call__(self, __cls: Any, values: dict[str, Any], **kwargs: Any) -> Any: ... + def __call__(self, __cls: Any, values: dict[str, Any], **kwargs: Any) -> Any: + ... class _V1RootValidatorClsMethod(Protocol): def __call__( self, __cls: Any, __values: _decorators_v1.RootValidatorValues - ) -> _decorators_v1.RootValidatorValues: ... + ) -> _decorators_v1.RootValidatorValues: + ... V1Validator = Union[ _OnlyValueValidatorClsMethod, @@ -73,12 +79,6 @@ else: DeprecationWarning = PydanticDeprecatedSince20 -@deprecated( - 'Pydantic V1 style `@validator` validators are deprecated.' - ' You should migrate to Pydantic V2 style `@field_validator` validators,' - ' see the migration guide for more details', - category=None, -) def validator( __field: str, *fields: str, @@ -94,7 +94,7 @@ def validator( __field (str): The first field the validator should be called on; this is separate from `fields` to ensure an error is raised if you don't pass at least one. *fields (str): Additional field(s) the validator should be called on. - pre (bool, optional): Whether this validator should be called before the standard + pre (bool, optional): Whether or not this validator should be called before the standard validators (else after). Defaults to False. each_item (bool, optional): For complex objects (sets, lists etc.) whether to validate individual elements rather than the whole object. Defaults to False. @@ -109,17 +109,9 @@ def validator( Callable: A decorator that can be used to decorate a function to be used as a validator. """ - warn( - 'Pydantic V1 style `@validator` validators are deprecated.' - ' You should migrate to Pydantic V2 style `@field_validator` validators,' - ' see the migration guide for more details', - DeprecationWarning, - stacklevel=2, - ) - if allow_reuse is True: # pragma: no cover - warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning, stacklevel=2) - fields = __field, *fields + warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning) + fields = tuple((__field, *fields)) if isinstance(fields[0], FunctionType): raise PydanticUserError( '`@validator` should be used with fields and keyword arguments, not bare. ' @@ -133,6 +125,14 @@ def validator( code='validator-invalid-fields', ) + warn( + 'Pydantic V1 style `@validator` validators are deprecated.' + ' You should migrate to Pydantic V2 style `@field_validator` validators,' + ' see the migration guide for more details', + DeprecationWarning, + stacklevel=2, + ) + mode: Literal['before', 'after'] = 'before' if pre is True else 'after' def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]: @@ -165,7 +165,8 @@ def root_validator( ) -> Callable[ [_V1RootValidatorFunctionType], _V1RootValidatorFunctionType, -]: ... +]: + ... @overload @@ -178,7 +179,8 @@ def root_validator( ) -> Callable[ [_V1RootValidatorFunctionType], _V1RootValidatorFunctionType, -]: ... +]: + ... @overload @@ -192,15 +194,10 @@ def root_validator( ) -> Callable[ [_V1RootValidatorFunctionType], _V1RootValidatorFunctionType, -]: ... +]: + ... -@deprecated( - 'Pydantic V1 style `@root_validator` validators are deprecated.' - ' You should migrate to Pydantic V2 style `@model_validator` validators,' - ' see the migration guide for more details', - category=None, -) def root_validator( *__args, pre: bool = False, @@ -234,7 +231,7 @@ def root_validator( return root_validator()(*__args) # type: ignore if allow_reuse is True: # pragma: no cover - warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning, stacklevel=2) + warn(_ALLOW_REUSE_WARNING_MESSAGE, DeprecationWarning) mode: Literal['before', 'after'] = 'before' if pre is True else 'after' if pre is False and skip_on_failure is not True: raise PydanticUserError( diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/config.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/config.py index bd4692ac..7409847b 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/config.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/config.py @@ -1,9 +1,9 @@ from __future__ import annotations as _annotations import warnings -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any -from typing_extensions import deprecated +from typing_extensions import Literal, deprecated from .._internal import _config from ..warnings import PydanticDeprecatedSince20 @@ -18,10 +18,10 @@ __all__ = 'BaseConfig', 'Extra' class _ConfigMetaclass(type): def __getattr__(self, item: str) -> Any: + warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) + try: - obj = _config.config_defaults[item] - warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) - return obj + return _config.config_defaults[item] except KeyError as exc: raise AttributeError(f"type object '{self.__name__}' has no attribute {exc}") from exc @@ -35,10 +35,9 @@ class BaseConfig(metaclass=_ConfigMetaclass): """ def __getattr__(self, item: str) -> Any: + warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) try: - obj = super().__getattribute__(item) - warnings.warn(_config.DEPRECATION_MESSAGE, DeprecationWarning) - return obj + return super().__getattribute__(item) except AttributeError as exc: try: return getattr(type(self), item) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/copy_internals.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/copy_internals.py index 0170dc08..efe5de28 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/copy_internals.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/copy_internals.py @@ -3,7 +3,7 @@ from __future__ import annotations as _annotations import typing from copy import deepcopy from enum import Enum -from typing import Any +from typing import Any, Tuple import typing_extensions @@ -18,7 +18,7 @@ if typing.TYPE_CHECKING: from .._internal._utils import AbstractSetIntStr, MappingIntStrAny AnyClassMethod = classmethod[Any, Any, Any] - TupleGenerator = typing.Generator[tuple[str, Any], None, None] + TupleGenerator = typing.Generator[Tuple[str, Any], None, None] Model = typing.TypeVar('Model', bound='BaseModel') # should be `set[int] | set[str] | dict[int, IncEx] | dict[str, IncEx] | None`, but mypy can't cope IncEx: typing_extensions.TypeAlias = 'set[int] | set[str] | dict[int, Any] | dict[str, Any] | None' @@ -40,11 +40,11 @@ def _iter( # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case. if exclude is not None: exclude = _utils.ValueItems.merge( - {k: v.exclude for k, v in self.__pydantic_fields__.items() if v.exclude is not None}, exclude + {k: v.exclude for k, v in self.model_fields.items() if v.exclude is not None}, exclude ) if include is not None: - include = _utils.ValueItems.merge(dict.fromkeys(self.__pydantic_fields__, True), include, intersect=True) + include = _utils.ValueItems.merge({k: True for k in self.model_fields}, include, intersect=True) allowed_keys = _calculate_keys(self, include=include, exclude=exclude, exclude_unset=exclude_unset) # type: ignore if allowed_keys is None and not (to_dict or by_alias or exclude_unset or exclude_defaults or exclude_none): @@ -68,15 +68,15 @@ def _iter( if exclude_defaults: try: - field = self.__pydantic_fields__[field_key] + field = self.model_fields[field_key] except KeyError: pass else: if not field.is_required() and field.default == v: continue - if by_alias and field_key in self.__pydantic_fields__: - dict_key = self.__pydantic_fields__[field_key].alias or field_key + if by_alias and field_key in self.model_fields: + dict_key = self.model_fields[field_key].alias or field_key else: dict_key = field_key @@ -200,7 +200,7 @@ def _calculate_keys( include: MappingIntStrAny | None, exclude: MappingIntStrAny | None, exclude_unset: bool, - update: dict[str, Any] | None = None, # noqa UP006 + update: typing.Dict[str, Any] | None = None, # noqa UP006 ) -> typing.AbstractSet[str] | None: if include is None and exclude is None and exclude_unset is False: return None diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/decorator.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/decorator.py index e73ad209..11244ba1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/decorator.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/decorator.py @@ -1,7 +1,6 @@ import warnings -from collections.abc import Mapping from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union, overload +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, TypeVar, Union, overload from typing_extensions import deprecated @@ -23,29 +22,29 @@ if TYPE_CHECKING: AnyCallable = Callable[..., Any] AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable) - ConfigType = Union[None, type[Any], dict[str, Any]] + ConfigType = Union[None, Type[Any], Dict[str, Any]] @overload -def validate_arguments( - func: None = None, *, config: 'ConfigType' = None -) -> Callable[['AnyCallableT'], 'AnyCallableT']: ... - - -@overload -def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT': ... - - @deprecated( - 'The `validate_arguments` method is deprecated; use `validate_call` instead.', - category=None, + 'The `validate_arguments` method is deprecated; use `validate_call` instead.', category=PydanticDeprecatedSince20 ) +def validate_arguments(func: None = None, *, config: 'ConfigType' = None) -> Callable[['AnyCallableT'], 'AnyCallableT']: + ... + + +@overload +@deprecated( + 'The `validate_arguments` method is deprecated; use `validate_call` instead.', category=PydanticDeprecatedSince20 +) +def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT': + ... + + def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'ConfigType' = None) -> Any: """Decorator to validate the arguments passed to a function.""" warnings.warn( - 'The `validate_arguments` method is deprecated; use `validate_call` instead.', - PydanticDeprecatedSince20, - stacklevel=2, + 'The `validate_arguments` method is deprecated; use `validate_call` instead.', DeprecationWarning, stacklevel=2 ) def validate(_func: 'AnyCallable') -> 'AnyCallable': @@ -87,7 +86,7 @@ class ValidatedFunction: ) self.raw_function = function - self.arg_mapping: dict[int, str] = {} + self.arg_mapping: Dict[int, str] = {} self.positional_only_args: set[str] = set() self.v_args_name = 'args' self.v_kwargs_name = 'kwargs' @@ -95,7 +94,7 @@ class ValidatedFunction: type_hints = _typing_extra.get_type_hints(function, include_extras=True) takes_args = False takes_kwargs = False - fields: dict[str, tuple[Any, Any]] = {} + fields: Dict[str, Tuple[Any, Any]] = {} for i, (name, p) in enumerate(parameters.items()): if p.annotation is p.empty: annotation = Any @@ -106,22 +105,22 @@ class ValidatedFunction: if p.kind == Parameter.POSITIONAL_ONLY: self.arg_mapping[i] = name fields[name] = annotation, default - fields[V_POSITIONAL_ONLY_NAME] = list[str], None + fields[V_POSITIONAL_ONLY_NAME] = List[str], None self.positional_only_args.add(name) elif p.kind == Parameter.POSITIONAL_OR_KEYWORD: self.arg_mapping[i] = name fields[name] = annotation, default - fields[V_DUPLICATE_KWARGS] = list[str], None + fields[V_DUPLICATE_KWARGS] = List[str], None elif p.kind == Parameter.KEYWORD_ONLY: fields[name] = annotation, default elif p.kind == Parameter.VAR_POSITIONAL: self.v_args_name = name - fields[name] = tuple[annotation, ...], None + fields[name] = Tuple[annotation, ...], None takes_args = True else: assert p.kind == Parameter.VAR_KEYWORD, p.kind self.v_kwargs_name = name - fields[name] = dict[str, annotation], None + fields[name] = Dict[str, annotation], None takes_kwargs = True # these checks avoid a clash between "args" and a field with that name @@ -134,11 +133,11 @@ class ValidatedFunction: if not takes_args: # we add the field so validation below can raise the correct exception - fields[self.v_args_name] = list[Any], None + fields[self.v_args_name] = List[Any], None if not takes_kwargs: # same with kwargs - fields[self.v_kwargs_name] = dict[Any, Any], None + fields[self.v_kwargs_name] = Dict[Any, Any], None self.create_model(fields, takes_args, takes_kwargs, config) @@ -150,8 +149,8 @@ class ValidatedFunction: m = self.init_model_instance(*args, **kwargs) return self.execute(m) - def build_values(self, args: tuple[Any, ...], kwargs: dict[str, Any]) -> dict[str, Any]: - values: dict[str, Any] = {} + def build_values(self, args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Dict[str, Any]: + values: Dict[str, Any] = {} if args: arg_iter = enumerate(args) while True: @@ -166,15 +165,15 @@ class ValidatedFunction: values[self.v_args_name] = [a] + [a for _, a in arg_iter] break - var_kwargs: dict[str, Any] = {} + var_kwargs: Dict[str, Any] = {} wrong_positional_args = [] duplicate_kwargs = [] fields_alias = [ field.alias - for name, field in self.model.__pydantic_fields__.items() + for name, field in self.model.model_fields.items() if name not in (self.v_args_name, self.v_kwargs_name) ] - non_var_fields = set(self.model.__pydantic_fields__) - {self.v_args_name, self.v_kwargs_name} + non_var_fields = set(self.model.model_fields) - {self.v_args_name, self.v_kwargs_name} for k, v in kwargs.items(): if k in non_var_fields or k in fields_alias: if k in self.positional_only_args: @@ -194,15 +193,11 @@ class ValidatedFunction: return values def execute(self, m: BaseModel) -> Any: - d = { - k: v - for k, v in m.__dict__.items() - if k in m.__pydantic_fields_set__ or m.__pydantic_fields__[k].default_factory - } + d = {k: v for k, v in m.__dict__.items() if k in m.__pydantic_fields_set__ or m.model_fields[k].default_factory} var_kwargs = d.pop(self.v_kwargs_name, {}) if self.v_args_name in d: - args_: list[Any] = [] + args_: List[Any] = [] in_kwargs = False kwargs = {} for name, value in d.items(): @@ -226,7 +221,7 @@ class ValidatedFunction: else: return self.raw_function(**d, **var_kwargs) - def create_model(self, fields: dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None: + def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None: pos_args = len(self.arg_mapping) config_wrapper = _config.ConfigWrapper(config) @@ -243,7 +238,7 @@ class ValidatedFunction: class DecoratorBaseModel(BaseModel): @field_validator(self.v_args_name, check_fields=False) @classmethod - def check_args(cls, v: Optional[list[Any]]) -> Optional[list[Any]]: + def check_args(cls, v: Optional[List[Any]]) -> Optional[List[Any]]: if takes_args or v is None: return v @@ -251,7 +246,7 @@ class ValidatedFunction: @field_validator(self.v_kwargs_name, check_fields=False) @classmethod - def check_kwargs(cls, v: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]: + def check_kwargs(cls, v: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: if takes_kwargs or v is None: return v @@ -261,7 +256,7 @@ class ValidatedFunction: @field_validator(V_POSITIONAL_ONLY_NAME, check_fields=False) @classmethod - def check_positional_only(cls, v: Optional[list[str]]) -> None: + def check_positional_only(cls, v: Optional[List[str]]) -> None: if v is None: return @@ -271,7 +266,7 @@ class ValidatedFunction: @field_validator(V_DUPLICATE_KWARGS, check_fields=False) @classmethod - def check_duplicate_kwargs(cls, v: Optional[list[str]]) -> None: + def check_duplicate_kwargs(cls, v: Optional[List[str]]) -> None: if v is None: return diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/json.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/json.py index 1e216a76..d0673532 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/json.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/json.py @@ -7,12 +7,11 @@ from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6 from pathlib import Path from re import Pattern from types import GeneratorType -from typing import TYPE_CHECKING, Any, Callable, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Type, Union from uuid import UUID from typing_extensions import deprecated -from .._internal._import_utils import import_cached_base_model from ..color import Color from ..networks import NameEmail from ..types import SecretBytes, SecretStr @@ -51,7 +50,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: return float(dec_value) -ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { +ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, datetime.date: isoformat, @@ -80,23 +79,18 @@ ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { @deprecated( - '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.', - category=None, + 'pydantic_encoder is deprecated, use pydantic_core.to_jsonable_python instead.', category=PydanticDeprecatedSince20 ) def pydantic_encoder(obj: Any) -> Any: - warnings.warn( - '`pydantic_encoder` is deprecated, use `pydantic_core.to_jsonable_python` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) from dataclasses import asdict, is_dataclass - BaseModel = import_cached_base_model() + from ..main import BaseModel + warnings.warn('pydantic_encoder is deprecated, use BaseModel.model_dump instead.', DeprecationWarning, stacklevel=2) if isinstance(obj, BaseModel): return obj.model_dump() elif is_dataclass(obj): - return asdict(obj) # type: ignore + return asdict(obj) # Check the class type and its superclasses for a matching encoder for base in obj.__class__.__mro__[:-1]: @@ -110,17 +104,12 @@ def pydantic_encoder(obj: Any) -> Any: # TODO: Add a suggested migration path once there is a way to use custom encoders -@deprecated( - '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.', - category=None, -) -def custom_pydantic_encoder(type_encoders: dict[Any, Callable[[type[Any]], Any]], obj: Any) -> Any: - warnings.warn( - '`custom_pydantic_encoder` is deprecated, use `BaseModel.model_dump` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) +@deprecated('custom_pydantic_encoder is deprecated.', category=PydanticDeprecatedSince20) +def custom_pydantic_encoder(type_encoders: Dict[Any, Callable[[Type[Any]], Any]], obj: Any) -> Any: # Check the class type and its superclasses for a matching encoder + warnings.warn( + 'custom_pydantic_encoder is deprecated, use BaseModel.model_dump instead.', DeprecationWarning, stacklevel=2 + ) for base in obj.__class__.__mro__[:-1]: try: encoder = type_encoders[base] @@ -132,10 +121,10 @@ def custom_pydantic_encoder(type_encoders: dict[Any, Callable[[type[Any]], Any]] return pydantic_encoder(obj) -@deprecated('`timedelta_isoformat` is deprecated.', category=None) +@deprecated('timedelta_isoformat is deprecated.', category=PydanticDeprecatedSince20) def timedelta_isoformat(td: datetime.timedelta) -> str: """ISO 8601 encoding for Python timedelta object.""" - warnings.warn('`timedelta_isoformat` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + warnings.warn('timedelta_isoformat is deprecated.', DeprecationWarning, stacklevel=2) minutes, seconds = divmod(td.seconds, 60) hours, minutes = divmod(minutes, 60) return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S' diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/parse.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/parse.py index 2a92e62b..126d6aae 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/parse.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/parse.py @@ -22,7 +22,7 @@ class Protocol(str, Enum): pickle = 'pickle' -@deprecated('`load_str_bytes` is deprecated.', category=None) +@deprecated('load_str_bytes is deprecated.', category=PydanticDeprecatedSince20) def load_str_bytes( b: str | bytes, *, @@ -32,7 +32,7 @@ def load_str_bytes( allow_pickle: bool = False, json_loads: Callable[[str], Any] = json.loads, ) -> Any: - warnings.warn('`load_str_bytes` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + warnings.warn('load_str_bytes is deprecated.', DeprecationWarning, stacklevel=2) if proto is None and content_type: if content_type.endswith(('json', 'javascript')): pass @@ -56,7 +56,7 @@ def load_str_bytes( raise TypeError(f'Unknown protocol: {proto}') -@deprecated('`load_file` is deprecated.', category=None) +@deprecated('load_file is deprecated.', category=PydanticDeprecatedSince20) def load_file( path: str | Path, *, @@ -66,7 +66,7 @@ def load_file( allow_pickle: bool = False, json_loads: Callable[[str], Any] = json.loads, ) -> Any: - warnings.warn('`load_file` is deprecated.', category=PydanticDeprecatedSince20, stacklevel=2) + warnings.warn('load_file is deprecated.', DeprecationWarning, stacklevel=2) path = Path(path) b = path.read_bytes() if content_type is None: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/tools.py b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/tools.py index 5ad7faef..2b05d38e 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/tools.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/deprecated/tools.py @@ -2,7 +2,7 @@ from __future__ import annotations import json import warnings -from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, Type, TypeVar, Union from typing_extensions import deprecated @@ -17,20 +17,19 @@ if not TYPE_CHECKING: __all__ = 'parse_obj_as', 'schema_of', 'schema_json_of' -NameFactory = Union[str, Callable[[type[Any]], str]] +NameFactory = Union[str, Callable[[Type[Any]], str]] T = TypeVar('T') @deprecated( - '`parse_obj_as` is deprecated. Use `pydantic.TypeAdapter.validate_python` instead.', - category=None, + 'parse_obj_as is deprecated. Use pydantic.TypeAdapter.validate_python instead.', category=PydanticDeprecatedSince20 ) def parse_obj_as(type_: type[T], obj: Any, type_name: NameFactory | None = None) -> T: warnings.warn( - '`parse_obj_as` is deprecated. Use `pydantic.TypeAdapter.validate_python` instead.', - category=PydanticDeprecatedSince20, + 'parse_obj_as is deprecated. Use pydantic.TypeAdapter.validate_python instead.', + DeprecationWarning, stacklevel=2, ) if type_name is not None: # pragma: no cover @@ -43,8 +42,7 @@ def parse_obj_as(type_: type[T], obj: Any, type_name: NameFactory | None = None) @deprecated( - '`schema_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', - category=None, + 'schema_of is deprecated. Use pydantic.TypeAdapter.json_schema instead.', category=PydanticDeprecatedSince20 ) def schema_of( type_: Any, @@ -56,9 +54,7 @@ def schema_of( ) -> dict[str, Any]: """Generate a JSON schema (as dict) for the passed model or dynamically generated one.""" warnings.warn( - '`schema_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'schema_of is deprecated. Use pydantic.TypeAdapter.json_schema instead.', DeprecationWarning, stacklevel=2 ) res = TypeAdapter(type_).json_schema( by_alias=by_alias, @@ -79,8 +75,7 @@ def schema_of( @deprecated( - '`schema_json_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', - category=None, + 'schema_json_of is deprecated. Use pydantic.TypeAdapter.json_schema instead.', category=PydanticDeprecatedSince20 ) def schema_json_of( type_: Any, @@ -93,9 +88,7 @@ def schema_json_of( ) -> str: """Generate a JSON schema (as JSON) for the passed model or dynamically generated one.""" warnings.warn( - '`schema_json_of` is deprecated. Use `pydantic.TypeAdapter.json_schema` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'schema_json_of is deprecated. Use pydantic.TypeAdapter.json_schema instead.', DeprecationWarning, stacklevel=2 ) return json.dumps( schema_of(type_, title=title, by_alias=by_alias, ref_template=ref_template, schema_generator=schema_generator), diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/env_settings.py b/Backend/venv/lib/python3.12/site-packages/pydantic/env_settings.py index cd0b04e6..662f5900 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/env_settings.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/env_settings.py @@ -1,5 +1,4 @@ """The `env_settings` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/error_wrappers.py b/Backend/venv/lib/python3.12/site-packages/pydantic/error_wrappers.py index 2985419a..5144eeee 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/error_wrappers.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/error_wrappers.py @@ -1,5 +1,4 @@ """The `error_wrappers` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/errors.py b/Backend/venv/lib/python3.12/site-packages/pydantic/errors.py index f2270682..6e6b3d28 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/errors.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/errors.py @@ -1,14 +1,9 @@ """Pydantic-specific errors.""" - from __future__ import annotations as _annotations import re -from typing import Any, ClassVar, Literal -from typing_extensions import Self -from typing_inspection.introspection import Qualifier - -from pydantic._internal import _repr +from typing_extensions import Literal, Self from ._migration import getattr_migration from .version import version_short @@ -19,7 +14,6 @@ __all__ = ( 'PydanticImportError', 'PydanticSchemaGenerationError', 'PydanticInvalidForJsonSchema', - 'PydanticForbiddenQualifier', 'PydanticErrorCodes', ) @@ -42,7 +36,6 @@ PydanticErrorCodes = Literal[ 'model-field-missing-annotation', 'config-both', 'removed-kwargs', - 'circular-reference-schema', 'invalid-for-json-schema', 'json-schema-already-used', 'base-model-instantiated', @@ -50,10 +43,10 @@ PydanticErrorCodes = Literal[ 'schema-for-unknown-type', 'import-error', 'create-model-field-definitions', + 'create-model-config-base', 'validator-no-fields', 'validator-invalid-fields', 'validator-instance-method', - 'validator-input-type', 'root-validator-pre-skip', 'model-serializer-instance-method', 'validator-field-config-info', @@ -62,20 +55,9 @@ PydanticErrorCodes = Literal[ 'field-serializer-signature', 'model-serializer-signature', 'multiple-field-serializers', - 'invalid-annotated-type', + 'invalid_annotated_type', 'type-adapter-config-unused', 'root-model-extra', - 'unevaluable-type-annotation', - 'dataclass-init-false-extra-allow', - 'clashing-init-and-init-var', - 'model-config-invalid-field-name', - 'with-config-on-model', - 'dataclass-on-model', - 'validate-call-type', - 'unpack-typed-dict', - 'overlapping-unpack-typed-dict', - 'invalid-self-type', - 'validate-by-alias-and-name-false', ] @@ -164,26 +146,4 @@ class PydanticInvalidForJsonSchema(PydanticUserError): super().__init__(message, code='invalid-for-json-schema') -class PydanticForbiddenQualifier(PydanticUserError): - """An error raised if a forbidden type qualifier is found in a type annotation.""" - - _qualifier_repr_map: ClassVar[dict[Qualifier, str]] = { - 'required': 'typing.Required', - 'not_required': 'typing.NotRequired', - 'read_only': 'typing.ReadOnly', - 'class_var': 'typing.ClassVar', - 'init_var': 'dataclasses.InitVar', - 'final': 'typing.Final', - } - - def __init__(self, qualifier: Qualifier, annotation: Any) -> None: - super().__init__( - message=( - f'The annotation {_repr.display_as_type(annotation)!r} contains the {self._qualifier_repr_map[qualifier]!r} ' - f'type qualifier, which is invalid in the context it is defined.' - ), - code=None, - ) - - __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__init__.py b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__init__.py deleted file mode 100644 index 5b5add10..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The "experimental" module of pydantic contains potential new features that are subject to change.""" diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e2aa759f..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc deleted file mode 100644 index 05df5ddb..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/missing_sentinel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/missing_sentinel.cpython-312.pyc deleted file mode 100644 index 19a5a382..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/missing_sentinel.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/pipeline.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/pipeline.cpython-312.pyc deleted file mode 100644 index 6cb30045..00000000 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/pipeline.cpython-312.pyc and /dev/null differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/arguments_schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/arguments_schema.py deleted file mode 100644 index af4a8f3b..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/arguments_schema.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Experimental module exposing a function to generate a core schema that validates callable arguments.""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import Any, Literal - -from pydantic_core import CoreSchema - -from pydantic import ConfigDict -from pydantic._internal import _config, _generate_schema, _namespace_utils - - -def generate_arguments_schema( - func: Callable[..., Any], - schema_type: Literal['arguments', 'arguments-v3'] = 'arguments-v3', - parameters_callback: Callable[[int, str, Any], Literal['skip'] | None] | None = None, - config: ConfigDict | None = None, -) -> CoreSchema: - """Generate the schema for the arguments of a function. - - Args: - func: The function to generate the schema for. - schema_type: The type of schema to generate. - parameters_callback: A callable that will be invoked for each parameter. The callback - should take three required arguments: the index, the name and the type annotation - (or [`Parameter.empty`][inspect.Parameter.empty] if not annotated) of the parameter. - The callback can optionally return `'skip'`, so that the parameter gets excluded - from the resulting schema. - config: The configuration to use. - - Returns: - The generated schema. - """ - generate_schema = _generate_schema.GenerateSchema( - _config.ConfigWrapper(config), - ns_resolver=_namespace_utils.NsResolver(namespaces_tuple=_namespace_utils.ns_for_function(func)), - ) - - if schema_type == 'arguments': - schema = generate_schema._arguments_schema(func, parameters_callback) # pyright: ignore[reportArgumentType] - else: - schema = generate_schema._arguments_v3_schema(func, parameters_callback) # pyright: ignore[reportArgumentType] - return generate_schema.clean_schema(schema) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/missing_sentinel.py b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/missing_sentinel.py deleted file mode 100644 index 3e7f820c..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/missing_sentinel.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Experimental module exposing a function a `MISSING` sentinel.""" - -from pydantic_core import MISSING - -__all__ = ('MISSING',) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/pipeline.py b/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/pipeline.py deleted file mode 100644 index 633fb00a..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/experimental/pipeline.py +++ /dev/null @@ -1,654 +0,0 @@ -"""Experimental pipeline API functionality. Be careful with this API, it's subject to change.""" - -from __future__ import annotations - -import datetime -import operator -import re -import sys -from collections import deque -from collections.abc import Container -from dataclasses import dataclass -from decimal import Decimal -from functools import cached_property, partial -from re import Pattern -from typing import TYPE_CHECKING, Annotated, Any, Callable, Generic, Protocol, TypeVar, Union, overload - -import annotated_types - -if TYPE_CHECKING: - from pydantic import GetCoreSchemaHandler - -from pydantic_core import PydanticCustomError -from pydantic_core import core_schema as cs - -from pydantic import Strict -from pydantic._internal._internal_dataclass import slots_true as _slots_true - -if sys.version_info < (3, 10): - EllipsisType = type(Ellipsis) -else: - from types import EllipsisType - -__all__ = ['validate_as', 'validate_as_deferred', 'transform'] - -_slots_frozen = {**_slots_true, 'frozen': True} - - -@dataclass(**_slots_frozen) -class _ValidateAs: - tp: type[Any] - strict: bool = False - - -@dataclass -class _ValidateAsDefer: - func: Callable[[], type[Any]] - - @cached_property - def tp(self) -> type[Any]: - return self.func() - - -@dataclass(**_slots_frozen) -class _Transform: - func: Callable[[Any], Any] - - -@dataclass(**_slots_frozen) -class _PipelineOr: - left: _Pipeline[Any, Any] - right: _Pipeline[Any, Any] - - -@dataclass(**_slots_frozen) -class _PipelineAnd: - left: _Pipeline[Any, Any] - right: _Pipeline[Any, Any] - - -@dataclass(**_slots_frozen) -class _Eq: - value: Any - - -@dataclass(**_slots_frozen) -class _NotEq: - value: Any - - -@dataclass(**_slots_frozen) -class _In: - values: Container[Any] - - -@dataclass(**_slots_frozen) -class _NotIn: - values: Container[Any] - - -_ConstraintAnnotation = Union[ - annotated_types.Le, - annotated_types.Ge, - annotated_types.Lt, - annotated_types.Gt, - annotated_types.Len, - annotated_types.MultipleOf, - annotated_types.Timezone, - annotated_types.Interval, - annotated_types.Predicate, - # common predicates not included in annotated_types - _Eq, - _NotEq, - _In, - _NotIn, - # regular expressions - Pattern[str], -] - - -@dataclass(**_slots_frozen) -class _Constraint: - constraint: _ConstraintAnnotation - - -_Step = Union[_ValidateAs, _ValidateAsDefer, _Transform, _PipelineOr, _PipelineAnd, _Constraint] - -_InT = TypeVar('_InT') -_OutT = TypeVar('_OutT') -_NewOutT = TypeVar('_NewOutT') - - -class _FieldTypeMarker: - pass - - -# TODO: ultimately, make this public, see https://github.com/pydantic/pydantic/pull/9459#discussion_r1628197626 -# Also, make this frozen eventually, but that doesn't work right now because of the generic base -# Which attempts to modify __orig_base__ and such. -# We could go with a manual freeze, but that seems overkill for now. -@dataclass(**_slots_true) -class _Pipeline(Generic[_InT, _OutT]): - """Abstract representation of a chain of validation, transformation, and parsing steps.""" - - _steps: tuple[_Step, ...] - - def transform( - self, - func: Callable[[_OutT], _NewOutT], - ) -> _Pipeline[_InT, _NewOutT]: - """Transform the output of the previous step. - - If used as the first step in a pipeline, the type of the field is used. - That is, the transformation is applied to after the value is parsed to the field's type. - """ - return _Pipeline[_InT, _NewOutT](self._steps + (_Transform(func),)) - - @overload - def validate_as(self, tp: type[_NewOutT], *, strict: bool = ...) -> _Pipeline[_InT, _NewOutT]: ... - - @overload - def validate_as(self, tp: EllipsisType, *, strict: bool = ...) -> _Pipeline[_InT, Any]: # type: ignore - ... - - def validate_as(self, tp: type[_NewOutT] | EllipsisType, *, strict: bool = False) -> _Pipeline[_InT, Any]: # type: ignore - """Validate / parse the input into a new type. - - If no type is provided, the type of the field is used. - - Types are parsed in Pydantic's `lax` mode by default, - but you can enable `strict` mode by passing `strict=True`. - """ - if isinstance(tp, EllipsisType): - return _Pipeline[_InT, Any](self._steps + (_ValidateAs(_FieldTypeMarker, strict=strict),)) - return _Pipeline[_InT, _NewOutT](self._steps + (_ValidateAs(tp, strict=strict),)) - - def validate_as_deferred(self, func: Callable[[], type[_NewOutT]]) -> _Pipeline[_InT, _NewOutT]: - """Parse the input into a new type, deferring resolution of the type until the current class - is fully defined. - - This is useful when you need to reference the class in it's own type annotations. - """ - return _Pipeline[_InT, _NewOutT](self._steps + (_ValidateAsDefer(func),)) - - # constraints - @overload - def constrain(self: _Pipeline[_InT, _NewOutGe], constraint: annotated_types.Ge) -> _Pipeline[_InT, _NewOutGe]: ... - - @overload - def constrain(self: _Pipeline[_InT, _NewOutGt], constraint: annotated_types.Gt) -> _Pipeline[_InT, _NewOutGt]: ... - - @overload - def constrain(self: _Pipeline[_InT, _NewOutLe], constraint: annotated_types.Le) -> _Pipeline[_InT, _NewOutLe]: ... - - @overload - def constrain(self: _Pipeline[_InT, _NewOutLt], constraint: annotated_types.Lt) -> _Pipeline[_InT, _NewOutLt]: ... - - @overload - def constrain( - self: _Pipeline[_InT, _NewOutLen], constraint: annotated_types.Len - ) -> _Pipeline[_InT, _NewOutLen]: ... - - @overload - def constrain( - self: _Pipeline[_InT, _NewOutT], constraint: annotated_types.MultipleOf - ) -> _Pipeline[_InT, _NewOutT]: ... - - @overload - def constrain( - self: _Pipeline[_InT, _NewOutDatetime], constraint: annotated_types.Timezone - ) -> _Pipeline[_InT, _NewOutDatetime]: ... - - @overload - def constrain(self: _Pipeline[_InT, _OutT], constraint: annotated_types.Predicate) -> _Pipeline[_InT, _OutT]: ... - - @overload - def constrain( - self: _Pipeline[_InT, _NewOutInterval], constraint: annotated_types.Interval - ) -> _Pipeline[_InT, _NewOutInterval]: ... - - @overload - def constrain(self: _Pipeline[_InT, _OutT], constraint: _Eq) -> _Pipeline[_InT, _OutT]: ... - - @overload - def constrain(self: _Pipeline[_InT, _OutT], constraint: _NotEq) -> _Pipeline[_InT, _OutT]: ... - - @overload - def constrain(self: _Pipeline[_InT, _OutT], constraint: _In) -> _Pipeline[_InT, _OutT]: ... - - @overload - def constrain(self: _Pipeline[_InT, _OutT], constraint: _NotIn) -> _Pipeline[_InT, _OutT]: ... - - @overload - def constrain(self: _Pipeline[_InT, _NewOutT], constraint: Pattern[str]) -> _Pipeline[_InT, _NewOutT]: ... - - def constrain(self, constraint: _ConstraintAnnotation) -> Any: - """Constrain a value to meet a certain condition. - - We support most conditions from `annotated_types`, as well as regular expressions. - - Most of the time you'll be calling a shortcut method like `gt`, `lt`, `len`, etc - so you don't need to call this directly. - """ - return _Pipeline[_InT, _OutT](self._steps + (_Constraint(constraint),)) - - def predicate(self: _Pipeline[_InT, _NewOutT], func: Callable[[_NewOutT], bool]) -> _Pipeline[_InT, _NewOutT]: - """Constrain a value to meet a certain predicate.""" - return self.constrain(annotated_types.Predicate(func)) - - def gt(self: _Pipeline[_InT, _NewOutGt], gt: _NewOutGt) -> _Pipeline[_InT, _NewOutGt]: - """Constrain a value to be greater than a certain value.""" - return self.constrain(annotated_types.Gt(gt)) - - def lt(self: _Pipeline[_InT, _NewOutLt], lt: _NewOutLt) -> _Pipeline[_InT, _NewOutLt]: - """Constrain a value to be less than a certain value.""" - return self.constrain(annotated_types.Lt(lt)) - - def ge(self: _Pipeline[_InT, _NewOutGe], ge: _NewOutGe) -> _Pipeline[_InT, _NewOutGe]: - """Constrain a value to be greater than or equal to a certain value.""" - return self.constrain(annotated_types.Ge(ge)) - - def le(self: _Pipeline[_InT, _NewOutLe], le: _NewOutLe) -> _Pipeline[_InT, _NewOutLe]: - """Constrain a value to be less than or equal to a certain value.""" - return self.constrain(annotated_types.Le(le)) - - def len(self: _Pipeline[_InT, _NewOutLen], min_len: int, max_len: int | None = None) -> _Pipeline[_InT, _NewOutLen]: - """Constrain a value to have a certain length.""" - return self.constrain(annotated_types.Len(min_len, max_len)) - - @overload - def multiple_of(self: _Pipeline[_InT, _NewOutDiv], multiple_of: _NewOutDiv) -> _Pipeline[_InT, _NewOutDiv]: ... - - @overload - def multiple_of(self: _Pipeline[_InT, _NewOutMod], multiple_of: _NewOutMod) -> _Pipeline[_InT, _NewOutMod]: ... - - def multiple_of(self: _Pipeline[_InT, Any], multiple_of: Any) -> _Pipeline[_InT, Any]: - """Constrain a value to be a multiple of a certain number.""" - return self.constrain(annotated_types.MultipleOf(multiple_of)) - - def eq(self: _Pipeline[_InT, _OutT], value: _OutT) -> _Pipeline[_InT, _OutT]: - """Constrain a value to be equal to a certain value.""" - return self.constrain(_Eq(value)) - - def not_eq(self: _Pipeline[_InT, _OutT], value: _OutT) -> _Pipeline[_InT, _OutT]: - """Constrain a value to not be equal to a certain value.""" - return self.constrain(_NotEq(value)) - - def in_(self: _Pipeline[_InT, _OutT], values: Container[_OutT]) -> _Pipeline[_InT, _OutT]: - """Constrain a value to be in a certain set.""" - return self.constrain(_In(values)) - - def not_in(self: _Pipeline[_InT, _OutT], values: Container[_OutT]) -> _Pipeline[_InT, _OutT]: - """Constrain a value to not be in a certain set.""" - return self.constrain(_NotIn(values)) - - # timezone methods - def datetime_tz_naive(self: _Pipeline[_InT, datetime.datetime]) -> _Pipeline[_InT, datetime.datetime]: - return self.constrain(annotated_types.Timezone(None)) - - def datetime_tz_aware(self: _Pipeline[_InT, datetime.datetime]) -> _Pipeline[_InT, datetime.datetime]: - return self.constrain(annotated_types.Timezone(...)) - - def datetime_tz( - self: _Pipeline[_InT, datetime.datetime], tz: datetime.tzinfo - ) -> _Pipeline[_InT, datetime.datetime]: - return self.constrain(annotated_types.Timezone(tz)) # type: ignore - - def datetime_with_tz( - self: _Pipeline[_InT, datetime.datetime], tz: datetime.tzinfo | None - ) -> _Pipeline[_InT, datetime.datetime]: - return self.transform(partial(datetime.datetime.replace, tzinfo=tz)) - - # string methods - def str_lower(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: - return self.transform(str.lower) - - def str_upper(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: - return self.transform(str.upper) - - def str_title(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: - return self.transform(str.title) - - def str_strip(self: _Pipeline[_InT, str]) -> _Pipeline[_InT, str]: - return self.transform(str.strip) - - def str_pattern(self: _Pipeline[_InT, str], pattern: str) -> _Pipeline[_InT, str]: - return self.constrain(re.compile(pattern)) - - def str_contains(self: _Pipeline[_InT, str], substring: str) -> _Pipeline[_InT, str]: - return self.predicate(lambda v: substring in v) - - def str_starts_with(self: _Pipeline[_InT, str], prefix: str) -> _Pipeline[_InT, str]: - return self.predicate(lambda v: v.startswith(prefix)) - - def str_ends_with(self: _Pipeline[_InT, str], suffix: str) -> _Pipeline[_InT, str]: - return self.predicate(lambda v: v.endswith(suffix)) - - # operators - def otherwise(self, other: _Pipeline[_OtherIn, _OtherOut]) -> _Pipeline[_InT | _OtherIn, _OutT | _OtherOut]: - """Combine two validation chains, returning the result of the first chain if it succeeds, and the second chain if it fails.""" - return _Pipeline((_PipelineOr(self, other),)) - - __or__ = otherwise - - def then(self, other: _Pipeline[_OutT, _OtherOut]) -> _Pipeline[_InT, _OtherOut]: - """Pipe the result of one validation chain into another.""" - return _Pipeline((_PipelineAnd(self, other),)) - - __and__ = then - - def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> cs.CoreSchema: - queue = deque(self._steps) - - s = None - - while queue: - step = queue.popleft() - s = _apply_step(step, s, handler, source_type) - - s = s or cs.any_schema() - return s - - def __supports_type__(self, _: _OutT) -> bool: - raise NotImplementedError - - -validate_as = _Pipeline[Any, Any](()).validate_as -validate_as_deferred = _Pipeline[Any, Any](()).validate_as_deferred -transform = _Pipeline[Any, Any]((_ValidateAs(_FieldTypeMarker),)).transform - - -def _check_func( - func: Callable[[Any], bool], predicate_err: str | Callable[[], str], s: cs.CoreSchema | None -) -> cs.CoreSchema: - def handler(v: Any) -> Any: - if func(v): - return v - raise ValueError(f'Expected {predicate_err if isinstance(predicate_err, str) else predicate_err()}') - - if s is None: - return cs.no_info_plain_validator_function(handler) - else: - return cs.no_info_after_validator_function(handler, s) - - -def _apply_step(step: _Step, s: cs.CoreSchema | None, handler: GetCoreSchemaHandler, source_type: Any) -> cs.CoreSchema: - if isinstance(step, _ValidateAs): - s = _apply_parse(s, step.tp, step.strict, handler, source_type) - elif isinstance(step, _ValidateAsDefer): - s = _apply_parse(s, step.tp, False, handler, source_type) - elif isinstance(step, _Transform): - s = _apply_transform(s, step.func, handler) - elif isinstance(step, _Constraint): - s = _apply_constraint(s, step.constraint) - elif isinstance(step, _PipelineOr): - s = cs.union_schema([handler(step.left), handler(step.right)]) - else: - assert isinstance(step, _PipelineAnd) - s = cs.chain_schema([handler(step.left), handler(step.right)]) - return s - - -def _apply_parse( - s: cs.CoreSchema | None, - tp: type[Any], - strict: bool, - handler: GetCoreSchemaHandler, - source_type: Any, -) -> cs.CoreSchema: - if tp is _FieldTypeMarker: - return cs.chain_schema([s, handler(source_type)]) if s else handler(source_type) - - if strict: - tp = Annotated[tp, Strict()] # type: ignore - - if s and s['type'] == 'any': - return handler(tp) - else: - return cs.chain_schema([s, handler(tp)]) if s else handler(tp) - - -def _apply_transform( - s: cs.CoreSchema | None, func: Callable[[Any], Any], handler: GetCoreSchemaHandler -) -> cs.CoreSchema: - if s is None: - return cs.no_info_plain_validator_function(func) - - if s['type'] == 'str': - if func is str.strip: - s = s.copy() - s['strip_whitespace'] = True - return s - elif func is str.lower: - s = s.copy() - s['to_lower'] = True - return s - elif func is str.upper: - s = s.copy() - s['to_upper'] = True - return s - - return cs.no_info_after_validator_function(func, s) - - -def _apply_constraint( # noqa: C901 - s: cs.CoreSchema | None, constraint: _ConstraintAnnotation -) -> cs.CoreSchema: - """Apply a single constraint to a schema.""" - if isinstance(constraint, annotated_types.Gt): - gt = constraint.gt - if s and s['type'] in {'int', 'float', 'decimal'}: - s = s.copy() - if s['type'] == 'int' and isinstance(gt, int): - s['gt'] = gt - elif s['type'] == 'float' and isinstance(gt, float): - s['gt'] = gt - elif s['type'] == 'decimal' and isinstance(gt, Decimal): - s['gt'] = gt - else: - - def check_gt(v: Any) -> bool: - return v > gt - - s = _check_func(check_gt, f'> {gt}', s) - elif isinstance(constraint, annotated_types.Ge): - ge = constraint.ge - if s and s['type'] in {'int', 'float', 'decimal'}: - s = s.copy() - if s['type'] == 'int' and isinstance(ge, int): - s['ge'] = ge - elif s['type'] == 'float' and isinstance(ge, float): - s['ge'] = ge - elif s['type'] == 'decimal' and isinstance(ge, Decimal): - s['ge'] = ge - - def check_ge(v: Any) -> bool: - return v >= ge - - s = _check_func(check_ge, f'>= {ge}', s) - elif isinstance(constraint, annotated_types.Lt): - lt = constraint.lt - if s and s['type'] in {'int', 'float', 'decimal'}: - s = s.copy() - if s['type'] == 'int' and isinstance(lt, int): - s['lt'] = lt - elif s['type'] == 'float' and isinstance(lt, float): - s['lt'] = lt - elif s['type'] == 'decimal' and isinstance(lt, Decimal): - s['lt'] = lt - - def check_lt(v: Any) -> bool: - return v < lt - - s = _check_func(check_lt, f'< {lt}', s) - elif isinstance(constraint, annotated_types.Le): - le = constraint.le - if s and s['type'] in {'int', 'float', 'decimal'}: - s = s.copy() - if s['type'] == 'int' and isinstance(le, int): - s['le'] = le - elif s['type'] == 'float' and isinstance(le, float): - s['le'] = le - elif s['type'] == 'decimal' and isinstance(le, Decimal): - s['le'] = le - - def check_le(v: Any) -> bool: - return v <= le - - s = _check_func(check_le, f'<= {le}', s) - elif isinstance(constraint, annotated_types.Len): - min_len = constraint.min_length - max_len = constraint.max_length - - if s and s['type'] in {'str', 'list', 'tuple', 'set', 'frozenset', 'dict'}: - assert ( - s['type'] == 'str' - or s['type'] == 'list' - or s['type'] == 'tuple' - or s['type'] == 'set' - or s['type'] == 'dict' - or s['type'] == 'frozenset' - ) - s = s.copy() - if min_len != 0: - s['min_length'] = min_len - if max_len is not None: - s['max_length'] = max_len - - def check_len(v: Any) -> bool: - if max_len is not None: - return (min_len <= len(v)) and (len(v) <= max_len) - return min_len <= len(v) - - s = _check_func(check_len, f'length >= {min_len} and length <= {max_len}', s) - elif isinstance(constraint, annotated_types.MultipleOf): - multiple_of = constraint.multiple_of - if s and s['type'] in {'int', 'float', 'decimal'}: - s = s.copy() - if s['type'] == 'int' and isinstance(multiple_of, int): - s['multiple_of'] = multiple_of - elif s['type'] == 'float' and isinstance(multiple_of, float): - s['multiple_of'] = multiple_of - elif s['type'] == 'decimal' and isinstance(multiple_of, Decimal): - s['multiple_of'] = multiple_of - - def check_multiple_of(v: Any) -> bool: - return v % multiple_of == 0 - - s = _check_func(check_multiple_of, f'% {multiple_of} == 0', s) - elif isinstance(constraint, annotated_types.Timezone): - tz = constraint.tz - - if tz is ...: - if s and s['type'] == 'datetime': - s = s.copy() - s['tz_constraint'] = 'aware' - else: - - def check_tz_aware(v: object) -> bool: - assert isinstance(v, datetime.datetime) - return v.tzinfo is not None - - s = _check_func(check_tz_aware, 'timezone aware', s) - elif tz is None: - if s and s['type'] == 'datetime': - s = s.copy() - s['tz_constraint'] = 'naive' - else: - - def check_tz_naive(v: object) -> bool: - assert isinstance(v, datetime.datetime) - return v.tzinfo is None - - s = _check_func(check_tz_naive, 'timezone naive', s) - else: - raise NotImplementedError('Constraining to a specific timezone is not yet supported') - elif isinstance(constraint, annotated_types.Interval): - if constraint.ge: - s = _apply_constraint(s, annotated_types.Ge(constraint.ge)) - if constraint.gt: - s = _apply_constraint(s, annotated_types.Gt(constraint.gt)) - if constraint.le: - s = _apply_constraint(s, annotated_types.Le(constraint.le)) - if constraint.lt: - s = _apply_constraint(s, annotated_types.Lt(constraint.lt)) - assert s is not None - elif isinstance(constraint, annotated_types.Predicate): - func = constraint.func - # Same logic as in `_known_annotated_metadata.apply_known_metadata()`: - predicate_name = f'{func.__qualname__!r} ' if hasattr(func, '__qualname__') else '' - - def predicate_func(v: Any) -> Any: - if not func(v): - raise PydanticCustomError( - 'predicate_failed', - f'Predicate {predicate_name}failed', # pyright: ignore[reportArgumentType] - ) - return v - - if s is None: - s = cs.no_info_plain_validator_function(predicate_func) - else: - s = cs.no_info_after_validator_function(predicate_func, s) - elif isinstance(constraint, _NotEq): - value = constraint.value - - def check_not_eq(v: Any) -> bool: - return operator.__ne__(v, value) - - s = _check_func(check_not_eq, f'!= {value}', s) - elif isinstance(constraint, _Eq): - value = constraint.value - - def check_eq(v: Any) -> bool: - return operator.__eq__(v, value) - - s = _check_func(check_eq, f'== {value}', s) - elif isinstance(constraint, _In): - values = constraint.values - - def check_in(v: Any) -> bool: - return operator.__contains__(values, v) - - s = _check_func(check_in, f'in {values}', s) - elif isinstance(constraint, _NotIn): - values = constraint.values - - def check_not_in(v: Any) -> bool: - return operator.__not__(operator.__contains__(values, v)) - - s = _check_func(check_not_in, f'not in {values}', s) - else: - assert isinstance(constraint, Pattern) - if s and s['type'] == 'str': - s = s.copy() - s['pattern'] = constraint.pattern - else: - - def check_pattern(v: object) -> bool: - assert isinstance(v, str) - return constraint.match(v) is not None - - s = _check_func(check_pattern, f'~ {constraint.pattern}', s) - return s - - -class _SupportsRange(annotated_types.SupportsLe, annotated_types.SupportsGe, Protocol): - pass - - -class _SupportsLen(Protocol): - def __len__(self) -> int: ... - - -_NewOutGt = TypeVar('_NewOutGt', bound=annotated_types.SupportsGt) -_NewOutGe = TypeVar('_NewOutGe', bound=annotated_types.SupportsGe) -_NewOutLt = TypeVar('_NewOutLt', bound=annotated_types.SupportsLt) -_NewOutLe = TypeVar('_NewOutLe', bound=annotated_types.SupportsLe) -_NewOutLen = TypeVar('_NewOutLen', bound=_SupportsLen) -_NewOutDiv = TypeVar('_NewOutDiv', bound=annotated_types.SupportsDiv) -_NewOutMod = TypeVar('_NewOutMod', bound=annotated_types.SupportsMod) -_NewOutDatetime = TypeVar('_NewOutDatetime', bound=datetime.datetime) -_NewOutInterval = TypeVar('_NewOutInterval', bound=_SupportsRange) -_OtherIn = TypeVar('_OtherIn') -_OtherOut = TypeVar('_OtherOut') diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/fields.py b/Backend/venv/lib/python3.12/site-packages/pydantic/fields.py index b091710a..15831b75 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/fields.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/fields.py @@ -1,92 +1,76 @@ """Defining fields on models.""" - from __future__ import annotations as _annotations import dataclasses import inspect -import re import sys -from collections.abc import Callable, Mapping +import typing from copy import copy from dataclasses import Field as DataclassField -from functools import cached_property -from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, TypeVar, cast, final, overload + +try: + from functools import cached_property # type: ignore +except ImportError: + # python 3.7 + cached_property = None +from typing import Any, ClassVar from warnings import warn import annotated_types import typing_extensions -from pydantic_core import MISSING, PydanticUndefined -from typing_extensions import Self, TypeAlias, TypedDict, Unpack, deprecated -from typing_inspection import typing_objects -from typing_inspection.introspection import UNKNOWN, AnnotationSource, ForbiddenQualifier, Qualifier, inspect_annotation +from pydantic_core import PydanticUndefined +from typing_extensions import Literal, Unpack from . import types from ._internal import _decorators, _fields, _generics, _internal_dataclass, _repr, _typing_extra, _utils -from ._internal._namespace_utils import GlobalsNamespace, MappingNamespace -from .aliases import AliasChoices, AliasGenerator, AliasPath from .config import JsonDict -from .errors import PydanticForbiddenQualifier, PydanticUserError -from .json_schema import PydanticJsonSchemaWarning +from .errors import PydanticUserError from .warnings import PydanticDeprecatedSince20 -if TYPE_CHECKING: - from ._internal._config import ConfigWrapper +if typing.TYPE_CHECKING: from ._internal._repr import ReprArgs - - -__all__ = 'Field', 'FieldInfo', 'PrivateAttr', 'computed_field' +else: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 _Unset: Any = PydanticUndefined -if sys.version_info >= (3, 13): - import warnings - Deprecated: TypeAlias = warnings.deprecated | deprecated -else: - Deprecated: TypeAlias = deprecated - - -class _FromFieldInfoInputs(TypedDict, total=False): +class _FromFieldInfoInputs(typing_extensions.TypedDict, total=False): """This class exists solely to add type checking for the `**kwargs` in `FieldInfo.from_field`.""" - # TODO PEP 747: use TypeForm: annotation: type[Any] | None - default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None + default_factory: typing.Callable[[], Any] | None alias: str | None alias_priority: int | None validation_alias: str | AliasPath | AliasChoices | None serialization_alias: str | None title: str | None - field_title_generator: Callable[[str, FieldInfo], str] | None description: str | None examples: list[Any] | None exclude: bool | None - exclude_if: Callable[[Any], bool] | None - gt: annotated_types.SupportsGt | None - ge: annotated_types.SupportsGe | None - lt: annotated_types.SupportsLt | None - le: annotated_types.SupportsLe | None + gt: float | None + ge: float | None + lt: float | None + le: float | None multiple_of: float | None strict: bool | None min_length: int | None max_length: int | None - pattern: str | re.Pattern[str] | None + pattern: str | None allow_inf_nan: bool | None max_digits: int | None decimal_places: int | None union_mode: Literal['smart', 'left_to_right'] | None discriminator: str | types.Discriminator | None - deprecated: Deprecated | str | bool | None - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None frozen: bool | None validate_default: bool | None repr: bool - init: bool | None init_var: bool | None kw_only: bool | None - coerce_numbers_to_str: bool | None - fail_fast: bool | None class _FieldInfoInputs(_FromFieldInfoInputs, total=False): @@ -95,14 +79,6 @@ class _FieldInfoInputs(_FromFieldInfoInputs, total=False): default: Any -class _FieldInfoAsDict(TypedDict, closed=True): - # TODO PEP 747: use TypeForm: - annotation: Any - metadata: list[Any] - attributes: dict[str, Any] - - -@final class FieldInfo(_repr.Representation): """This class holds information about a field. @@ -110,65 +86,47 @@ class FieldInfo(_repr.Representation): function is explicitly used. !!! warning - The `FieldInfo` class is meant to expose information about a field in a Pydantic model or dataclass. - `FieldInfo` instances shouldn't be instantiated directly, nor mutated. - - If you need to derive a new model from another one and are willing to alter `FieldInfo` instances, - refer to this [dynamic model example](../examples/dynamic_models.md). + You generally shouldn't be creating `FieldInfo` directly, you'll only need to use it when accessing + [`BaseModel`][pydantic.main.BaseModel] `.model_fields` internals. Attributes: annotation: The type annotation of the field. default: The default value of the field. - default_factory: A callable to generate the default value. The callable can either take 0 arguments - (in which case it is called as is) or a single argument containing the already validated data. + default_factory: The factory function used to construct the default for the field. alias: The alias name of the field. alias_priority: The priority of the field's alias. - validation_alias: The validation alias of the field. - serialization_alias: The serialization alias of the field. + validation_alias: The validation alias name of the field. + serialization_alias: The serialization alias name of the field. title: The title of the field. - field_title_generator: A callable that takes a field name and returns title for it. description: The description of the field. examples: List of examples of the field. exclude: Whether to exclude the field from the model serialization. - exclude_if: A callable that determines whether to exclude a field during serialization based on its value. discriminator: Field name or Discriminator for discriminating the type in a tagged union. - deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, - or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. - json_schema_extra: A dict or callable to provide extra JSON schema properties. + json_schema_extra: Dictionary of extra JSON schema properties. frozen: Whether the field is frozen. validate_default: Whether to validate the default value of the field. repr: Whether to include the field in representation of the model. - init: Whether the field should be included in the constructor of the dataclass. - init_var: Whether the field should _only_ be included in the constructor of the dataclass, and not stored. + init_var: Whether the field should be included in the constructor of the dataclass. kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass. - metadata: The metadata list. Contains all the data that isn't expressed as direct `FieldInfo` attributes, including: - - * Type-specific constraints, such as `gt` or `min_length` (these are converted to metadata classes such as `annotated_types.Gt`). - * Any other arbitrary object used within [`Annotated`][typing.Annotated] metadata - (e.g. [custom types handlers](../concepts/types.md#as-an-annotation) or any object not recognized by Pydantic). + metadata: List of metadata constraints. """ - # TODO PEP 747: use TypeForm: annotation: type[Any] | None default: Any - default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None + default_factory: typing.Callable[[], Any] | None alias: str | None alias_priority: int | None validation_alias: str | AliasPath | AliasChoices | None serialization_alias: str | None title: str | None - field_title_generator: Callable[[str, FieldInfo], str] | None description: str | None examples: list[Any] | None exclude: bool | None - exclude_if: Callable[[Any], bool] | None discriminator: str | types.Discriminator | None - deprecated: Deprecated | str | bool | None - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None frozen: bool | None validate_default: bool | None repr: bool - init: bool | None init_var: bool | None kw_only: bool | None metadata: list[Any] @@ -182,32 +140,23 @@ class FieldInfo(_repr.Representation): 'validation_alias', 'serialization_alias', 'title', - 'field_title_generator', 'description', 'examples', 'exclude', - 'exclude_if', 'discriminator', - 'deprecated', 'json_schema_extra', 'frozen', 'validate_default', 'repr', - 'init', 'init_var', 'kw_only', 'metadata', '_attributes_set', - '_qualifiers', - '_complete', - '_original_assignment', - '_original_annotation', - '_final', ) # used to convert kwargs to metadata/constraints, # None has a special meaning - these items are collected into a `PydanticGeneralMetadata` - metadata_lookup: ClassVar[dict[str, Callable[[Any], Any] | None]] = { + metadata_lookup: ClassVar[dict[str, typing.Callable[[Any], Any] | None]] = { 'strict': types.Strict, 'gt': annotated_types.Gt, 'ge': annotated_types.Ge, @@ -221,8 +170,6 @@ class FieldInfo(_repr.Representation): 'max_digits': None, 'decimal_places': None, 'union_mode': None, - 'coerce_numbers_to_str': None, - 'fail_fast': types.FailFast, } def __init__(self, **kwargs: Unpack[_FieldInfoInputs]) -> None: @@ -231,18 +178,13 @@ class FieldInfo(_repr.Representation): See the signature of `pydantic.fields.Field` for more details about the expected arguments. """ - # Tracking the explicitly set attributes is necessary to correctly merge `Field()` functions - # (e.g. with `Annotated[int, Field(alias='a'), Field(alias=None)]`, even though `None` is the default value, - # we need to track that `alias=None` was explicitly set): - self._attributes_set = {k: v for k, v in kwargs.items() if v is not _Unset and k not in self.metadata_lookup} + self._attributes_set = {k: v for k, v in kwargs.items() if v is not _Unset} kwargs = {k: _DefaultValues.get(k) if v is _Unset else v for k, v in kwargs.items()} # type: ignore - self.annotation = kwargs.get('annotation') + self.annotation, annotation_metadata = self._extract_metadata(kwargs.get('annotation')) - # Note: in theory, the second `pop()` arguments are not required below, as defaults are already set from `_DefaultsValues`. default = kwargs.pop('default', PydanticUndefined) if default is Ellipsis: self.default = PydanticUndefined - self._attributes_set.pop('default', None) else: self.default = default @@ -251,44 +193,30 @@ class FieldInfo(_repr.Representation): if self.default is not PydanticUndefined and self.default_factory is not None: raise TypeError('cannot specify both default and default_factory') + self.title = kwargs.pop('title', None) self.alias = kwargs.pop('alias', None) self.validation_alias = kwargs.pop('validation_alias', None) self.serialization_alias = kwargs.pop('serialization_alias', None) alias_is_set = any(alias is not None for alias in (self.alias, self.validation_alias, self.serialization_alias)) self.alias_priority = kwargs.pop('alias_priority', None) or 2 if alias_is_set else None - self.title = kwargs.pop('title', None) - self.field_title_generator = kwargs.pop('field_title_generator', None) self.description = kwargs.pop('description', None) self.examples = kwargs.pop('examples', None) self.exclude = kwargs.pop('exclude', None) - self.exclude_if = kwargs.pop('exclude_if', None) self.discriminator = kwargs.pop('discriminator', None) - # For compatibility with FastAPI<=0.110.0, we preserve the existing value if it is not overridden - self.deprecated = kwargs.pop('deprecated', getattr(self, 'deprecated', None)) self.repr = kwargs.pop('repr', True) self.json_schema_extra = kwargs.pop('json_schema_extra', None) self.validate_default = kwargs.pop('validate_default', None) self.frozen = kwargs.pop('frozen', None) # currently only used on dataclasses - self.init = kwargs.pop('init', None) self.init_var = kwargs.pop('init_var', None) self.kw_only = kwargs.pop('kw_only', None) - self.metadata = self._collect_metadata(kwargs) # type: ignore + self.metadata = self._collect_metadata(kwargs) + annotation_metadata # type: ignore - # Private attributes: - self._qualifiers: set[Qualifier] = set() - # Used to rebuild FieldInfo instances: - self._complete = True - self._original_annotation: Any = PydanticUndefined - self._original_assignment: Any = PydanticUndefined - # Used to track whether the `FieldInfo` instance represents the data about a field (and is exposed in `model_fields`/`__pydantic_fields__`), - # or if it is the result of the `Field()` function being used as metadata in an `Annotated` type/as an assignment - # (not an ideal pattern, see https://github.com/pydantic/pydantic/issues/11122): - self._final = False - - @staticmethod - def from_field(default: Any = PydanticUndefined, **kwargs: Unpack[_FromFieldInfoInputs]) -> FieldInfo: + @classmethod + def from_field( + cls, default: Any = PydanticUndefined, **kwargs: Unpack[_FromFieldInfoInputs] + ) -> typing_extensions.Self: """Create a new `FieldInfo` object with the `Field` function. Args: @@ -313,263 +241,148 @@ class FieldInfo(_repr.Representation): """ if 'annotation' in kwargs: raise TypeError('"annotation" is not permitted as a Field keyword argument') - return FieldInfo(default=default, **kwargs) + return cls(default=default, **kwargs) - @staticmethod - def from_annotation(annotation: type[Any], *, _source: AnnotationSource = AnnotationSource.ANY) -> FieldInfo: + @classmethod + def from_annotation(cls, annotation: type[Any]) -> FieldInfo: """Creates a `FieldInfo` instance from a bare annotation. - This function is used internally to create a `FieldInfo` from a bare annotation like this: - - ```python - import pydantic - - class MyModel(pydantic.BaseModel): - foo: int # <-- like this - ``` - - We also account for the case where the annotation can be an instance of `Annotated` and where - one of the (not first) arguments in `Annotated` is an instance of `FieldInfo`, e.g.: - - ```python - from typing import Annotated - - import annotated_types - - import pydantic - - class MyModel(pydantic.BaseModel): - foo: Annotated[int, annotated_types.Gt(42)] - bar: Annotated[int, pydantic.Field(gt=42)] - ``` - Args: annotation: An annotation object. Returns: An instance of the field metadata. + + Example: + This is how you can create a field from a bare annotation like this: + + ```python + import pydantic + + class MyModel(pydantic.BaseModel): + foo: int # <-- like this + ``` + + We also account for the case where the annotation can be an instance of `Annotated` and where + one of the (not first) arguments in `Annotated` are an instance of `FieldInfo`, e.g.: + + ```python + import annotated_types + from typing_extensions import Annotated + + import pydantic + + class MyModel(pydantic.BaseModel): + foo: Annotated[int, annotated_types.Gt(42)] + bar: Annotated[int, pydantic.Field(gt=42)] + ``` + """ - try: - inspected_ann = inspect_annotation( - annotation, - annotation_source=_source, - unpack_type_aliases='skip', - ) - except ForbiddenQualifier as e: - raise PydanticForbiddenQualifier(e.qualifier, annotation) + final = False + if _typing_extra.is_finalvar(annotation): + final = True + if annotation is not typing_extensions.Final: + annotation = typing_extensions.get_args(annotation)[0] - # TODO check for classvar and error? + if _typing_extra.is_annotated(annotation): + first_arg, *extra_args = typing_extensions.get_args(annotation) + if _typing_extra.is_finalvar(first_arg): + final = True + field_info_annotations = [a for a in extra_args if isinstance(a, FieldInfo)] + field_info = cls.merge_field_infos(*field_info_annotations, annotation=first_arg) + if field_info: + new_field_info = copy(field_info) + new_field_info.annotation = first_arg + new_field_info.frozen = final or field_info.frozen + metadata: list[Any] = [] + for a in extra_args: + if not isinstance(a, FieldInfo): + metadata.append(a) + else: + metadata.extend(a.metadata) + new_field_info.metadata = metadata + return new_field_info - # No assigned value, this happens when using a bare `Final` qualifier (also for other - # qualifiers, but they shouldn't appear here). In this case we infer the type as `Any` - # because we don't have any assigned value. - type_expr: Any = Any if inspected_ann.type is UNKNOWN else inspected_ann.type - final = 'final' in inspected_ann.qualifiers - metadata = inspected_ann.metadata + return cls(annotation=annotation, frozen=final or None) - attr_overrides = {'annotation': type_expr} - if final: - attr_overrides['frozen'] = True - field_info = FieldInfo._construct(metadata, **attr_overrides) - field_info._qualifiers = inspected_ann.qualifiers - field_info._final = True - return field_info - - @staticmethod - def from_annotated_attribute( - annotation: type[Any], default: Any, *, _source: AnnotationSource = AnnotationSource.ANY - ) -> FieldInfo: + @classmethod + def from_annotated_attribute(cls, annotation: type[Any], default: Any) -> FieldInfo: """Create `FieldInfo` from an annotation with a default value. - This is used in cases like the following: - - ```python - from typing import Annotated - - import annotated_types - - import pydantic - - class MyModel(pydantic.BaseModel): - foo: int = 4 # <-- like this - bar: Annotated[int, annotated_types.Gt(4)] = 4 # <-- or this - spam: Annotated[int, pydantic.Field(gt=4)] = 4 # <-- or this - ``` - Args: annotation: The type annotation of the field. default: The default value of the field. Returns: A field object with the passed values. + + Example: + ```python + import annotated_types + from typing_extensions import Annotated + + import pydantic + + class MyModel(pydantic.BaseModel): + foo: int = 4 # <-- like this + bar: Annotated[int, annotated_types.Gt(4)] = 4 # <-- or this + spam: Annotated[int, pydantic.Field(gt=4)] = 4 # <-- or this + ``` """ - if annotation is not MISSING and annotation is default: - raise PydanticUserError( - 'Error when building FieldInfo from annotated attribute. ' - "Make sure you don't have any field name clashing with a type annotation.", - code='unevaluable-type-annotation', + final = False + if _typing_extra.is_finalvar(annotation): + final = True + if annotation is not typing_extensions.Final: + annotation = typing_extensions.get_args(annotation)[0] + + if isinstance(default, cls): + default.annotation, annotation_metadata = cls._extract_metadata(annotation) + default.metadata += annotation_metadata + default = default.merge_field_infos( + *[x for x in annotation_metadata if isinstance(x, cls)], default, annotation=default.annotation ) - - try: - inspected_ann = inspect_annotation( - annotation, - annotation_source=_source, - unpack_type_aliases='skip', - ) - except ForbiddenQualifier as e: - raise PydanticForbiddenQualifier(e.qualifier, annotation) - - # TODO check for classvar and error? - - # TODO infer from the default, this can be done in v3 once we treat final fields with - # a default as proper fields and not class variables: - type_expr: Any = Any if inspected_ann.type is UNKNOWN else inspected_ann.type - final = 'final' in inspected_ann.qualifiers - metadata = inspected_ann.metadata - - # HACK 1: the order in which the metadata is merged is inconsistent; we need to prepend - # metadata from the assignment at the beginning of the metadata. Changing this is only - # possible in v3 (at least). See https://github.com/pydantic/pydantic/issues/10507 - prepend_metadata: list[Any] | None = None - attr_overrides = {'annotation': type_expr} - if final: - attr_overrides['frozen'] = True - - # HACK 2: FastAPI is subclassing `FieldInfo` and historically expected the actual - # instance's type to be preserved when constructing new models with its subclasses as assignments. - # This code is never reached by Pydantic itself, and in an ideal world this shouldn't be necessary. - if not metadata and isinstance(default, FieldInfo) and type(default) is not FieldInfo: - field_info = default._copy() - field_info._attributes_set.update(attr_overrides) - for k, v in attr_overrides.items(): - setattr(field_info, k, v) - return field_info - - if isinstance(default, FieldInfo): - default_copy = default._copy() # Copy unnecessary when we remove HACK 1. - prepend_metadata = default_copy.metadata - default_copy.metadata = [] - metadata = metadata + [default_copy] + default.frozen = final or default.frozen + return default elif isinstance(default, dataclasses.Field): - from_field = FieldInfo._from_dataclass_field(default) - prepend_metadata = from_field.metadata # Unnecessary when we remove HACK 1. - from_field.metadata = [] - metadata = metadata + [from_field] - if 'init_var' in inspected_ann.qualifiers: - attr_overrides['init_var'] = True - if (init := getattr(default, 'init', None)) is not None: - attr_overrides['init'] = init - if (kw_only := getattr(default, 'kw_only', None)) is not None: - attr_overrides['kw_only'] = kw_only + init_var = False + if annotation is dataclasses.InitVar: + if sys.version_info < (3, 8): + raise RuntimeError('InitVar is not supported in Python 3.7 as type information is lost') + + init_var = True + annotation = Any + elif isinstance(annotation, dataclasses.InitVar): + init_var = True + annotation = annotation.type + pydantic_field = cls._from_dataclass_field(default) + pydantic_field.annotation, annotation_metadata = cls._extract_metadata(annotation) + pydantic_field.metadata += annotation_metadata + pydantic_field = pydantic_field.merge_field_infos( + *[x for x in annotation_metadata if isinstance(x, cls)], + pydantic_field, + annotation=pydantic_field.annotation, + ) + pydantic_field.frozen = final or pydantic_field.frozen + pydantic_field.init_var = init_var + pydantic_field.kw_only = getattr(default, 'kw_only', None) + return pydantic_field else: - # `default` is the actual default value - attr_overrides['default'] = default + if _typing_extra.is_annotated(annotation): + first_arg, *extra_args = typing_extensions.get_args(annotation) + field_infos = [a for a in extra_args if isinstance(a, FieldInfo)] + field_info = cls.merge_field_infos(*field_infos, annotation=first_arg, default=default) + metadata: list[Any] = [] + for a in extra_args: + if not isinstance(a, FieldInfo): + metadata.append(a) + else: + metadata.extend(a.metadata) + field_info.metadata = metadata + return field_info - field_info = FieldInfo._construct( - prepend_metadata + metadata if prepend_metadata is not None else metadata, **attr_overrides - ) - field_info._qualifiers = inspected_ann.qualifiers - field_info._final = True - return field_info - - @classmethod - def _construct(cls, metadata: list[Any], **attr_overrides: Any) -> Self: - """Construct the final `FieldInfo` instance, by merging the possibly existing `FieldInfo` instances from the metadata. - - With the following example: - - ```python {test="skip" lint="skip"} - class Model(BaseModel): - f: Annotated[int, Gt(1), Field(description='desc', lt=2)] - ``` - - `metadata` refers to the metadata elements of the `Annotated` form. This metadata is iterated over from left to right: - - - If the element is a `Field()` function (which is itself a `FieldInfo` instance), the field attributes (such as - `description`) are saved to be set on the final `FieldInfo` instance. - On the other hand, some kwargs (such as `lt`) are stored as `metadata` (see `FieldInfo.__init__()`, calling - `FieldInfo._collect_metadata()`). In this case, the final metadata list is extended with the one from this instance. - - Else, the element is considered as a single metadata object, and is appended to the final metadata list. - - Args: - metadata: The list of metadata elements to merge together. If the `FieldInfo` instance to be constructed is for - a field with an assigned `Field()`, this `Field()` assignment should be added as the last element of the - provided metadata. - **attr_overrides: Extra attributes that should be set on the final merged `FieldInfo` instance. - - Returns: - The final merged `FieldInfo` instance. - """ - merged_metadata: list[Any] = [] - merged_kwargs: dict[str, Any] = {} - - for meta in metadata: - if isinstance(meta, FieldInfo): - merged_metadata.extend(meta.metadata) - - new_js_extra: JsonDict | None = None - current_js_extra = meta.json_schema_extra - if current_js_extra is not None and 'json_schema_extra' in merged_kwargs: - # We need to merge `json_schema_extra`'s: - existing_js_extra = merged_kwargs['json_schema_extra'] - if isinstance(existing_js_extra, dict): - if isinstance(current_js_extra, dict): - new_js_extra = { - **existing_js_extra, - **current_js_extra, - } - elif callable(current_js_extra): - warn( - 'Composing `dict` and `callable` type `json_schema_extra` is not supported. ' - 'The `callable` type is being ignored. ' - "If you'd like support for this behavior, please open an issue on pydantic.", - UserWarning, - ) - elif callable(existing_js_extra) and isinstance(current_js_extra, dict): - warn( - 'Composing `dict` and `callable` type `json_schema_extra` is not supported. ' - 'The `callable` type is being ignored. ' - "If you'd like support for this behavior, please open an issue on pydantic.", - UserWarning, - ) - - # HACK: It is common for users to define "make model partial" (or similar) utilities, that - # convert all model fields to be optional (i.e. have a default value). To do so, they mutate - # each `FieldInfo` instance from `model_fields` to set a `default`, and use `create_model()` - # with `Annotated[ | None, mutated_field_info]`` as an annotation. However, such - # mutations (by doing simple assignments) are only accidentally working, because we also - # need to track attributes explicitly set in `_attributes_set` (relying on default values for - # each attribute is *not* enough, for instance with `Annotated[int, Field(alias='a'), Field(alias=None)]` - # the resulting `FieldInfo` should have `alias=None`). - # To mitigate this, we add a special case when a "final" `FieldInfo` instance (that is an instance coming - # from `model_fields`) is used in annotated metadata (or assignment). In this case, we assume *all* attributes - # were explicitly set, and as such we use all of them (and this will correctly pick up the mutations). - # In theory, this shouldn't really be supported, you are only supposed to use the `Field()` function, not - # a `FieldInfo` instance directly (granted, `Field()` returns a `FieldInfo`, see - # https://github.com/pydantic/pydantic/issues/11122): - if meta._final: - merged_kwargs.update({attr: getattr(meta, attr) for attr in _Attrs}) - else: - merged_kwargs.update(meta._attributes_set) - - if new_js_extra is not None: - merged_kwargs['json_schema_extra'] = new_js_extra - elif typing_objects.is_deprecated(meta): - merged_kwargs['deprecated'] = meta - else: - merged_metadata.append(meta) - - merged_kwargs.update(attr_overrides) - merged_field_info = cls(**merged_kwargs) - merged_field_info.metadata = merged_metadata - return merged_field_info + return cls(annotation=annotation, default=default, frozen=final or None) @staticmethod - @typing_extensions.deprecated( - "The 'merge_field_infos()' method is deprecated and will be removed in a future version. " - 'If you relied on this method, please open an issue in the Pydantic issue tracker.', - category=None, - ) def merge_field_infos(*field_infos: FieldInfo, **overrides: Any) -> FieldInfo: """Merge `FieldInfo` instances keeping only explicitly set attributes. @@ -578,65 +391,33 @@ class FieldInfo(_repr.Representation): Returns: FieldInfo: A merged FieldInfo instance. """ + flattened_field_infos: list[FieldInfo] = [] + for field_info in field_infos: + flattened_field_infos.extend(x for x in field_info.metadata if isinstance(x, FieldInfo)) + flattened_field_infos.append(field_info) + field_infos = tuple(flattened_field_infos) if len(field_infos) == 1: # No merging necessary, but we still need to make a copy and apply the overrides - field_info = field_infos[0]._copy() + field_info = copy(field_infos[0]) field_info._attributes_set.update(overrides) - - default_override = overrides.pop('default', PydanticUndefined) - if default_override is Ellipsis: - default_override = PydanticUndefined - if default_override is not PydanticUndefined: - field_info.default = default_override - for k, v in overrides.items(): setattr(field_info, k, v) return field_info # type: ignore - merged_field_info_kwargs: dict[str, Any] = {} + new_kwargs: dict[str, Any] = {} metadata = {} for field_info in field_infos: - attributes_set = field_info._attributes_set.copy() - - try: - json_schema_extra = attributes_set.pop('json_schema_extra') - existing_json_schema_extra = merged_field_info_kwargs.get('json_schema_extra') - - if existing_json_schema_extra is None: - merged_field_info_kwargs['json_schema_extra'] = json_schema_extra - if isinstance(existing_json_schema_extra, dict): - if isinstance(json_schema_extra, dict): - merged_field_info_kwargs['json_schema_extra'] = { - **existing_json_schema_extra, - **json_schema_extra, - } - if callable(json_schema_extra): - warn( - 'Composing `dict` and `callable` type `json_schema_extra` is not supported.' - 'The `callable` type is being ignored.' - "If you'd like support for this behavior, please open an issue on pydantic.", - PydanticJsonSchemaWarning, - ) - elif callable(json_schema_extra): - # if ever there's a case of a callable, we'll just keep the last json schema extra spec - merged_field_info_kwargs['json_schema_extra'] = json_schema_extra - except KeyError: - pass - - # later FieldInfo instances override everything except json_schema_extra from earlier FieldInfo instances - merged_field_info_kwargs.update(attributes_set) - + new_kwargs.update(field_info._attributes_set) for x in field_info.metadata: if not isinstance(x, FieldInfo): metadata[type(x)] = x - - merged_field_info_kwargs.update(overrides) - field_info = FieldInfo(**merged_field_info_kwargs) + new_kwargs.update(overrides) + field_info = FieldInfo(**new_kwargs) field_info.metadata = list(metadata.values()) return field_info - @staticmethod - def _from_dataclass_field(dc_field: DataclassField[Any]) -> FieldInfo: + @classmethod + def _from_dataclass_field(cls, dc_field: DataclassField[Any]) -> typing_extensions.Self: """Return a new `FieldInfo` instance from a `dataclasses.Field` instance. Args: @@ -650,23 +431,41 @@ class FieldInfo(_repr.Representation): """ default = dc_field.default if default is dataclasses.MISSING: - default = _Unset + default = PydanticUndefined if dc_field.default_factory is dataclasses.MISSING: - default_factory = _Unset + default_factory: typing.Callable[[], Any] | None = None else: default_factory = dc_field.default_factory # use the `Field` function so in correct kwargs raise the correct `TypeError` dc_field_metadata = {k: v for k, v in dc_field.metadata.items() if k in _FIELD_ARG_NAMES} - if sys.version_info >= (3, 14) and dc_field.doc is not None: - dc_field_metadata['description'] = dc_field.doc - return Field(default=default, default_factory=default_factory, repr=dc_field.repr, **dc_field_metadata) # pyright: ignore[reportCallIssue] + return Field(default=default, default_factory=default_factory, repr=dc_field.repr, **dc_field_metadata) - @staticmethod - def _collect_metadata(kwargs: dict[str, Any]) -> list[Any]: + @classmethod + def _extract_metadata(cls, annotation: type[Any] | None) -> tuple[type[Any] | None, list[Any]]: + """Tries to extract metadata/constraints from an annotation if it uses `Annotated`. + + Args: + annotation: The type hint annotation for which metadata has to be extracted. + + Returns: + A tuple containing the extracted metadata type and the list of extra arguments. + """ + if annotation is not None: + if _typing_extra.is_annotated(annotation): + first_arg, *extra_args = typing_extensions.get_args(annotation) + return first_arg, list(extra_args) + + return annotation, [] + + @classmethod + def _collect_metadata(cls, kwargs: dict[str, Any]) -> list[Any]: """Collect annotations from kwargs. + The return type is actually `annotated_types.BaseMetadata | PydanticMetadata`, + but it gets combined with `list[Any]` from `Annotated[T, ...]`, hence types. + Args: kwargs: Keyword arguments passed to the function. @@ -678,7 +477,7 @@ class FieldInfo(_repr.Representation): general_metadata = {} for key, value in list(kwargs.items()): try: - marker = FieldInfo.metadata_lookup[key] + marker = cls.metadata_lookup[key] except KeyError: continue @@ -692,33 +491,7 @@ class FieldInfo(_repr.Representation): metadata.append(_fields.pydantic_general_metadata(**general_metadata)) return metadata - @property - def deprecation_message(self) -> str | None: - """The deprecation message to be emitted, or `None` if not set.""" - if self.deprecated is None: - return None - if isinstance(self.deprecated, bool): - return 'deprecated' if self.deprecated else None - return self.deprecated if isinstance(self.deprecated, str) else self.deprecated.message - - @property - def default_factory_takes_validated_data(self) -> bool | None: - """Whether the provided default factory callable has a validated data parameter. - - Returns `None` if no default factory is set. - """ - if self.default_factory is not None: - return _fields.takes_validated_data_argument(self.default_factory) - - @overload - def get_default( - self, *, call_default_factory: Literal[True], validated_data: dict[str, Any] | None = None - ) -> Any: ... - - @overload - def get_default(self, *, call_default_factory: Literal[False] = ...) -> Any: ... - - def get_default(self, *, call_default_factory: bool = False, validated_data: dict[str, Any] | None = None) -> Any: + def get_default(self, *, call_default_factory: bool = False) -> Any: """Get the default value. We expose an option for whether to call the default_factory (if present), as calling it may @@ -726,8 +499,7 @@ class FieldInfo(_repr.Representation): be called (namely, when instantiating a model via `model_construct`). Args: - call_default_factory: Whether to call the default factory or not. - validated_data: The already validated data to be passed to the default factory. + call_default_factory: Whether to call the default_factory or not. Defaults to `False`. Returns: The default value, calling the default factory if requested or `None` if not set. @@ -735,36 +507,23 @@ class FieldInfo(_repr.Representation): if self.default_factory is None: return _utils.smart_deepcopy(self.default) elif call_default_factory: - if self.default_factory_takes_validated_data: - fac = cast('Callable[[dict[str, Any]], Any]', self.default_factory) - if validated_data is None: - raise ValueError( - "The default factory requires the 'validated_data' argument, which was not provided when calling 'get_default'." - ) - return fac(validated_data) - else: - fac = cast('Callable[[], Any]', self.default_factory) - return fac() + return self.default_factory() else: return None def is_required(self) -> bool: - """Check if the field is required (i.e., does not have a default value or factory). + """Check if the argument is required. Returns: - `True` if the field is required, `False` otherwise. + `True` if the argument is required, `False` otherwise. """ return self.default is PydanticUndefined and self.default_factory is None def rebuild_annotation(self) -> Any: - """Attempts to rebuild the original annotation for use in function signatures. + """Rebuilds the original annotation for use in function signatures. - If metadata is present, it adds it to the original annotation using - `Annotated`. Otherwise, it returns the original annotation as-is. - - Note that because the metadata has been flattened, the original annotation - may not be reconstructed exactly as originally provided, e.g. if the original - type had unrecognized annotations, or was annotated with a call to `pydantic.Field`. + If metadata is present, it adds it to the original annotation using an + `AnnotatedAlias`. Otherwise, it returns the original annotation as is. Returns: The rebuilt annotation. @@ -773,14 +532,9 @@ class FieldInfo(_repr.Representation): return self.annotation else: # Annotated arguments must be a tuple - return Annotated[(self.annotation, *self.metadata)] # type: ignore + return typing_extensions.Annotated[(self.annotation, *self.metadata)] # type: ignore - def apply_typevars_map( - self, - typevars_map: Mapping[TypeVar, Any] | None, - globalns: GlobalsNamespace | None = None, - localns: MappingNamespace | None = None, - ) -> None: + def apply_typevars_map(self, typevars_map: dict[Any, Any] | None, types_namespace: dict[str, Any] | None) -> None: """Apply a `typevars_map` to the annotation. This method is used when analyzing parametrized generic types to replace typevars with their concrete types. @@ -789,64 +543,23 @@ class FieldInfo(_repr.Representation): Args: typevars_map: A dictionary mapping type variables to their concrete types. - globalns: The globals namespace to use during type annotation evaluation. - localns: The locals namespace to use during type annotation evaluation. + types_namespace (dict | None): A dictionary containing related types to the annotated type. See Also: pydantic._internal._generics.replace_types is used for replacing the typevars with their concrete types. """ - annotation = _generics.replace_types(self.annotation, typevars_map) - annotation, evaluated = _typing_extra.try_eval_type(annotation, globalns, localns) - self.annotation = annotation - if not evaluated: - self._complete = False - self._original_annotation = self.annotation - - def asdict(self) -> _FieldInfoAsDict: - """Return a dictionary representation of the `FieldInfo` instance. - - The returned value is a dictionary with three items: - - * `annotation`: The type annotation of the field. - * `metadata`: The metadata list. - * `attributes`: A mapping of the remaining `FieldInfo` attributes to their values (e.g. `alias`, `title`). - """ - return { - 'annotation': self.annotation, - 'metadata': self.metadata, - 'attributes': {attr: getattr(self, attr) for attr in _Attrs}, - } - - def _copy(self) -> Self: - """Return a copy of the `FieldInfo` instance.""" - # Note: we can't define a custom `__copy__()`, as `FieldInfo` is being subclassed - # by some third-party libraries with extra attributes defined (and as `FieldInfo` - # is slotted, we can't make a copy of the `__dict__`). - copied = copy(self) - for attr_name in ('metadata', '_attributes_set', '_qualifiers'): - # Apply "deep-copy" behavior on collections attributes: - value = getattr(copied, attr_name).copy() - setattr(copied, attr_name, value) - - return copied + annotation = _typing_extra.eval_type_lenient(self.annotation, types_namespace, None) + self.annotation = _generics.replace_types(annotation, typevars_map) def __repr_args__(self) -> ReprArgs: yield 'annotation', _repr.PlainRepr(_repr.display_as_type(self.annotation)) yield 'required', self.is_required() for s in self.__slots__: - # TODO: properly make use of the protocol (https://rich.readthedocs.io/en/stable/pretty.html#rich-repr-protocol) - # By yielding a three-tuple: - if s in ( - 'annotation', - '_attributes_set', - '_qualifiers', - '_complete', - '_original_assignment', - '_original_annotation', - '_final', - ): + if s == '_attributes_set': + continue + if s == 'annotation': continue elif s == 'metadata' and not self.metadata: continue @@ -858,9 +571,7 @@ class FieldInfo(_repr.Representation): continue if s == 'serialization_alias' and self.serialization_alias == self.alias: continue - if s == 'default' and self.default is not PydanticUndefined: - yield 'default', self.default - elif s == 'default_factory' and self.default_factory is not None: + if s == 'default_factory' and self.default_factory is not None: yield 'default_factory', _repr.PlainRepr(_repr.display_as_type(self.default_factory)) else: value = getattr(self, s) @@ -868,333 +579,122 @@ class FieldInfo(_repr.Representation): yield s, value -class _EmptyKwargs(TypedDict): +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class AliasPath: + """Usage docs: https://docs.pydantic.dev/2.5/concepts/fields#aliaspath-and-aliaschoices + + A data class used by `validation_alias` as a convenience to create aliases. + + Attributes: + path: A list of string or integer aliases. + """ + + path: list[int | str] + + def __init__(self, first_arg: str, *args: str | int) -> None: + self.path = [first_arg] + list(args) + + def convert_to_aliases(self) -> list[str | int]: + """Converts arguments to a list of string or integer aliases. + + Returns: + The list of aliases. + """ + return self.path + + +@dataclasses.dataclass(**_internal_dataclass.slots_true) +class AliasChoices: + """Usage docs: https://docs.pydantic.dev/2.5/concepts/fields#aliaspath-and-aliaschoices + + A data class used by `validation_alias` as a convenience to create aliases. + + Attributes: + choices: A list containing a string or `AliasPath`. + """ + + choices: list[str | AliasPath] + + def __init__(self, first_choice: str | AliasPath, *choices: str | AliasPath) -> None: + self.choices = [first_choice] + list(choices) + + def convert_to_aliases(self) -> list[list[str | int]]: + """Converts arguments to a list of lists containing string or integer aliases. + + Returns: + The list of aliases. + """ + aliases: list[list[str | int]] = [] + for c in self.choices: + if isinstance(c, AliasPath): + aliases.append(c.convert_to_aliases()) + else: + aliases.append([c]) + return aliases + + +class _EmptyKwargs(typing_extensions.TypedDict): """This class exists solely to ensure that type checking warns about passing `**extra` in `Field`.""" -_Attrs = { - 'default': ..., - 'default_factory': None, - 'alias': None, - 'alias_priority': None, - 'validation_alias': None, - 'serialization_alias': None, - 'title': None, - 'field_title_generator': None, - 'description': None, - 'examples': None, - 'exclude': None, - 'exclude_if': None, - 'discriminator': None, - 'deprecated': None, - 'json_schema_extra': None, - 'frozen': None, - 'validate_default': None, - 'repr': True, - 'init': None, - 'init_var': None, - 'kw_only': None, -} - -_DefaultValues = { - **_Attrs, - 'kw_only': None, - 'pattern': None, - 'strict': None, - 'gt': None, - 'ge': None, - 'lt': None, - 'le': None, - 'multiple_of': None, - 'allow_inf_nan': None, - 'max_digits': None, - 'decimal_places': None, - 'min_length': None, - 'max_length': None, - 'coerce_numbers_to_str': None, -} +_DefaultValues = dict( + default=..., + default_factory=None, + alias=None, + alias_priority=None, + validation_alias=None, + serialization_alias=None, + title=None, + description=None, + examples=None, + exclude=None, + discriminator=None, + json_schema_extra=None, + frozen=None, + validate_default=None, + repr=True, + init_var=None, + kw_only=None, + pattern=None, + strict=None, + gt=None, + ge=None, + lt=None, + le=None, + multiple_of=None, + allow_inf_nan=None, + max_digits=None, + decimal_places=None, + min_length=None, + max_length=None, +) -_T = TypeVar('_T') - - -# NOTE: Actual return type is 'FieldInfo', but we want to help type checkers -# to understand the magic that happens at runtime with the following overloads: -@overload # type hint the return value as `Any` to avoid type checking regressions when using `...`. -def Field( - default: ellipsis, # noqa: F821 # TODO: use `_typing_extra.EllipsisType` when we drop Py3.9 - *, - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: bool | None = _Unset, - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> Any: ... -@overload # `default` argument set, validate_default=True (no type checking on the default value) -def Field( - default: Any, - *, - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: Literal[True], - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> Any: ... -@overload # `default` argument set, validate_default=False or unset -def Field( - default: _T, - *, - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - # NOTE: to get proper type checking on `exclude_if`'s argument, we could use `_T` instead of `Any`. However, - # this requires (at least for pyright) adding an additional overload where `exclude_if` is required (otherwise - # `a: int = Field(default_factory=str)` results in a false negative). - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: Literal[False] = ..., - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> _T: ... -@overload # `default_factory` argument set, validate_default=True (no type checking on the default value) -def Field( # pyright: ignore[reportOverlappingOverload] - *, - default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any], - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: Literal[True], - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> Any: ... -@overload # `default_factory` argument set, validate_default=False or unset -def Field( - *, - default_factory: Callable[[], _T] | Callable[[dict[str, Any]], _T], - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - # NOTE: to get proper type checking on `exclude_if`'s argument, we could use `_T` instead of `Any`. However, - # this requires (at least for pyright) adding an additional overload where `exclude_if` is required (otherwise - # `a: int = Field(default_factory=str)` results in a false negative). - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: Literal[False] | None = _Unset, - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> _T: ... -@overload -def Field( # No default set - *, - alias: str | None = _Unset, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = _Unset, - serialization_alias: str | None = _Unset, - title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, - description: str | None = _Unset, - examples: list[Any] | None = _Unset, - exclude: bool | None = _Unset, - exclude_if: Callable[[Any], bool] | None = _Unset, - discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, - frozen: bool | None = _Unset, - validate_default: bool | None = _Unset, - repr: bool = _Unset, - init: bool | None = _Unset, - init_var: bool | None = _Unset, - kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, - strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - min_length: int | None = _Unset, - max_length: int | None = _Unset, - union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, - **extra: Unpack[_EmptyKwargs], -) -> Any: ... def Field( # noqa: C901 default: Any = PydanticUndefined, *, - default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None = _Unset, + default_factory: typing.Callable[[], Any] | None = _Unset, alias: str | None = _Unset, alias_priority: int | None = _Unset, validation_alias: str | AliasPath | AliasChoices | None = _Unset, serialization_alias: str | None = _Unset, title: str | None = _Unset, - field_title_generator: Callable[[str, FieldInfo], str] | None = _Unset, description: str | None = _Unset, examples: list[Any] | None = _Unset, exclude: bool | None = _Unset, - exclude_if: Callable[[Any], bool] | None = _Unset, discriminator: str | types.Discriminator | None = _Unset, - deprecated: Deprecated | str | bool | None = _Unset, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = _Unset, + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None = _Unset, frozen: bool | None = _Unset, validate_default: bool | None = _Unset, repr: bool = _Unset, - init: bool | None = _Unset, init_var: bool | None = _Unset, kw_only: bool | None = _Unset, - pattern: str | re.Pattern[str] | None = _Unset, + pattern: str | None = _Unset, strict: bool | None = _Unset, - coerce_numbers_to_str: bool | None = _Unset, - gt: annotated_types.SupportsGt | None = _Unset, - ge: annotated_types.SupportsGe | None = _Unset, - lt: annotated_types.SupportsLt | None = _Unset, - le: annotated_types.SupportsLe | None = _Unset, + gt: float | None = _Unset, + ge: float | None = _Unset, + lt: float | None = _Unset, + le: float | None = _Unset, multiple_of: float | None = _Unset, allow_inf_nan: bool | None = _Unset, max_digits: int | None = _Unset, @@ -1202,11 +702,9 @@ def Field( # noqa: C901 min_length: int | None = _Unset, max_length: int | None = _Unset, union_mode: Literal['smart', 'left_to_right'] = _Unset, - fail_fast: bool | None = _Unset, **extra: Unpack[_EmptyKwargs], ) -> Any: - """!!! abstract "Usage Documentation" - [Fields](../concepts/fields.md) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/fields Create a field for objects that can be configured. @@ -1218,34 +716,24 @@ def Field( # noqa: C901 Args: default: Default value if the field is not set. - default_factory: A callable to generate the default value. The callable can either take 0 arguments - (in which case it is called as is) or a single argument containing the already validated data. - alias: The name to use for the attribute when validating or serializing by alias. - This is often used for things like converting between snake and camel case. + default_factory: A callable to generate the default value, such as :func:`~datetime.utcnow`. + alias: An alternative name for the attribute. alias_priority: Priority of the alias. This affects whether an alias generator is used. - validation_alias: Like `alias`, but only affects validation, not serialization. - serialization_alias: Like `alias`, but only affects serialization, not validation. + validation_alias: 'Whitelist' validation step. The field will be the single one allowed by the alias or set of + aliases defined. + serialization_alias: 'Blacklist' validation step. The vanilla field will be the single one of the alias' or set + of aliases' fields and all the other fields will be ignored at serialization time. title: Human-readable title. - field_title_generator: A callable that takes a field name and returns title for it. description: Human-readable description. examples: Example values for this field. exclude: Whether to exclude the field from the model serialization. - exclude_if: A callable that determines whether to exclude a field during serialization based on its value. discriminator: Field name or Discriminator for discriminating the type in a tagged union. - deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, - or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. - json_schema_extra: A dict or callable to provide extra JSON schema properties. - frozen: Whether the field is frozen. If true, attempts to change the value on an instance will raise an error. - validate_default: If `True`, apply validation to the default value every time you create an instance. - Otherwise, for performance reasons, the default value of the field is trusted and not validated. + json_schema_extra: Any additional JSON schema data for the schema property. + frozen: Whether the field is frozen. + validate_default: Run validation that isn't only checking existence of defaults. This can be set to `True` or `False`. If not set, it defaults to `None`. repr: A boolean indicating whether to include the field in the `__repr__` output. - init: Whether the field should be included in the constructor of the dataclass. - (Only applies to dataclasses.) - init_var: Whether the field should _only_ be included in the constructor of the dataclass. - (Only applies to dataclasses.) + init_var: Whether the field should be included in the constructor of the dataclass. kw_only: Whether the field should be a keyword-only argument in the constructor of the dataclass. - (Only applies to dataclasses.) - coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). strict: If `True`, strict validation is applied to the field. See [Strict Mode](../concepts/strict_mode.md) for details. gt: Greater than. If set, value must be greater than this. Only applicable to numbers. @@ -1253,24 +741,22 @@ def Field( # noqa: C901 lt: Less than. If set, value must be less than this. Only applicable to numbers. le: Less than or equal. If set, value must be less than or equal to this. Only applicable to numbers. multiple_of: Value must be a multiple of this. Only applicable to numbers. - min_length: Minimum length for iterables. - max_length: Maximum length for iterables. - pattern: Pattern for strings (a regular expression). - allow_inf_nan: Allow `inf`, `-inf`, `nan`. Only applicable to float and [`Decimal`][decimal.Decimal] numbers. + min_length: Minimum length for strings. + max_length: Maximum length for strings. + pattern: Pattern for strings. + allow_inf_nan: Allow `inf`, `-inf`, `nan`. Only applicable to numbers. max_digits: Maximum number of allow digits for strings. decimal_places: Maximum number of decimal places allowed for numbers. union_mode: The strategy to apply when validating a union. Can be `smart` (the default), or `left_to_right`. - See [Union Mode](../concepts/unions.md#union-modes) for details. - fail_fast: If `True`, validation will stop on the first error. If `False`, all validation errors will be collected. - This option can be applied only to iterable types (list, tuple, set, and frozenset). - extra: (Deprecated) Extra fields that will be included in the JSON schema. + See [Union Mode](standard_library_types.md#union-mode) for details. + extra: Include extra fields used by the JSON schema. !!! warning Deprecated The `extra` kwargs is deprecated. Use `json_schema_extra` instead. Returns: - A new [`FieldInfo`][pydantic.fields.FieldInfo]. The return annotation is `Any` so `Field` can be used on - type-annotated fields without causing a type error. + A new [`FieldInfo`][pydantic.fields.FieldInfo], the return annotation is `Any` so `Field` can be used on + type annotated fields without causing a typing error. """ # Check deprecated and removed params from V1. This logic should eventually be removed. const = extra.pop('const', None) # type: ignore @@ -1279,21 +765,13 @@ def Field( # noqa: C901 min_items = extra.pop('min_items', None) # type: ignore if min_items is not None: - warn( - '`min_items` is deprecated and will be removed, use `min_length` instead', - PydanticDeprecatedSince20, - stacklevel=2, - ) + warn('`min_items` is deprecated and will be removed, use `min_length` instead', DeprecationWarning) if min_length in (None, _Unset): min_length = min_items # type: ignore max_items = extra.pop('max_items', None) # type: ignore if max_items is not None: - warn( - '`max_items` is deprecated and will be removed, use `max_length` instead', - PydanticDeprecatedSince20, - stacklevel=2, - ) + warn('`max_items` is deprecated and will be removed, use `max_length` instead', DeprecationWarning) if max_length in (None, _Unset): max_length = max_items # type: ignore @@ -1309,11 +787,7 @@ def Field( # noqa: C901 allow_mutation = extra.pop('allow_mutation', None) # type: ignore if allow_mutation is not None: - warn( - '`allow_mutation` is deprecated and will be removed. use `frozen` instead', - PydanticDeprecatedSince20, - stacklevel=2, - ) + warn('`allow_mutation` is deprecated and will be removed. use `frozen` instead', DeprecationWarning) if allow_mutation is False: frozen = True @@ -1326,8 +800,7 @@ def Field( # noqa: C901 'Using extra keyword arguments on `Field` is deprecated and will be removed.' ' Use `json_schema_extra` instead.' f' (Extra keys: {", ".join(k.__repr__() for k in extra.keys())})', - PydanticDeprecatedSince20, - stacklevel=2, + DeprecationWarning, ) if not json_schema_extra or json_schema_extra is _Unset: json_schema_extra = extra # type: ignore @@ -1347,11 +820,7 @@ def Field( # noqa: C901 include = extra.pop('include', None) # type: ignore if include is not None: - warn( - '`include` is deprecated and does nothing. It will be removed, use `exclude` instead', - PydanticDeprecatedSince20, - stacklevel=2, - ) + warn('`include` is deprecated and does nothing. It will be removed, use `exclude` instead', DeprecationWarning) return FieldInfo.from_field( default, @@ -1361,22 +830,17 @@ def Field( # noqa: C901 validation_alias=validation_alias, serialization_alias=serialization_alias, title=title, - field_title_generator=field_title_generator, description=description, examples=examples, exclude=exclude, - exclude_if=exclude_if, discriminator=discriminator, - deprecated=deprecated, json_schema_extra=json_schema_extra, frozen=frozen, pattern=pattern, validate_default=validate_default, repr=repr, - init=init, init_var=init_var, kw_only=kw_only, - coerce_numbers_to_str=coerce_numbers_to_str, strict=strict, gt=gt, ge=ge, @@ -1389,7 +853,6 @@ def Field( # noqa: C901 max_digits=max_digits, decimal_places=decimal_places, union_mode=union_mode, - fail_fast=fail_fast, ) @@ -1400,26 +863,21 @@ _FIELD_ARG_NAMES.remove('extra') # do not include the varkwargs parameter class ModelPrivateAttr(_repr.Representation): """A descriptor for private attributes in class models. - !!! warning - You generally shouldn't be creating `ModelPrivateAttr` instances directly, instead use - `pydantic.fields.PrivateAttr`. (This is similar to `FieldInfo` vs. `Field`.) - Attributes: default: The default value of the attribute if not provided. default_factory: A callable function that generates the default value of the attribute if not provided. """ - __slots__ = ('default', 'default_factory') + __slots__ = 'default', 'default_factory' - def __init__(self, default: Any = PydanticUndefined, *, default_factory: Callable[[], Any] | None = None) -> None: - if default is Ellipsis: - self.default = PydanticUndefined - else: - self.default = default + def __init__( + self, default: Any = PydanticUndefined, *, default_factory: typing.Callable[[], Any] | None = None + ) -> None: + self.default = default self.default_factory = default_factory - if not TYPE_CHECKING: + if not typing.TYPE_CHECKING: # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access def __getattr__(self, item: str) -> Any: @@ -1433,10 +891,11 @@ class ModelPrivateAttr(_repr.Representation): def __set_name__(self, cls: type[Any], name: str) -> None: """Preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487.""" - default = self.default - if default is PydanticUndefined: + if self.default is PydanticUndefined: return - set_name = getattr(default, '__set_name__', None) + if not hasattr(self.default, '__set_name__'): + return + set_name = self.default.__set_name__ if callable(set_name): set_name(cls, name) @@ -1459,37 +918,14 @@ class ModelPrivateAttr(_repr.Representation): ) -# NOTE: Actual return type is 'ModelPrivateAttr', but we want to help type checkers -# to understand the magic that happens at runtime. -@overload # `default` argument set -def PrivateAttr( - default: _T, - *, - init: Literal[False] = False, -) -> _T: ... -@overload # `default_factory` argument set -def PrivateAttr( - *, - default_factory: Callable[[], _T], - init: Literal[False] = False, -) -> _T: ... -@overload # No default set -def PrivateAttr( - *, - init: Literal[False] = False, -) -> Any: ... def PrivateAttr( default: Any = PydanticUndefined, *, - default_factory: Callable[[], Any] | None = None, - init: Literal[False] = False, + default_factory: typing.Callable[[], Any] | None = None, ) -> Any: - """!!! abstract "Usage Documentation" - [Private Model Attributes](../concepts/models.md#private-model-attributes) + """Indicates that attribute is only used internally and never mixed with regular fields. - Indicates that an attribute is intended for private use and not handled during normal validation/serialization. - - Private attributes are not validated by Pydantic, so it's up to you to ensure they are used in a type-safe manner. + Private attributes are not checked by Pydantic, so it's up to you to maintain their accuracy. Private attributes are stored in `__private_attributes__` on the model. @@ -1498,7 +934,6 @@ def PrivateAttr( default_factory: Callable that will be called when a default value is needed for this attribute. If both `default` and `default_factory` are set, an error will be raised. - init: Whether the attribute should be included in the constructor of the dataclass. Always `False`. Returns: An instance of [`ModelPrivateAttr`][pydantic.fields.ModelPrivateAttr] class. @@ -1523,16 +958,13 @@ class ComputedFieldInfo: decorator_repr: A class variable representing the decorator string, '@computed_field'. wrapped_property: The wrapped computed field property. return_type: The type of the computed field property's return value. - alias: The alias of the property to be used during serialization. - alias_priority: The priority of the alias. This affects whether an alias generator is used. - title: Title of the computed field to include in the serialization JSON schema. - field_title_generator: A callable that takes a field name and returns title for it. - description: Description of the computed field to include in the serialization JSON schema. - deprecated: A deprecation message, an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport, - or a boolean. If `True`, a default deprecation message will be emitted when accessing the field. - examples: Example values of the computed field to include in the serialization JSON schema. - json_schema_extra: A dict or callable to provide extra JSON schema properties. - repr: A boolean indicating whether to include the field in the __repr__ output. + alias: The alias of the property to be used during encoding and decoding. + alias_priority: priority of the alias. This affects whether an alias generator is used + title: Title of the computed field as in OpenAPI document, should be a short summary. + description: Description of the computed field as in OpenAPI document. + examples: Example values of the computed field as in OpenAPI document. + json_schema_extra: Dictionary of extra JSON schema properties. + repr: A boolean indicating whether or not to include the field in the __repr__ output. """ decorator_repr: ClassVar[str] = '@computed_field' @@ -1541,60 +973,35 @@ class ComputedFieldInfo: alias: str | None alias_priority: int | None title: str | None - field_title_generator: Callable[[str, ComputedFieldInfo], str] | None description: str | None - deprecated: Deprecated | str | bool | None examples: list[Any] | None - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None repr: bool - @property - def deprecation_message(self) -> str | None: - """The deprecation message to be emitted, or `None` if not set.""" - if self.deprecated is None: - return None - if isinstance(self.deprecated, bool): - return 'deprecated' if self.deprecated else None - return self.deprecated if isinstance(self.deprecated, str) else self.deprecated.message - def _update_from_config(self, config_wrapper: ConfigWrapper, name: str) -> None: - """Update the instance from the configuration set on the class this computed field belongs to.""" - title_generator = self.field_title_generator or config_wrapper.field_title_generator - if title_generator is not None and self.title is None: - self.title = title_generator(name, self) - if config_wrapper.alias_generator is not None: - self._apply_alias_generator(config_wrapper.alias_generator, name) +# this should really be `property[T], cached_proprety[T]` but property is not generic unlike cached_property +# See https://github.com/python/typing/issues/985 and linked issues +PropertyT = typing.TypeVar('PropertyT') - def _apply_alias_generator(self, alias_generator: Callable[[str], str] | AliasGenerator, name: str) -> None: - """Apply an alias generator to aliases if appropriate. - Args: - alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance. - name: The name of the computed field from which to generate the alias. - """ - # Apply an alias_generator if - # 1. An alias is not specified - # 2. An alias is specified, but the priority is <= 1 +@typing.overload +def computed_field( + *, + alias: str | None = None, + alias_priority: int | None = None, + title: str | None = None, + description: str | None = None, + examples: list[Any] | None = None, + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None = None, + repr: bool = True, + return_type: Any = PydanticUndefined, +) -> typing.Callable[[PropertyT], PropertyT]: + ... - if self.alias_priority is None or self.alias_priority <= 1 or self.alias is None: - alias, _, serialization_alias = None, None, None - if isinstance(alias_generator, AliasGenerator): - alias, _, serialization_alias = alias_generator.generate_aliases(name) - elif callable(alias_generator): - alias = alias_generator(name) - - # if priority is not set, we set to 1 - # which supports the case where the alias_generator from a child class is used - # to generate an alias for a field in a parent class - if self.alias_priority is None or self.alias_priority <= 1: - self.alias_priority = 1 - - # if the priority is 1, then we set the aliases to the generated alias - # note that we use the serialization_alias with priority over alias, as computed_field - # aliases are used for serialization only (not validation) - if self.alias_priority == 1: - self.alias = _utils.get_first_not_none(serialization_alias, alias) +@typing.overload +def computed_field(__func: PropertyT) -> PropertyT: + ... def _wrapped_property_is_private(property_: cached_property | property) -> bool: # type: ignore @@ -1609,54 +1016,23 @@ def _wrapped_property_is_private(property_: cached_property | property) -> bool: return wrapped_name.startswith('_') and not wrapped_name.startswith('__') -# this should really be `property[T], cached_property[T]` but property is not generic unlike cached_property -# See https://github.com/python/typing/issues/985 and linked issues -PropertyT = TypeVar('PropertyT') - - -@overload -def computed_field(func: PropertyT, /) -> PropertyT: ... - - -@overload def computed_field( + __f: PropertyT | None = None, *, alias: str | None = None, alias_priority: int | None = None, title: str | None = None, - field_title_generator: Callable[[str, ComputedFieldInfo], str] | None = None, description: str | None = None, - deprecated: Deprecated | str | bool | None = None, examples: list[Any] | None = None, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = None, - repr: bool = True, - return_type: Any = PydanticUndefined, -) -> Callable[[PropertyT], PropertyT]: ... - - -def computed_field( - func: PropertyT | None = None, - /, - *, - alias: str | None = None, - alias_priority: int | None = None, - title: str | None = None, - field_title_generator: Callable[[str, ComputedFieldInfo], str] | None = None, - description: str | None = None, - deprecated: Deprecated | str | bool | None = None, - examples: list[Any] | None = None, - json_schema_extra: JsonDict | Callable[[JsonDict], None] | None = None, + json_schema_extra: JsonDict | typing.Callable[[JsonDict], None] | None = None, repr: bool | None = None, return_type: Any = PydanticUndefined, -) -> PropertyT | Callable[[PropertyT], PropertyT]: - """!!! abstract "Usage Documentation" - [The `computed_field` decorator](../concepts/fields.md#the-computed_field-decorator) - - Decorator to include `property` and `cached_property` when serializing models or dataclasses. +) -> PropertyT | typing.Callable[[PropertyT], PropertyT]: + """Decorator to include `property` and `cached_property` when serializing models or dataclasses. This is useful for fields that are computed from other fields, or for fields that are expensive to compute and should be cached. - ```python + ```py from pydantic import BaseModel, computed_field class Rectangle(BaseModel): @@ -1680,11 +1056,11 @@ def computed_field( Even with the `@property` or `@cached_property` applied to your function before `@computed_field`, mypy may throw a `Decorated property not supported` error. See [mypy issue #1362](https://github.com/python/mypy/issues/1362), for more information. - To avoid this error message, add `# type: ignore[prop-decorator]` to the `@computed_field` line. + To avoid this error message, add `# type: ignore[misc]` to the `@computed_field` line. [pyright](https://github.com/microsoft/pyright) supports `@computed_field` without error. - ```python + ```py import random from pydantic import BaseModel, computed_field @@ -1724,7 +1100,7 @@ def computed_field( `mypy` complains about this behavior if allowed, and `dataclasses` doesn't allow this pattern either. See the example below: - ```python + ```py from pydantic import BaseModel, computed_field class Parent(BaseModel): @@ -1738,16 +1114,14 @@ def computed_field( def a(self) -> str: return 'new a' - except TypeError as e: - print(e) - ''' - Field 'a' of class 'Child' overrides symbol of same name in a parent class. This override with a computed_field is incompatible. - ''' + except ValueError as e: + print(repr(e)) + #> ValueError("you can't override a field with a computed field") ``` Private properties decorated with `@computed_field` have `repr=False` by default. - ```python + ```py from functools import cached_property from pydantic import BaseModel, computed_field @@ -1767,22 +1141,18 @@ def computed_field( m = Model(foo=1) print(repr(m)) - #> Model(foo=1) + #> M(foo=1) ``` Args: - func: the function to wrap. + __f: the function to wrap. alias: alias to use when serializing this computed field, only used when `by_alias=True` alias_priority: priority of the alias. This affects whether an alias generator is used title: Title to use when including this computed field in JSON Schema - field_title_generator: A callable that takes a field name and returns title for it. description: Description to use when including this computed field in JSON Schema, defaults to the function's docstring - deprecated: A deprecation message (or an instance of `warnings.deprecated` or the `typing_extensions.deprecated` backport). - to be emitted when accessing the field. Or a boolean. This will automatically be set if the property is decorated with the - `deprecated` decorator. examples: Example values to use when including this computed field in JSON Schema - json_schema_extra: A dict or callable to provide extra JSON schema properties. + json_schema_extra: Dictionary of extra JSON schema properties. repr: whether to include this computed field in model repr. Default is `False` for private properties and `True` for public properties. return_type: optional return for serialization logic to expect when serializing to JSON, if included @@ -1795,40 +1165,26 @@ def computed_field( """ def dec(f: Any) -> Any: - nonlocal description, deprecated, return_type, alias_priority + nonlocal description, return_type, alias_priority unwrapped = _decorators.unwrap_wrapped_function(f) - if description is None and unwrapped.__doc__: description = inspect.cleandoc(unwrapped.__doc__) - if deprecated is None and hasattr(unwrapped, '__deprecated__'): - deprecated = unwrapped.__deprecated__ - # if the function isn't already decorated with `@property` (or another descriptor), then we wrap it now f = _decorators.ensure_property(f) alias_priority = (alias_priority or 2) if alias is not None else None if repr is None: - repr_: bool = not _wrapped_property_is_private(property_=f) + repr_: bool = False if _wrapped_property_is_private(property_=f) else True else: repr_ = repr dec_info = ComputedFieldInfo( - f, - return_type, - alias, - alias_priority, - title, - field_title_generator, - description, - deprecated, - examples, - json_schema_extra, - repr_, + f, return_type, alias, alias_priority, title, description, examples, json_schema_extra, repr_ ) return _decorators.PydanticDescriptorProxy(f, dec_info) - if func is None: + if __f is None: return dec else: - return dec(func) + return dec(__f) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/functional_serializers.py b/Backend/venv/lib/python3.12/site-packages/pydantic/functional_serializers.py index 0c1522f1..849dfe5c 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/functional_serializers.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/functional_serializers.py @@ -1,14 +1,13 @@ """This module contains related classes and functions for serialization.""" - from __future__ import annotations import dataclasses -from functools import partial, partialmethod -from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, overload +from functools import partialmethod +from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, overload from pydantic_core import PydanticUndefined, core_schema -from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler, WhenUsed -from typing_extensions import TypeAlias +from pydantic_core import core_schema as _core_schema +from typing_extensions import Annotated, Literal, TypeAlias from . import PydanticUndefinedAnnotation from ._internal import _decorators, _internal_dataclass @@ -19,26 +18,6 @@ from .annotated_handlers import GetCoreSchemaHandler class PlainSerializer: """Plain serializers use a function to modify the output of serialization. - This is particularly helpful when you want to customize the serialization for annotated types. - Consider an input of `list`, which will be serialized into a space-delimited string. - - ```python - from typing import Annotated - - from pydantic import BaseModel, PlainSerializer - - CustomStr = Annotated[ - list, PlainSerializer(lambda x: ' '.join(x), return_type=str) - ] - - class StudentModel(BaseModel): - courses: CustomStr - - student = StudentModel(courses=['Math', 'Chemistry', 'English']) - print(student.model_dump()) - #> {'courses': 'Math Chemistry English'} - ``` - Attributes: func: The serializer function. return_type: The return type for the function. If omitted it will be inferred from the type annotation. @@ -48,7 +27,7 @@ class PlainSerializer: func: core_schema.SerializerFunction return_type: Any = PydanticUndefined - when_used: WhenUsed = 'always' + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = 'always' def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: """Gets the Pydantic core schema. @@ -61,20 +40,12 @@ class PlainSerializer: The Pydantic core schema. """ schema = handler(source_type) - if self.return_type is not PydanticUndefined: - return_type = self.return_type - else: - try: - # Do not pass in globals as the function could be defined in a different module. - # Instead, let `get_callable_return_type` infer the globals to use, but still pass - # in locals that may contain a parent/rebuild namespace: - return_type = _decorators.get_callable_return_type( - self.func, - localns=handler._get_types_namespace().locals, - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - + try: + return_type = _decorators.get_function_return_type( + self.func, self.return_type, handler._get_types_namespace() + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) schema['serialization'] = core_schema.plain_serializer_function_ser_schema( function=self.func, @@ -90,58 +61,6 @@ class WrapSerializer: """Wrap serializers receive the raw inputs along with a handler function that applies the standard serialization logic, and can modify the resulting value before returning it as the final output of serialization. - For example, here's a scenario in which a wrap serializer transforms timezones to UTC **and** utilizes the existing `datetime` serialization logic. - - ```python - from datetime import datetime, timezone - from typing import Annotated, Any - - from pydantic import BaseModel, WrapSerializer - - class EventDatetime(BaseModel): - start: datetime - end: datetime - - def convert_to_utc(value: Any, handler, info) -> dict[str, datetime]: - # Note that `handler` can actually help serialize the `value` for - # further custom serialization in case it's a subclass. - partial_result = handler(value, info) - if info.mode == 'json': - return { - k: datetime.fromisoformat(v).astimezone(timezone.utc) - for k, v in partial_result.items() - } - return {k: v.astimezone(timezone.utc) for k, v in partial_result.items()} - - UTCEventDatetime = Annotated[EventDatetime, WrapSerializer(convert_to_utc)] - - class EventModel(BaseModel): - event_datetime: UTCEventDatetime - - dt = EventDatetime( - start='2024-01-01T07:00:00-08:00', end='2024-01-03T20:00:00+06:00' - ) - event = EventModel(event_datetime=dt) - print(event.model_dump()) - ''' - { - 'event_datetime': { - 'start': datetime.datetime( - 2024, 1, 1, 15, 0, tzinfo=datetime.timezone.utc - ), - 'end': datetime.datetime( - 2024, 1, 3, 14, 0, tzinfo=datetime.timezone.utc - ), - } - } - ''' - - print(event.model_dump_json()) - ''' - {"event_datetime":{"start":"2024-01-01T15:00:00Z","end":"2024-01-03T14:00:00Z"}} - ''' - ``` - Attributes: func: The serializer function to be wrapped. return_type: The return type for the function. If omitted it will be inferred from the type annotation. @@ -151,7 +70,7 @@ class WrapSerializer: func: core_schema.WrapSerializerFunction return_type: Any = PydanticUndefined - when_used: WhenUsed = 'always' + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = 'always' def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: """This method is used to get the Pydantic core schema of the class. @@ -164,20 +83,12 @@ class WrapSerializer: The generated core schema of the class. """ schema = handler(source_type) - if self.return_type is not PydanticUndefined: - return_type = self.return_type - else: - try: - # Do not pass in globals as the function could be defined in a different module. - # Instead, let `get_callable_return_type` infer the globals to use, but still pass - # in locals that may contain a parent/rebuild namespace: - return_type = _decorators.get_callable_return_type( - self.func, - localns=handler._get_types_namespace().locals, - ) - except NameError as e: - raise PydanticUndefinedAnnotation.from_name_error(e) from e - + try: + return_type = _decorators.get_function_return_type( + self.func, self.return_type, handler._get_types_namespace() + ) + except NameError as e: + raise PydanticUndefinedAnnotation.from_name_error(e) from e return_schema = None if return_type is PydanticUndefined else handler.generate_schema(return_type) schema['serialization'] = core_schema.wrap_serializer_function_ser_schema( function=self.func, @@ -189,77 +100,58 @@ class WrapSerializer: if TYPE_CHECKING: - _Partial: TypeAlias = 'partial[Any] | partialmethod[Any]' - - FieldPlainSerializer: TypeAlias = 'core_schema.SerializerFunction | _Partial' - """A field serializer method or function in `plain` mode.""" - - FieldWrapSerializer: TypeAlias = 'core_schema.WrapSerializerFunction | _Partial' - """A field serializer method or function in `wrap` mode.""" - - FieldSerializer: TypeAlias = 'FieldPlainSerializer | FieldWrapSerializer' - """A field serializer method or function.""" - - _FieldPlainSerializerT = TypeVar('_FieldPlainSerializerT', bound=FieldPlainSerializer) - _FieldWrapSerializerT = TypeVar('_FieldWrapSerializerT', bound=FieldWrapSerializer) + _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] + _PlainSerializationFunction = Union[_core_schema.SerializerFunction, _PartialClsOrStaticMethod] + _WrapSerializationFunction = Union[_core_schema.WrapSerializerFunction, _PartialClsOrStaticMethod] + _PlainSerializeMethodType = TypeVar('_PlainSerializeMethodType', bound=_PlainSerializationFunction) + _WrapSerializeMethodType = TypeVar('_WrapSerializeMethodType', bound=_WrapSerializationFunction) @overload def field_serializer( - field: str, - /, + __field: str, + *fields: str, + return_type: Any = ..., + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = ..., + check_fields: bool | None = ..., +) -> Callable[[_PlainSerializeMethodType], _PlainSerializeMethodType]: + ... + + +@overload +def field_serializer( + __field: str, + *fields: str, + mode: Literal['plain'], + return_type: Any = ..., + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = ..., + check_fields: bool | None = ..., +) -> Callable[[_PlainSerializeMethodType], _PlainSerializeMethodType]: + ... + + +@overload +def field_serializer( + __field: str, *fields: str, mode: Literal['wrap'], return_type: Any = ..., - when_used: WhenUsed = ..., + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = ..., check_fields: bool | None = ..., -) -> Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT]: ... - - -@overload -def field_serializer( - field: str, - /, - *fields: str, - mode: Literal['plain'] = ..., - return_type: Any = ..., - when_used: WhenUsed = ..., - check_fields: bool | None = ..., -) -> Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT]: ... +) -> Callable[[_WrapSerializeMethodType], _WrapSerializeMethodType]: + ... def field_serializer( *fields: str, mode: Literal['plain', 'wrap'] = 'plain', - # TODO PEP 747 (grep for 'return_type' on the whole code base): return_type: Any = PydanticUndefined, - when_used: WhenUsed = 'always', + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = 'always', check_fields: bool | None = None, -) -> ( - Callable[[_FieldWrapSerializerT], _FieldWrapSerializerT] - | Callable[[_FieldPlainSerializerT], _FieldPlainSerializerT] -): +) -> Callable[[Any], Any]: """Decorator that enables custom field serialization. - In the below example, a field of type `set` is used to mitigate duplication. A `field_serializer` is used to serialize the data as a sorted list. - - ```python - from pydantic import BaseModel, field_serializer - - class StudentModel(BaseModel): - name: str = 'Jane' - courses: set[str] - - @field_serializer('courses', when_used='json') - def serialize_courses_in_order(self, courses: set[str]): - return sorted(courses) - - student = StudentModel(courses={'Math', 'Chemistry', 'English'}) - print(student.model_dump_json()) - #> {"name":"Jane","courses":["Chemistry","English","Math"]} - ``` - - See [the usage documentation](../concepts/serialization.md#serializers) for more information. + See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information. Four signatures are supported: @@ -283,7 +175,9 @@ def field_serializer( The decorator function. """ - def dec(f: FieldSerializer) -> _decorators.PydanticDescriptorProxy[Any]: + def dec( + f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any] + ) -> _decorators.PydanticDescriptorProxy[Any]: dec_info = _decorators.FieldSerializerDecoratorInfo( fields=fields, mode=mode, @@ -291,109 +185,42 @@ def field_serializer( when_used=when_used, check_fields=check_fields, ) - return _decorators.PydanticDescriptorProxy(f, dec_info) # pyright: ignore[reportArgumentType] + return _decorators.PydanticDescriptorProxy(f, dec_info) - return dec # pyright: ignore[reportReturnType] + return dec -if TYPE_CHECKING: - # The first argument in the following callables represent the `self` type: - - ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any] - """A model serializer method with the `info` argument, in `plain` mode.""" - - ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any] - """A model serializer method without the `info` argument, in `plain` mode.""" - - ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo' - """A model serializer method in `plain` mode.""" - - ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any] - """A model serializer method with the `info` argument, in `wrap` mode.""" - - ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any] - """A model serializer method without the `info` argument, in `wrap` mode.""" - - ModelWrapSerializer: TypeAlias = 'ModelWrapSerializerWithInfo | ModelWrapSerializerWithoutInfo' - """A model serializer method in `wrap` mode.""" - - ModelSerializer: TypeAlias = 'ModelPlainSerializer | ModelWrapSerializer' - - _ModelPlainSerializerT = TypeVar('_ModelPlainSerializerT', bound=ModelPlainSerializer) - _ModelWrapSerializerT = TypeVar('_ModelWrapSerializerT', bound=ModelWrapSerializer) +FuncType = TypeVar('FuncType', bound=Callable[..., Any]) @overload -def model_serializer(f: _ModelPlainSerializerT, /) -> _ModelPlainSerializerT: ... - - -@overload -def model_serializer( - *, mode: Literal['wrap'], when_used: WhenUsed = 'always', return_type: Any = ... -) -> Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT]: ... +def model_serializer(__f: FuncType) -> FuncType: + ... @overload def model_serializer( *, - mode: Literal['plain'] = ..., - when_used: WhenUsed = 'always', + mode: Literal['plain', 'wrap'] = ..., + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = 'always', return_type: Any = ..., -) -> Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT]: ... +) -> Callable[[FuncType], FuncType]: + ... def model_serializer( - f: _ModelPlainSerializerT | _ModelWrapSerializerT | None = None, - /, + __f: Callable[..., Any] | None = None, *, mode: Literal['plain', 'wrap'] = 'plain', - when_used: WhenUsed = 'always', + when_used: Literal['always', 'unless-none', 'json', 'json-unless-none'] = 'always', return_type: Any = PydanticUndefined, -) -> ( - _ModelPlainSerializerT - | Callable[[_ModelWrapSerializerT], _ModelWrapSerializerT] - | Callable[[_ModelPlainSerializerT], _ModelPlainSerializerT] -): +) -> Callable[[Any], Any]: """Decorator that enables custom model serialization. - This is useful when a model need to be serialized in a customized manner, allowing for flexibility beyond just specific fields. - - An example would be to serialize temperature to the same temperature scale, such as degrees Celsius. - - ```python - from typing import Literal - - from pydantic import BaseModel, model_serializer - - class TemperatureModel(BaseModel): - unit: Literal['C', 'F'] - value: int - - @model_serializer() - def serialize_model(self): - if self.unit == 'F': - return {'unit': 'C', 'value': int((self.value - 32) / 1.8)} - return {'unit': self.unit, 'value': self.value} - - temperature = TemperatureModel(unit='F', value=212) - print(temperature.model_dump()) - #> {'unit': 'C', 'value': 100} - ``` - - Two signatures are supported for `mode='plain'`, which is the default: - - - `(self)` - - `(self, info: SerializationInfo)` - - And two other signatures for `mode='wrap'`: - - - `(self, nxt: SerializerFunctionWrapHandler)` - - `(self, nxt: SerializerFunctionWrapHandler, info: SerializationInfo)` - - See [the usage documentation](../concepts/serialization.md#serializers) for more information. + See [Custom serializers](../concepts/serialization.md#custom-serializers) for more information. Args: - f: The function to be decorated. + __f: The function to be decorated. mode: The serialization mode. - `'plain'` means the function will be called instead of the default serialization logic @@ -406,14 +233,14 @@ def model_serializer( The decorator function. """ - def dec(f: ModelSerializer) -> _decorators.PydanticDescriptorProxy[Any]: + def dec(f: Callable[..., Any]) -> _decorators.PydanticDescriptorProxy[Any]: dec_info = _decorators.ModelSerializerDecoratorInfo(mode=mode, return_type=return_type, when_used=when_used) return _decorators.PydanticDescriptorProxy(f, dec_info) - if f is None: - return dec # pyright: ignore[reportReturnType] + if __f is None: + return dec else: - return dec(f) # pyright: ignore[reportReturnType] + return dec(__f) # type: ignore AnyType = TypeVar('AnyType') @@ -421,19 +248,15 @@ AnyType = TypeVar('AnyType') if TYPE_CHECKING: SerializeAsAny = Annotated[AnyType, ...] # SerializeAsAny[list[str]] will be treated by type checkers as list[str] - """Annotation used to mark a type as having duck-typing serialization behavior. - - See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. + """Force serialization to ignore whatever is defined in the schema and instead ask the object + itself how it should be serialized. + In particular, this means that when model subclasses are serialized, fields present in the subclass + but not in the original schema will be included. """ else: @dataclasses.dataclass(**_internal_dataclass.slots_true) - class SerializeAsAny: - """Annotation used to mark a type as having duck-typing serialization behavior. - - See [usage documentation](../concepts/serialization.md#serializing-with-duck-typing) for more details. - """ - + class SerializeAsAny: # noqa: D101 def __class_getitem__(cls, item: Any) -> Any: return Annotated[item, SerializeAsAny()] @@ -445,7 +268,9 @@ else: while schema_to_update['type'] == 'definitions': schema_to_update = schema_to_update.copy() schema_to_update = schema_to_update['schema'] - schema_to_update['serialization'] = core_schema.simple_ser_schema('any') + schema_to_update['serialization'] = core_schema.wrap_serializer_function_ser_schema( + lambda x, h: h(x), schema=core_schema.any_schema() + ) return schema __hash__ = object.__hash__ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/functional_validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/functional_validators.py index fc4bbba6..df5d6c7e 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/functional_validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/functional_validators.py @@ -4,19 +4,18 @@ from __future__ import annotations as _annotations import dataclasses import sys -import warnings from functools import partialmethod from types import FunctionType -from typing import TYPE_CHECKING, Annotated, Any, Callable, Literal, TypeVar, Union, cast, overload +from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast, overload -from pydantic_core import PydanticUndefined, core_schema -from typing_extensions import Self, TypeAlias +from pydantic_core import core_schema +from pydantic_core import core_schema as _core_schema +from typing_extensions import Annotated, Literal, TypeAlias -from ._internal import _decorators, _generics, _internal_dataclass +from . import GetCoreSchemaHandler as _GetCoreSchemaHandler +from ._internal import _core_metadata, _decorators, _generics, _internal_dataclass from .annotated_handlers import GetCoreSchemaHandler from .errors import PydanticUserError -from .version import version_short -from .warnings import ArbitraryTypeWarning, PydanticDeprecatedSince212 if sys.version_info < (3, 11): from typing_extensions import Protocol @@ -28,8 +27,7 @@ _inspect_validator = _decorators.inspect_validator @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class AfterValidator: - """!!! abstract "Usage Documentation" - [field *after* validators](../concepts/validators.md#field-after-validator) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **after** the inner validation logic. @@ -37,8 +35,8 @@ class AfterValidator: func: The validator function. Example: - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import AfterValidator, BaseModel, ValidationError @@ -72,36 +70,29 @@ class AfterValidator: func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction - def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) - info_arg = _inspect_validator(self.func, mode='after', type='field') + info_arg = _inspect_validator(self.func, 'after') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) - return core_schema.with_info_after_validator_function(func, schema=schema) + return core_schema.with_info_after_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) return core_schema.no_info_after_validator_function(func, schema=schema) - @classmethod - def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: - return cls(func=decorator.func) - @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class BeforeValidator: - """!!! abstract "Usage Documentation" - [field *before* validators](../concepts/validators.md#field-before-validator) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **before** the inner validation logic. Attributes: func: The validator function. - json_schema_input_type: The input type used to generate the appropriate - JSON Schema (in validation mode). The actual input type is `Any`. Example: - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, BeforeValidator @@ -122,153 +113,68 @@ class BeforeValidator: """ func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction - json_schema_input_type: Any = PydanticUndefined - def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) - input_schema = ( - None - if self.json_schema_input_type is PydanticUndefined - else handler.generate_schema(self.json_schema_input_type) - ) - - info_arg = _inspect_validator(self.func, mode='before', type='field') + info_arg = _inspect_validator(self.func, 'before') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) - return core_schema.with_info_before_validator_function( - func, - schema=schema, - json_schema_input_schema=input_schema, - ) + return core_schema.with_info_before_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) - return core_schema.no_info_before_validator_function( - func, schema=schema, json_schema_input_schema=input_schema - ) - - @classmethod - def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: - return cls( - func=decorator.func, - json_schema_input_type=decorator.info.json_schema_input_type, - ) + return core_schema.no_info_before_validator_function(func, schema=schema) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class PlainValidator: - """!!! abstract "Usage Documentation" - [field *plain* validators](../concepts/validators.md#field-plain-validator) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **instead** of the inner validation logic. - !!! note - Before v2.9, `PlainValidator` wasn't always compatible with JSON Schema generation for `mode='validation'`. - You can now use the `json_schema_input_type` argument to specify the input type of the function - to be used in the JSON schema when `mode='validation'` (the default). See the example below for more details. - Attributes: func: The validator function. - json_schema_input_type: The input type used to generate the appropriate - JSON Schema (in validation mode). The actual input type is `Any`. Example: - ```python - from typing import Annotated, Union + ```py + from typing_extensions import Annotated from pydantic import BaseModel, PlainValidator - def validate(v: object) -> int: - if not isinstance(v, (int, str)): - raise ValueError(f'Expected int or str, go {type(v)}') - - return int(v) + 1 - - MyInt = Annotated[ - int, - PlainValidator(validate, json_schema_input_type=Union[str, int]), # (1)! - ] + MyInt = Annotated[int, PlainValidator(lambda v: int(v) + 1)] class Model(BaseModel): a: MyInt print(Model(a='1').a) #> 2 - - print(Model(a=1).a) - #> 2 ``` - - 1. In this example, we've specified the `json_schema_input_type` as `Union[str, int]` which indicates to the JSON schema - generator that in validation mode, the input type for the `a` field can be either a [`str`][] or an [`int`][]. """ func: core_schema.NoInfoValidatorFunction | core_schema.WithInfoValidatorFunction - json_schema_input_type: Any = Any - def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - # Note that for some valid uses of PlainValidator, it is not possible to generate a core schema for the - # source_type, so calling `handler(source_type)` will error, which prevents us from generating a proper - # serialization schema. To work around this for use cases that will not involve serialization, we simply - # catch any PydanticSchemaGenerationError that may be raised while attempting to build the serialization schema - # and abort any attempts to handle special serialization. - from pydantic import PydanticSchemaGenerationError - - try: - schema = handler(source_type) - # TODO if `schema['serialization']` is one of `'include-exclude-dict/sequence', - # schema validation will fail. That's why we use 'type ignore' comments below. - serialization = schema.get( - 'serialization', - core_schema.wrap_serializer_function_ser_schema( - function=lambda v, h: h(v), - schema=schema, - return_schema=handler.generate_schema(source_type), - ), - ) - except PydanticSchemaGenerationError: - serialization = None - - input_schema = handler.generate_schema(self.json_schema_input_type) - - info_arg = _inspect_validator(self.func, mode='plain', type='field') + def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: + info_arg = _inspect_validator(self.func, 'plain') if info_arg: func = cast(core_schema.WithInfoValidatorFunction, self.func) - return core_schema.with_info_plain_validator_function( - func, - serialization=serialization, # pyright: ignore[reportArgumentType] - json_schema_input_schema=input_schema, - ) + return core_schema.with_info_plain_validator_function(func, field_name=handler.field_name) else: func = cast(core_schema.NoInfoValidatorFunction, self.func) - return core_schema.no_info_plain_validator_function( - func, - serialization=serialization, # pyright: ignore[reportArgumentType] - json_schema_input_schema=input_schema, - ) - - @classmethod - def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: - return cls( - func=decorator.func, - json_schema_input_type=decorator.info.json_schema_input_type, - ) + return core_schema.no_info_plain_validator_function(func) @dataclasses.dataclass(frozen=True, **_internal_dataclass.slots_true) class WrapValidator: - """!!! abstract "Usage Documentation" - [field *wrap* validators](../concepts/validators.md#field-wrap-validator) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#annotated-validators A metadata class that indicates that a validation should be applied **around** the inner validation logic. Attributes: func: The validator function. - json_schema_input_type: The input type used to generate the appropriate - JSON Schema (in validation mode). The actual input type is `Any`. - ```python + ```py from datetime import datetime - from typing import Annotated + + from typing_extensions import Annotated from pydantic import BaseModel, ValidationError, WrapValidator @@ -295,133 +201,95 @@ class WrapValidator: """ func: core_schema.NoInfoWrapValidatorFunction | core_schema.WithInfoWrapValidatorFunction - json_schema_input_type: Any = PydanticUndefined - def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: + def __get_pydantic_core_schema__(self, source_type: Any, handler: _GetCoreSchemaHandler) -> core_schema.CoreSchema: schema = handler(source_type) - input_schema = ( - None - if self.json_schema_input_type is PydanticUndefined - else handler.generate_schema(self.json_schema_input_type) - ) - - info_arg = _inspect_validator(self.func, mode='wrap', type='field') + info_arg = _inspect_validator(self.func, 'wrap') if info_arg: func = cast(core_schema.WithInfoWrapValidatorFunction, self.func) - return core_schema.with_info_wrap_validator_function( - func, - schema=schema, - json_schema_input_schema=input_schema, - ) + return core_schema.with_info_wrap_validator_function(func, schema=schema, field_name=handler.field_name) else: func = cast(core_schema.NoInfoWrapValidatorFunction, self.func) - return core_schema.no_info_wrap_validator_function( - func, - schema=schema, - json_schema_input_schema=input_schema, - ) - - @classmethod - def _from_decorator(cls, decorator: _decorators.Decorator[_decorators.FieldValidatorDecoratorInfo]) -> Self: - return cls( - func=decorator.func, - json_schema_input_type=decorator.info.json_schema_input_type, - ) + return core_schema.no_info_wrap_validator_function(func, schema=schema) if TYPE_CHECKING: class _OnlyValueValidatorClsMethod(Protocol): - def __call__(self, cls: Any, value: Any, /) -> Any: ... + def __call__(self, __cls: Any, __value: Any) -> Any: + ... class _V2ValidatorClsMethod(Protocol): - def __call__(self, cls: Any, value: Any, info: core_schema.ValidationInfo[Any], /) -> Any: ... - - class _OnlyValueWrapValidatorClsMethod(Protocol): - def __call__(self, cls: Any, value: Any, handler: core_schema.ValidatorFunctionWrapHandler, /) -> Any: ... + def __call__(self, __cls: Any, __input_value: Any, __info: _core_schema.ValidationInfo) -> Any: + ... class _V2WrapValidatorClsMethod(Protocol): def __call__( self, - cls: Any, - value: Any, - handler: core_schema.ValidatorFunctionWrapHandler, - info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... + __cls: Any, + __input_value: Any, + __validator: _core_schema.ValidatorFunctionWrapHandler, + __info: _core_schema.ValidationInfo, + ) -> Any: + ... _V2Validator = Union[ _V2ValidatorClsMethod, - core_schema.WithInfoValidatorFunction, + _core_schema.WithInfoValidatorFunction, _OnlyValueValidatorClsMethod, - core_schema.NoInfoValidatorFunction, + _core_schema.NoInfoValidatorFunction, ] _V2WrapValidator = Union[ _V2WrapValidatorClsMethod, - core_schema.WithInfoWrapValidatorFunction, - _OnlyValueWrapValidatorClsMethod, - core_schema.NoInfoWrapValidatorFunction, + _core_schema.WithInfoWrapValidatorFunction, ] _PartialClsOrStaticMethod: TypeAlias = Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any]] _V2BeforeAfterOrPlainValidatorType = TypeVar( '_V2BeforeAfterOrPlainValidatorType', - bound=Union[_V2Validator, _PartialClsOrStaticMethod], + _V2Validator, + _PartialClsOrStaticMethod, ) - _V2WrapValidatorType = TypeVar('_V2WrapValidatorType', bound=Union[_V2WrapValidator, _PartialClsOrStaticMethod]) + _V2WrapValidatorType = TypeVar('_V2WrapValidatorType', _V2WrapValidator, _PartialClsOrStaticMethod) + + +@overload +def field_validator( + __field: str, + *fields: str, + mode: Literal['before', 'after', 'plain'] = ..., + check_fields: bool | None = ..., +) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: + ... + + +@overload +def field_validator( + __field: str, + *fields: str, + mode: Literal['wrap'], + check_fields: bool | None = ..., +) -> Callable[[_V2WrapValidatorType], _V2WrapValidatorType]: + ... + FieldValidatorModes: TypeAlias = Literal['before', 'after', 'wrap', 'plain'] -@overload def field_validator( - field: str, - /, - *fields: str, - mode: Literal['wrap'], - check_fields: bool | None = ..., - json_schema_input_type: Any = ..., -) -> Callable[[_V2WrapValidatorType], _V2WrapValidatorType]: ... - - -@overload -def field_validator( - field: str, - /, - *fields: str, - mode: Literal['before', 'plain'], - check_fields: bool | None = ..., - json_schema_input_type: Any = ..., -) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: ... - - -@overload -def field_validator( - field: str, - /, - *fields: str, - mode: Literal['after'] = ..., - check_fields: bool | None = ..., -) -> Callable[[_V2BeforeAfterOrPlainValidatorType], _V2BeforeAfterOrPlainValidatorType]: ... - - -def field_validator( - field: str, - /, + __field: str, *fields: str, mode: FieldValidatorModes = 'after', check_fields: bool | None = None, - json_schema_input_type: Any = PydanticUndefined, ) -> Callable[[Any], Any]: - """!!! abstract "Usage Documentation" - [field validators](../concepts/validators.md#field-validators) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#field-validators Decorate methods on the class indicating that they should be used to validate fields. Example usage: - ```python + ```py from typing import Any from pydantic import ( @@ -457,14 +325,11 @@ def field_validator( For more in depth examples, see [Field Validators](../concepts/validators.md#field-validators). Args: - field: The first field the `field_validator` should be called on; this is separate + __field: The first field the `field_validator` should be called on; this is separate from `fields` to ensure an error is raised if you don't pass at least one. *fields: Additional field(s) the `field_validator` should be called on. mode: Specifies whether to validate the fields before or after validation. check_fields: Whether to check that the fields actually exist on the model. - json_schema_input_type: The input type of the function. This is only used to generate - the appropriate JSON Schema (in validation mode) and can only specified - when `mode` is either `'before'`, `'plain'` or `'wrap'`. Returns: A decorator that can be used to decorate a function to be used as a field_validator. @@ -475,23 +340,13 @@ def field_validator( - If the args passed to `@field_validator` as fields are not strings. - If `@field_validator` applied to instance methods. """ - if isinstance(field, FunctionType): + if isinstance(__field, FunctionType): raise PydanticUserError( '`@field_validator` should be used with fields and keyword arguments, not bare. ' "E.g. usage should be `@validator('', ...)`", code='validator-no-fields', ) - - if mode not in ('before', 'plain', 'wrap') and json_schema_input_type is not PydanticUndefined: - raise PydanticUserError( - f"`json_schema_input_type` can't be used when mode is set to {mode!r}", - code='validator-input-type', - ) - - if json_schema_input_type is PydanticUndefined and mode == 'plain': - json_schema_input_type = Any - - fields = field, *fields + fields = __field, *fields if not all(isinstance(field, str) for field in fields): raise PydanticUserError( '`@field_validator` fields should be passed as separate string args. ' @@ -500,7 +355,7 @@ def field_validator( ) def dec( - f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any], + f: Callable[..., Any] | staticmethod[Any, Any] | classmethod[Any, Any, Any] ) -> _decorators.PydanticDescriptorProxy[Any]: if _decorators.is_instance_method_from_sig(f): raise PydanticUserError( @@ -510,9 +365,7 @@ def field_validator( # auto apply the @classmethod decorator f = _decorators.ensure_classmethod_based_on_signature(f) - dec_info = _decorators.FieldValidatorDecoratorInfo( - fields=fields, mode=mode, check_fields=check_fields, json_schema_input_type=json_schema_input_type - ) + dec_info = _decorators.FieldValidatorDecoratorInfo(fields=fields, mode=mode, check_fields=check_fields) return _decorators.PydanticDescriptorProxy(f, dec_info) return dec @@ -522,20 +375,17 @@ _ModelType = TypeVar('_ModelType') _ModelTypeCo = TypeVar('_ModelTypeCo', covariant=True) -class ModelWrapValidatorHandler(core_schema.ValidatorFunctionWrapHandler, Protocol[_ModelTypeCo]): - """`@model_validator` decorated function handler argument type. This is used when `mode='wrap'`.""" +class ModelWrapValidatorHandler(_core_schema.ValidatorFunctionWrapHandler, Protocol[_ModelTypeCo]): + """@model_validator decorated function handler argument type. This is used when `mode='wrap'`.""" def __call__( # noqa: D102 - self, - value: Any, - outer_location: str | int | None = None, - /, + self, input_value: Any, outer_location: str | int | None = None ) -> _ModelTypeCo: # pragma: no cover ... class ModelWrapValidatorWithoutInfo(Protocol[_ModelType]): - """A `@model_validator` decorated function signature. + """A @model_validator decorated function signature. This is used when `mode='wrap'` and the function does not have info argument. """ @@ -545,14 +395,14 @@ class ModelWrapValidatorWithoutInfo(Protocol[_ModelType]): # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases - value: Any, - handler: ModelWrapValidatorHandler[_ModelType], - /, - ) -> _ModelType: ... + __value: Any, + __handler: ModelWrapValidatorHandler[_ModelType], + ) -> _ModelType: + ... class ModelWrapValidator(Protocol[_ModelType]): - """A `@model_validator` decorated function signature. This is used when `mode='wrap'`.""" + """A @model_validator decorated function signature. This is used when `mode='wrap'`.""" def __call__( # noqa: D102 self, @@ -560,30 +410,15 @@ class ModelWrapValidator(Protocol[_ModelType]): # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases - value: Any, - handler: ModelWrapValidatorHandler[_ModelType], - info: core_schema.ValidationInfo, - /, - ) -> _ModelType: ... - - -class FreeModelBeforeValidatorWithoutInfo(Protocol): - """A `@model_validator` decorated function signature. - This is used when `mode='before'` and the function does not have info argument. - """ - - def __call__( # noqa: D102 - self, - # this can be a dict, a model instance - # or anything else that gets passed to validate_python - # thus validators _must_ handle all cases - value: Any, - /, - ) -> Any: ... + __value: Any, + __handler: ModelWrapValidatorHandler[_ModelType], + __info: _core_schema.ValidationInfo, + ) -> _ModelType: + ... class ModelBeforeValidatorWithoutInfo(Protocol): - """A `@model_validator` decorated function signature. + """A @model_validator decorated function signature. This is used when `mode='before'` and the function does not have info argument. """ @@ -593,23 +428,9 @@ class ModelBeforeValidatorWithoutInfo(Protocol): # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases - value: Any, - /, - ) -> Any: ... - - -class FreeModelBeforeValidator(Protocol): - """A `@model_validator` decorated function signature. This is used when `mode='before'`.""" - - def __call__( # noqa: D102 - self, - # this can be a dict, a model instance - # or anything else that gets passed to validate_python - # thus validators _must_ handle all cases - value: Any, - info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... + __value: Any, + ) -> Any: + ... class ModelBeforeValidator(Protocol): @@ -621,10 +442,10 @@ class ModelBeforeValidator(Protocol): # this can be a dict, a model instance # or anything else that gets passed to validate_python # thus validators _must_ handle all cases - value: Any, - info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... + __value: Any, + __info: _core_schema.ValidationInfo, + ) -> Any: + ... ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType] @@ -632,13 +453,11 @@ ModelAfterValidatorWithoutInfo = Callable[[_ModelType], _ModelType] have info argument. """ -ModelAfterValidator = Callable[[_ModelType, core_schema.ValidationInfo[Any]], _ModelType] +ModelAfterValidator = Callable[[_ModelType, _core_schema.ValidationInfo], _ModelType] """A `@model_validator` decorated function signature. This is used when `mode='after'`.""" _AnyModelWrapValidator = Union[ModelWrapValidator[_ModelType], ModelWrapValidatorWithoutInfo[_ModelType]] -_AnyModelBeforeValidator = Union[ - FreeModelBeforeValidator, ModelBeforeValidator, FreeModelBeforeValidatorWithoutInfo, ModelBeforeValidatorWithoutInfo -] +_AnyModeBeforeValidator = Union[ModelBeforeValidator, ModelBeforeValidatorWithoutInfo] _AnyModelAfterValidator = Union[ModelAfterValidator[_ModelType], ModelAfterValidatorWithoutInfo[_ModelType]] @@ -648,16 +467,16 @@ def model_validator( mode: Literal['wrap'], ) -> Callable[ [_AnyModelWrapValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] -]: ... +]: + ... @overload def model_validator( *, mode: Literal['before'], -) -> Callable[ - [_AnyModelBeforeValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] -]: ... +) -> Callable[[_AnyModeBeforeValidator], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo]]: + ... @overload @@ -666,33 +485,33 @@ def model_validator( mode: Literal['after'], ) -> Callable[ [_AnyModelAfterValidator[_ModelType]], _decorators.PydanticDescriptorProxy[_decorators.ModelValidatorDecoratorInfo] -]: ... +]: + ... def model_validator( *, mode: Literal['wrap', 'before', 'after'], ) -> Any: - """!!! abstract "Usage Documentation" - [Model Validators](../concepts/validators.md#model-validators) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validators/#model-validators Decorate model methods for validation purposes. Example usage: - ```python - from typing_extensions import Self + ```py + from typing import Optional from pydantic import BaseModel, ValidationError, model_validator class Square(BaseModel): - width: float - height: float + width: float + height: float - @model_validator(mode='after') - def verify_square(self) -> Self: - if self.width != self.height: - raise ValueError('width and height do not match') - return self + @model_validator(mode='after') + def verify_square(self) -> 'Rectangle': + if self.width != self.height: + raise ValueError('width and height do not match') + return self s = Square(width=1, height=1) print(repr(s)) @@ -704,7 +523,8 @@ def model_validator( print(e) ''' 1 validation error for Square - Value error, width and height do not match [type=value_error, input_value={'width': 1, 'height': 2}, input_type=dict] + __root__ + width and height do not match (type=value_error) ''' ``` @@ -719,18 +539,8 @@ def model_validator( """ def dec(f: Any) -> _decorators.PydanticDescriptorProxy[Any]: - # auto apply the @classmethod decorator. NOTE: in V3, do not apply the conversion for 'after' validators: + # auto apply the @classmethod decorator f = _decorators.ensure_classmethod_based_on_signature(f) - if mode == 'after' and isinstance(f, classmethod): - warnings.warn( - category=PydanticDeprecatedSince212, - message=( - "Using `@model_validator` with mode='after' on a classmethod is deprecated. Instead, use an instance method. " - f'See the documentation at https://docs.pydantic.dev/{version_short()}/concepts/validators/#model-after-validator.' - ), - stacklevel=2, - ) - dec_info = _decorators.ModelValidatorDecoratorInfo(mode=mode) return _decorators.PydanticDescriptorProxy(f, dec_info) @@ -751,7 +561,7 @@ else: '''Generic type for annotating a type that is an instance of a given class. Example: - ```python + ```py from pydantic import BaseModel, InstanceOf class Foo: @@ -829,10 +639,8 @@ else: @classmethod def __get_pydantic_core_schema__(cls, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - with warnings.catch_warnings(): - warnings.simplefilter('ignore', ArbitraryTypeWarning) - original_schema = handler(source) - metadata = {'pydantic_js_annotation_functions': [lambda _c, h: h(original_schema)]} + original_schema = handler(source) + metadata = _core_metadata.build_metadata_dict(js_annotation_functions=[lambda _c, h: h(original_schema)]) return core_schema.any_schema( metadata=metadata, serialization=core_schema.wrap_serializer_function_ser_schema( @@ -841,53 +649,3 @@ else: ) __hash__ = object.__hash__ - - -_FromTypeT = TypeVar('_FromTypeT') - - -class ValidateAs: - """A helper class to validate a custom type from a type that is natively supported by Pydantic. - - Args: - from_type: The type natively supported by Pydantic to use to perform validation. - instantiation_hook: A callable taking the validated type as an argument, and returning - the populated custom type. - - Example: - ```python {lint="skip"} - from typing import Annotated - - from pydantic import BaseModel, TypeAdapter, ValidateAs - - class MyCls: - def __init__(self, a: int) -> None: - self.a = a - - def __repr__(self) -> str: - return f"MyCls(a={self.a})" - - class Model(BaseModel): - a: int - - - ta = TypeAdapter( - Annotated[MyCls, ValidateAs(Model, lambda v: MyCls(a=v.a))] - ) - - print(ta.validate_python({'a': 1})) - #> MyCls(a=1) - ``` - """ - - # TODO: make use of PEP 747 - def __init__(self, from_type: type[_FromTypeT], /, instantiation_hook: Callable[[_FromTypeT], Any]) -> None: - self.from_type = from_type - self.instantiation_hook = instantiation_hook - - def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - schema = handler(self.from_type) - return core_schema.no_info_after_validator_function( - self.instantiation_hook, - schema=schema, - ) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/generics.py b/Backend/venv/lib/python3.12/site-packages/pydantic/generics.py index 3f1070d0..5f6f7f7a 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/generics.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/generics.py @@ -1,5 +1,4 @@ """The `generics` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/json.py b/Backend/venv/lib/python3.12/site-packages/pydantic/json.py index bcaff9f5..020fb6d2 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/json.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/json.py @@ -1,5 +1,4 @@ """The `json` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/json_schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/json_schema.py index a16afb89..636669c7 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/json_schema.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/json_schema.py @@ -1,47 +1,44 @@ -"""!!! abstract "Usage Documentation" - [JSON Schema](../concepts/json_schema.md) +""" +Usage docs: https://docs.pydantic.dev/2.5/concepts/json_schema/ The `json_schema` module contains classes and functions to allow the way [JSON Schema](https://json-schema.org/) is generated to be customized. -In general you shouldn't need to use this module directly; instead, you can use +In general you shouldn't need to use this module directly; instead, you can [`BaseModel.model_json_schema`][pydantic.BaseModel.model_json_schema] and [`TypeAdapter.json_schema`][pydantic.TypeAdapter.json_schema]. """ - from __future__ import annotations as _annotations import dataclasses import inspect import math -import os import re import warnings -from collections import Counter, defaultdict -from collections.abc import Hashable, Iterable, Sequence +from collections import defaultdict from copy import deepcopy +from dataclasses import is_dataclass from enum import Enum -from re import Pattern from typing import ( TYPE_CHECKING, - Annotated, Any, Callable, - Literal, + Counter, + Dict, + Hashable, + Iterable, NewType, + Sequence, + Tuple, TypeVar, Union, cast, - overload, ) import pydantic_core -from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python +from pydantic_core import CoreSchema, PydanticOmit, core_schema, to_jsonable_python from pydantic_core.core_schema import ComputedField -from typing_extensions import TypeAlias, assert_never, deprecated, final -from typing_inspection.introspection import get_literal_values - -from pydantic.warnings import PydanticDeprecatedSince26, PydanticDeprecatedSince29 +from typing_extensions import Annotated, Literal, TypeAlias, assert_never from ._internal import ( _config, @@ -51,10 +48,11 @@ from ._internal import ( _internal_dataclass, _mock_val_ser, _schema_generation_shared, + _typing_extra, ) from .annotated_handlers import GetJsonSchemaHandler -from .config import JsonDict, JsonValue -from .errors import PydanticInvalidForJsonSchema, PydanticSchemaGenerationError, PydanticUserError +from .config import JsonDict, JsonSchemaExtraCallable, JsonValue +from .errors import PydanticInvalidForJsonSchema, PydanticUserError if TYPE_CHECKING: from . import ConfigDict @@ -71,9 +69,9 @@ A type alias for defined schema types that represents a union of `core_schema.CoreSchemaFieldType`. """ -JsonSchemaValue = dict[str, Any] +JsonSchemaValue = Dict[str, Any] """ -A type alias for a JSON schema value. This is a dictionary of string keys to arbitrary JSON values. +A type alias for a JSON schema value. This is a dictionary of string keys to arbitrary values. """ JsonSchemaMode = Literal['validation', 'serialization'] @@ -89,7 +87,23 @@ for validation inputs, or that will be matched by serialization outputs. _MODE_TITLE_MAPPING: dict[JsonSchemaMode, str] = {'validation': 'Input', 'serialization': 'Output'} -JsonSchemaWarningKind = Literal['skipped-choice', 'non-serializable-default', 'skipped-discriminator'] +def update_json_schema(schema: JsonSchemaValue, updates: dict[str, Any]) -> JsonSchemaValue: + """Update a JSON schema by providing a dictionary of updates. + + This function sets the provided key-value pairs in the schema and returns the updated schema. + + Args: + schema: The JSON schema to update. + updates: A dictionary of key-value pairs to set in the schema. + + Returns: + The updated JSON schema. + """ + schema.update(updates) + return schema + + +JsonSchemaWarningKind = Literal['skipped-choice', 'non-serializable-default'] """ A type alias representing the kinds of warnings that can be emitted during JSON schema generation. @@ -106,12 +120,6 @@ class PydanticJsonSchemaWarning(UserWarning): """ -NoDefault = object() -"""A sentinel value used to indicate that no default value should be used when generating a JSON Schema -for a core schema with a default value. -""" - - # ##### JSON Schema Generation ##### DEFAULT_REF_TEMPLATE = '#/$defs/{model}' """The default format string used to generate reference names.""" @@ -128,11 +136,9 @@ DefsRef = NewType('DefsRef', str) # * By default, these look like "#/$defs/MyModel", as in {"$ref": "#/$defs/MyModel"} JsonRef = NewType('JsonRef', str) -CoreModeRef = tuple[CoreRef, JsonSchemaMode] +CoreModeRef = Tuple[CoreRef, JsonSchemaMode] JsonSchemaKeyT = TypeVar('JsonSchemaKeyT', bound=Hashable) -_PRIMITIVE_JSON_SCHEMA_TYPES = ('string', 'boolean', 'null', 'integer', 'number') - @dataclasses.dataclass(**_internal_dataclass.slots_true) class _DefinitionsRemapping: @@ -165,7 +171,7 @@ class _DefinitionsRemapping: # Deduplicate the schemas for each alternative; the idea is that we only want to remap to a new DefsRef # if it introduces no ambiguity, i.e., there is only one distinct schema for that DefsRef. - for defs_ref in schemas_for_alternatives: + for defs_ref, schemas in schemas_for_alternatives.items(): schemas_for_alternatives[defs_ref] = _deduplicate_schemas(schemas_for_alternatives[defs_ref]) # Build the remapping @@ -216,8 +222,7 @@ class _DefinitionsRemapping: class GenerateJsonSchema: - """!!! abstract "Usage Documentation" - [Customizing the JSON Schema Generation Process](../concepts/json_schema.md#customizing-the-json-schema-generation-process) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/json_schema/#customizing-the-json-schema-generation-process A class for generating JSON schemas. @@ -233,28 +238,27 @@ class GenerateJsonSchema: ignored_warning_kinds: Warnings to ignore when generating the schema. `self.render_warning_message` will do nothing if its argument `kind` is in `ignored_warning_kinds`; this value can be modified on subclasses to easily control which warnings are emitted. - by_alias: Whether to use field aliases when generating the schema. + by_alias: Whether or not to use field names when generating the schema. ref_template: The format string used when generating reference names. core_to_json_refs: A mapping of core refs to JSON refs. core_to_defs_refs: A mapping of core refs to definition refs. defs_to_core_refs: A mapping of definition refs to core refs. json_to_defs_refs: A mapping of JSON refs to definition refs. definitions: Definitions in the schema. + collisions: Definitions with colliding names. When collisions are detected, we choose a non-colliding + name during generation, but we also track the colliding tag so that it can be remapped for the first + occurrence at the end of the process. + defs_ref_fallbacks: Core refs to fallback definitions refs. + _schema_type_to_method: A mapping of schema types to generator methods. + _used: Set to `True` after generating a schema to avoid re-use issues. + mode: The schema mode. Args: - by_alias: Whether to use field aliases in the generated schemas. + by_alias: Whether or not to include field names. ref_template: The format string to use when generating reference names. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. Raises: - JsonSchemaError: If the instance of the class is inadvertently reused after generating a schema. + JsonSchemaError: If the instance of the class is inadvertently re-used after generating a schema. """ schema_dialect = 'https://json-schema.org/draft/2020-12/schema' @@ -263,15 +267,9 @@ class GenerateJsonSchema: # this value can be modified on subclasses to easily control which warnings are emitted ignored_warning_kinds: set[JsonSchemaWarningKind] = {'skipped-choice'} - def __init__( - self, - by_alias: bool = True, - ref_template: str = DEFAULT_REF_TEMPLATE, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', - ) -> None: + def __init__(self, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE): self.by_alias = by_alias self.ref_template = ref_template - self.union_format: Literal['any_of', 'primitive_type_array'] = union_format self.core_to_json_refs: dict[CoreModeRef, JsonRef] = {} self.core_to_defs_refs: dict[CoreModeRef, DefsRef] = {} @@ -301,7 +299,7 @@ class GenerateJsonSchema: # store the error raised and re-throw it if we end up needing that def self._core_defs_invalid_for_json_schema: dict[DefsRef, PydanticInvalidForJsonSchema] = {} - # This changes to True after generating a schema, to prevent issues caused by accidental reuse + # This changes to True after generating a schema, to prevent issues caused by accidental re-use # of a single instance of a schema generator self._used = False @@ -328,14 +326,14 @@ class GenerateJsonSchema: TypeError: If no method has been defined for generating a JSON schema for a given pydantic core schema type. """ mapping: dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]] = {} - core_schema_types: list[CoreSchemaOrFieldType] = list(get_literal_values(CoreSchemaOrFieldType)) + core_schema_types: list[CoreSchemaOrFieldType] = _typing_extra.all_literal_values( + CoreSchemaOrFieldType # type: ignore + ) for key in core_schema_types: - method_name = f'{key.replace("-", "_")}_schema' + method_name = f"{key.replace('-', '_')}_schema" try: mapping[key] = getattr(self, method_name) except AttributeError as e: # pragma: no cover - if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'): - continue raise TypeError( f'No method for generating JsonSchema for core_schema.type={key!r} ' f'(expected: {type(self).__name__}.{method_name})' @@ -374,7 +372,7 @@ class GenerateJsonSchema: code='json-schema-already-used', ) - for _, mode, schema in inputs: + for key, mode, schema in inputs: self._mode = mode self.generate_inner(schema) @@ -389,7 +387,7 @@ class GenerateJsonSchema: json_schema = {'$defs': self.definitions} json_schema = definitions_remapping.remap_json_schema(json_schema) self._used = True - return json_schemas_map, self.sort(json_schema['$defs']) # type: ignore + return json_schemas_map, _sort_json_schema(json_schema['$defs']) # type: ignore def generate(self, schema: CoreSchema, mode: JsonSchemaMode = 'validation') -> JsonSchemaValue: """Generates a JSON schema for a specified schema in a specified mode. @@ -415,15 +413,18 @@ class GenerateJsonSchema: json_schema: JsonSchemaValue = self.generate_inner(schema) json_ref_counts = self.get_json_ref_counts(json_schema) + # Remove the top-level $ref if present; note that the _generate method already ensures there are no sibling keys ref = cast(JsonRef, json_schema.get('$ref')) while ref is not None: # may need to unpack multiple levels ref_json_schema = self.get_schema_from_definitions(ref) - if json_ref_counts[ref] == 1 and ref_json_schema is not None and len(json_schema) == 1: - # "Unpack" the ref since this is the only reference and there are no sibling keys + if json_ref_counts[ref] > 1 or ref_json_schema is None: + # Keep the ref, but use an allOf to remove the top level $ref + json_schema = {'allOf': [{'$ref': ref}]} + else: + # "Unpack" the ref since this is the only reference json_schema = ref_json_schema.copy() # copy to prevent recursive dict reference json_ref_counts[ref] -= 1 - ref = cast(JsonRef, json_schema.get('$ref')) - ref = None + ref = cast(JsonRef, json_schema.get('$ref')) self._garbage_collect_definitions(json_schema) definitions_remapping = self._build_definitions_remapping() @@ -438,7 +439,7 @@ class GenerateJsonSchema: # json_schema['$schema'] = self.schema_dialect self._used = True - return self.sort(json_schema) + return _sort_json_schema(json_schema) def generate_inner(self, schema: CoreSchemaOrField) -> JsonSchemaValue: # noqa: C901 """Generates a JSON schema for a given core schema. @@ -448,10 +449,6 @@ class GenerateJsonSchema: Returns: The generated JSON schema. - - TODO: the nested function definitions here seem like bad practice, I'd like to unpack these - in a future PR. It'd be great if we could shorten the call stack a bit for JSON schema generation, - and I think there's potential for that here. """ # If a schema with the same CoreRef has been handled, just return a reference to it # Note that this assumes that it will _never_ be the case that the same CoreRef is used @@ -462,11 +459,15 @@ class GenerateJsonSchema: if core_mode_ref in self.core_to_defs_refs and self.core_to_defs_refs[core_mode_ref] in self.definitions: return {'$ref': self.core_to_json_refs[core_mode_ref]} + # Generate the JSON schema, accounting for the json_schema_override and core_schema_override + metadata_handler = _core_metadata.CoreMetadataHandler(schema) + def populate_defs(core_schema: CoreSchema, json_schema: JsonSchemaValue) -> JsonSchemaValue: if 'ref' in core_schema: core_ref = CoreRef(core_schema['ref']) # type: ignore[typeddict-item] defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) json_ref = JsonRef(ref_json_schema['$ref']) + self.json_to_defs_refs[json_ref] = defs_ref # Replace the schema if it's not a reference to itself # What we want to avoid is having the def be just a ref to itself # which is what would happen if we blindly assigned any @@ -476,6 +477,15 @@ class GenerateJsonSchema: json_schema = ref_json_schema return json_schema + def convert_to_all_of(json_schema: JsonSchemaValue) -> JsonSchemaValue: + if '$ref' in json_schema and len(json_schema.keys()) > 1: + # technically you can't have any other keys next to a "$ref" + # but it's an easy mistake to make and not hard to correct automatically here + json_schema = json_schema.copy() + ref = json_schema.pop('$ref') + json_schema = {'allOf': [{'$ref': ref}], **json_schema} + return json_schema + def handler_func(schema_or_field: CoreSchemaOrField) -> JsonSchemaValue: """Generate a JSON schema based on the input schema. @@ -491,62 +501,22 @@ class GenerateJsonSchema: # Generate the core-schema-type-specific bits of the schema generation: json_schema: JsonSchemaValue | None = None if self.mode == 'serialization' and 'serialization' in schema_or_field: - # In this case, we skip the JSON Schema generation of the schema - # and use the `'serialization'` schema instead (canonical example: - # `Annotated[int, PlainSerializer(str)]`). ser_schema = schema_or_field['serialization'] # type: ignore json_schema = self.ser_schema(ser_schema) - - # It might be that the 'serialization'` is skipped depending on `when_used`. - # This is only relevant for `nullable` schemas though, so we special case here. - if ( - json_schema is not None - and ser_schema.get('when_used') in ('unless-none', 'json-unless-none') - and schema_or_field['type'] == 'nullable' - ): - json_schema = self.get_union_of_schemas([{'type': 'null'}, json_schema]) if json_schema is None: if _core_utils.is_core_schema(schema_or_field) or _core_utils.is_core_schema_field(schema_or_field): generate_for_schema_type = self._schema_type_to_method[schema_or_field['type']] json_schema = generate_for_schema_type(schema_or_field) else: raise TypeError(f'Unexpected schema type: schema={schema_or_field}') + if _core_utils.is_core_schema(schema_or_field): + json_schema = populate_defs(schema_or_field, json_schema) + json_schema = convert_to_all_of(json_schema) return json_schema current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func) - metadata = cast(_core_metadata.CoreMetadata, schema.get('metadata', {})) - - # TODO: I dislike that we have to wrap these basic dict updates in callables, is there any way around this? - - if js_updates := metadata.get('pydantic_js_updates'): - - def js_updates_handler_func( - schema_or_field: CoreSchemaOrField, - current_handler: GetJsonSchemaHandler = current_handler, - ) -> JsonSchemaValue: - json_schema = {**current_handler(schema_or_field), **js_updates} - return json_schema - - current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func) - - if js_extra := metadata.get('pydantic_js_extra'): - - def js_extra_handler_func( - schema_or_field: CoreSchemaOrField, - current_handler: GetJsonSchemaHandler = current_handler, - ) -> JsonSchemaValue: - json_schema = current_handler(schema_or_field) - if isinstance(js_extra, dict): - json_schema.update(to_jsonable_python(js_extra)) - elif callable(js_extra): - # similar to typing issue in _update_class_schema when we're working with callable js extra - js_extra(json_schema) # type: ignore - return json_schema - - current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func) - - for js_modify_function in metadata.get('pydantic_js_functions', ()): + for js_modify_function in metadata_handler.metadata.get('pydantic_js_functions', ()): def new_handler_func( schema_or_field: CoreSchemaOrField, @@ -564,59 +534,28 @@ class GenerateJsonSchema: current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) - for js_modify_function in metadata.get('pydantic_js_annotation_functions', ()): + for js_modify_function in metadata_handler.metadata.get('pydantic_js_annotation_functions', ()): def new_handler_func( schema_or_field: CoreSchemaOrField, current_handler: GetJsonSchemaHandler = current_handler, js_modify_function: GetJsonSchemaFunction = js_modify_function, ) -> JsonSchemaValue: - return js_modify_function(schema_or_field, current_handler) + json_schema = js_modify_function(schema_or_field, current_handler) + if _core_utils.is_core_schema(schema_or_field): + json_schema = populate_defs(schema_or_field, json_schema) + json_schema = convert_to_all_of(json_schema) + return json_schema current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) json_schema = current_handler(schema) if _core_utils.is_core_schema(schema): json_schema = populate_defs(schema, json_schema) + json_schema = convert_to_all_of(json_schema) return json_schema - def sort(self, value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue: - """Override this method to customize the sorting of the JSON schema (e.g., don't sort at all, sort all keys unconditionally, etc.) - - By default, alphabetically sort the keys in the JSON schema, skipping the 'properties' and 'default' keys to preserve field definition order. - This sort is recursive, so it will sort all nested dictionaries as well. - """ - sorted_dict: dict[str, JsonSchemaValue] = {} - keys = value.keys() - if parent_key not in ('properties', 'default'): - keys = sorted(keys) - for key in keys: - sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) - return sorted_dict - - def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any: - """Recursively sort a JSON schema value.""" - if isinstance(value, dict): - sorted_dict: dict[str, JsonSchemaValue] = {} - keys = value.keys() - if parent_key not in ('properties', 'default'): - keys = sorted(keys) - for key in keys: - sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) - return sorted_dict - elif isinstance(value, list): - sorted_list: list[JsonSchemaValue] = [self._sort_recursive(item, parent_key) for item in value] - return sorted_list - else: - return value - # ### Schema generation methods - - def invalid_schema(self, schema: core_schema.InvalidSchema) -> JsonSchemaValue: - """Placeholder - should never be called.""" - - raise RuntimeError('Cannot generate schema for invalid_schema. This is a bug! Please report it.') - def any_schema(self, schema: core_schema.AnySchema) -> JsonSchemaValue: """Generates a JSON schema that matches any value. @@ -629,7 +568,7 @@ class GenerateJsonSchema: return {} def none_schema(self, schema: core_schema.NoneSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches `None`. + """Generates a JSON schema that matches a None value. Args: schema: The core schema. @@ -651,7 +590,7 @@ class GenerateJsonSchema: return {'type': 'boolean'} def int_schema(self, schema: core_schema.IntSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches an int value. + """Generates a JSON schema that matches an Int value. Args: schema: The core schema. @@ -687,49 +626,7 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - - def get_decimal_pattern(schema: core_schema.DecimalSchema) -> str: - max_digits = schema.get('max_digits') - decimal_places = schema.get('decimal_places') - - pattern = ( - r'^(?!^[-+.]*$)[+-]?0*' # check it is not empty string and not one or sequence of ".+-" characters. - ) - - # Case 1: Both max_digits and decimal_places are set - if max_digits is not None and decimal_places is not None: - integer_places = max(0, max_digits - decimal_places) - pattern += ( - rf'(?:' - rf'\d{{0,{integer_places}}}' - rf'|' - rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' - rf'\d{{0,{integer_places}}}\.\d{{0,{decimal_places}}}0*$' - rf')' - ) - - # Case 2: Only max_digits is set - elif max_digits is not None and decimal_places is None: - pattern += ( - rf'(?:' - rf'\d{{0,{max_digits}}}' - rf'|' - rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' - rf'\d*\.\d*0*$' - rf')' - ) - - # Case 3: Only decimal_places is set - elif max_digits is None and decimal_places is not None: - pattern += rf'\d*\.?\d{{0,{decimal_places}}}0*$' - - # Case 4: Both are None (no restrictions) - else: - pattern += r'\d*\.?\d*$' # look for arbitrary integer or decimal - - return pattern - - json_schema = self.str_schema(core_schema.str_schema(pattern=get_decimal_pattern(schema))) + json_schema = self.str_schema(core_schema.str_schema()) if self.mode == 'validation': multiple_of = schema.get('multiple_of') le = schema.get('le') @@ -764,9 +661,6 @@ class GenerateJsonSchema: """ json_schema = {'type': 'string'} self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) - if isinstance(json_schema.get('pattern'), Pattern): - # TODO: should we add regex flags to the pattern? - json_schema['pattern'] = json_schema.get('pattern').pattern # type: ignore return json_schema def bytes_schema(self, schema: core_schema.BytesSchema) -> JsonSchemaValue: @@ -791,7 +685,9 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - return {'type': 'string', 'format': 'date'} + json_schema = {'type': 'string', 'format': 'date'} + self.update_with_validations(json_schema, schema, self.ValidationsMapping.date) + return json_schema def time_schema(self, schema: core_schema.TimeSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a time value. @@ -837,80 +733,32 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - expected = [to_jsonable_python(v.value if isinstance(v, Enum) else v) for v in schema['expected']] + expected = [v.value if isinstance(v, Enum) else v for v in schema['expected']] + # jsonify the expected values + expected = [to_jsonable_python(v) for v in expected] - result: dict[str, Any] = {} if len(expected) == 1: - result['const'] = expected[0] - else: - result['enum'] = expected + return {'const': expected[0]} types = {type(e) for e in expected} if types == {str}: - result['type'] = 'string' + return {'enum': expected, 'type': 'string'} elif types == {int}: - result['type'] = 'integer' + return {'enum': expected, 'type': 'integer'} elif types == {float}: - result['type'] = 'number' + return {'enum': expected, 'type': 'number'} elif types == {bool}: - result['type'] = 'boolean' + return {'enum': expected, 'type': 'boolean'} elif types == {list}: - result['type'] = 'array' - elif types == {type(None)}: - result['type'] = 'null' - return result - - def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches the `MISSING` sentinel value. - - Args: - schema: The core schema. - - Returns: - The generated JSON schema. - """ - raise PydanticOmit - - def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches an Enum value. - - Args: - schema: The core schema. - - Returns: - The generated JSON schema. - """ - enum_type = schema['cls'] - description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) - if ( - description == 'An enumeration.' - ): # This is the default value provided by enum.EnumMeta.__new__; don't use it - description = None - result: dict[str, Any] = {'title': enum_type.__name__, 'description': description} - result = {k: v for k, v in result.items() if v is not None} - - expected = [to_jsonable_python(v.value) for v in schema['members']] - - result['enum'] = expected - - types = {type(e) for e in expected} - if isinstance(enum_type, str) or types == {str}: - result['type'] = 'string' - elif isinstance(enum_type, int) or types == {int}: - result['type'] = 'integer' - elif isinstance(enum_type, float) or types == {float}: - result['type'] = 'number' - elif types == {bool}: - result['type'] = 'boolean' - elif types == {list}: - result['type'] = 'array' - - return result + return {'enum': expected, 'type': 'array'} + # there is not None case because if it's mixed it hits the final `else` + # if it's a single Literal[None] then it becomes a `const` schema above + else: + return {'enum': expected} def is_instance_schema(self, schema: core_schema.IsInstanceSchema) -> JsonSchemaValue: - """Handles JSON schema generation for a core schema that checks if a value is an instance of a class. - - Unless overridden in a subclass, this raises an error. + """Generates a JSON schema that checks if a value is an instance of a class, equivalent to Python's + `isinstance` method. Args: schema: The core schema. @@ -921,9 +769,8 @@ class GenerateJsonSchema: return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})') def is_subclass_schema(self, schema: core_schema.IsSubclassSchema) -> JsonSchemaValue: - """Handles JSON schema generation for a core schema that checks if a value is a subclass of a class. - - For backwards compatibility with v1, this does not raise an error, but can be overridden to change this. + """Generates a JSON schema that checks if a value is a subclass of a class, equivalent to Python's `issubclass` + method. Args: schema: The core schema. @@ -937,8 +784,6 @@ class GenerateJsonSchema: def callable_schema(self, schema: core_schema.CallableSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a callable value. - Unless overridden in a subclass, this raises an error. - Args: schema: The core schema. @@ -961,31 +806,8 @@ class GenerateJsonSchema: self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) return json_schema - @deprecated('`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', category=None) - @final - def tuple_positional_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: - """Replaced by `tuple_schema`.""" - warnings.warn( - '`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', - PydanticDeprecatedSince26, - stacklevel=2, - ) - return self.tuple_schema(schema) - - @deprecated('`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', category=None) - @final - def tuple_variable_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: - """Replaced by `tuple_schema`.""" - warnings.warn( - '`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', - PydanticDeprecatedSince26, - stacklevel=2, - ) - return self.tuple_schema(schema) - - def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches a tuple schema e.g. `tuple[int, - str, bool]` or `tuple[int, ...]`. + def tuple_positional_schema(self, schema: core_schema.TuplePositionalSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a positional tuple schema e.g. `Tuple[int, str, bool]`. Args: schema: The core schema. @@ -994,27 +816,28 @@ class GenerateJsonSchema: The generated JSON schema. """ json_schema: JsonSchemaValue = {'type': 'array'} - if 'variadic_item_index' in schema: - variadic_item_index = schema['variadic_item_index'] - if variadic_item_index > 0: - json_schema['minItems'] = variadic_item_index - json_schema['prefixItems'] = [ - self.generate_inner(item) for item in schema['items_schema'][:variadic_item_index] - ] - if variadic_item_index + 1 == len(schema['items_schema']): - # if the variadic item is the last item, then represent it faithfully - json_schema['items'] = self.generate_inner(schema['items_schema'][variadic_item_index]) - else: - # otherwise, 'items' represents the schema for the variadic - # item plus the suffix, so just allow anything for simplicity - # for now - json_schema['items'] = True + json_schema['minItems'] = len(schema['items_schema']) + prefixItems = [self.generate_inner(item) for item in schema['items_schema']] + if prefixItems: + json_schema['prefixItems'] = prefixItems + if 'extras_schema' in schema: + json_schema['items'] = self.generate_inner(schema['extras_schema']) else: - prefixItems = [self.generate_inner(item) for item in schema['items_schema']] - if prefixItems: - json_schema['prefixItems'] = prefixItems - json_schema['minItems'] = len(prefixItems) - json_schema['maxItems'] = len(prefixItems) + json_schema['maxItems'] = len(schema['items_schema']) + self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) + return json_schema + + def tuple_variable_schema(self, schema: core_schema.TupleVariableSchema) -> JsonSchemaValue: + """Generates a JSON schema that matches a variable tuple schema e.g. `Tuple[int, ...]`. + + Args: + schema: The core schema. + + Returns: + The generated JSON schema. + """ + items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) + json_schema = {'type': 'array', 'items': items_schema} self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) return json_schema @@ -1072,42 +895,33 @@ class GenerateJsonSchema: json_schema: JsonSchemaValue = {'type': 'object'} keys_schema = self.generate_inner(schema['keys_schema']).copy() if 'keys_schema' in schema else {} - if '$ref' not in keys_schema: - keys_pattern = keys_schema.pop('pattern', None) - # Don't give a title to patternProperties/propertyNames: - keys_schema.pop('title', None) - else: - # Here, we assume that if the keys schema is a definition reference, - # it can't be a simple string core schema (and thus no pattern can exist). - # However, this is only in practice (in theory, a definition reference core - # schema could be generated for a simple string schema). - # Note that we avoid calling `self.resolve_ref_schema`, as it might not exist yet. - keys_pattern = None + keys_pattern = keys_schema.pop('pattern', None) values_schema = self.generate_inner(schema['values_schema']).copy() if 'values_schema' in schema else {} - # don't give a title to additionalProperties: - values_schema.pop('title', None) - - if values_schema or keys_pattern is not None: + values_schema.pop('title', None) # don't give a title to the additionalProperties + if values_schema or keys_pattern is not None: # don't add additionalProperties if it's empty if keys_pattern is None: json_schema['additionalProperties'] = values_schema else: json_schema['patternProperties'] = {keys_pattern: values_schema} - else: # for `dict[str, Any]`, we allow any key and any value, since `str` is the default key type - json_schema['additionalProperties'] = True - - if ( - # The len check indicates that constraints are probably present: - (keys_schema.get('type') == 'string' and len(keys_schema) > 1) - # If this is a definition reference schema, it most likely has constraints: - or '$ref' in keys_schema - ): - keys_schema.pop('type', None) - json_schema['propertyNames'] = keys_schema self.update_with_validations(json_schema, schema, self.ValidationsMapping.object) return json_schema + def _function_schema( + self, + schema: _core_utils.AnyFunctionSchema, + ) -> JsonSchemaValue: + if _core_utils.is_function_with_inner_schema(schema): + # This could be wrong if the function's mode is 'before', but in practice will often be right, and when it + # isn't, I think it would be hard to automatically infer what the desired schema should be. + return self.generate_inner(schema['schema']) + + # function-plain + return self.handle_invalid_for_json_schema( + schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})' + ) + def function_before_schema(self, schema: core_schema.BeforeValidatorFunctionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a function-before schema. @@ -1117,10 +931,7 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): - return self.generate_inner(input_schema) - - return self.generate_inner(schema['schema']) + return self._function_schema(schema) def function_after_schema(self, schema: core_schema.AfterValidatorFunctionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a function-after schema. @@ -1131,7 +942,7 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - return self.generate_inner(schema['schema']) + return self._function_schema(schema) def function_plain_schema(self, schema: core_schema.PlainValidatorFunctionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a function-plain schema. @@ -1142,12 +953,7 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): - return self.generate_inner(input_schema) - - return self.handle_invalid_for_json_schema( - schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})' - ) + return self._function_schema(schema) def function_wrap_schema(self, schema: core_schema.WrapValidatorFunctionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a function-wrap schema. @@ -1158,10 +964,7 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): - return self.generate_inner(input_schema) - - return self.generate_inner(schema['schema']) + return self._function_schema(schema) def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema with a default value. @@ -1174,35 +977,17 @@ class GenerateJsonSchema: """ json_schema = self.generate_inner(schema['schema']) - default = self.get_default_value(schema) - if default is NoDefault or default is MISSING: + if 'default' not in schema: return json_schema - - # we reflect the application of custom plain, no-info serializers to defaults for - # JSON Schemas viewed in serialization mode: - # TODO: improvements along with https://github.com/pydantic/pydantic/issues/8208 - if self.mode == 'serialization': - # `_get_ser_schema_for_default_value()` is used to unpack potentially nested validator schemas: - ser_schema = _get_ser_schema_for_default_value(schema['schema']) - if ( - ser_schema is not None - and (ser_func := ser_schema.get('function')) - and not (default is None and ser_schema.get('when_used') in ('unless-none', 'json-unless-none')) - ): - try: - default = ser_func(default) # type: ignore - except Exception: - # It might be that the provided default needs to be validated (read: parsed) first - # (assuming `validate_default` is enabled). However, we can't perform - # such validation during JSON Schema generation so we don't support - # this pattern for now. - # (One example is when using `foo: ByteSize = '1MB'`, which validates and - # serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails). - self.emit_warning( - 'non-serializable-default', - f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema', - ) - return json_schema + default = schema['default'] + # Note: if you want to include the value returned by the default_factory, + # override this method and replace the code above with: + # if 'default' in schema: + # default = schema['default'] + # elif 'default_factory' in schema: + # default = schema['default_factory']() + # else: + # return json_schema try: encoded_default = self.encode_default(default) @@ -1214,23 +999,12 @@ class GenerateJsonSchema: # Return the inner schema, as though there was no default return json_schema - json_schema['default'] = encoded_default - return json_schema - - def get_default_value(self, schema: core_schema.WithDefaultSchema) -> Any: - """Get the default value to be used when generating a JSON Schema for a core schema with a default. - - The default implementation is to use the statically defined default value. This method can be overridden - if you want to make use of the default factory. - - Args: - schema: The `'with-default'` core schema. - - Returns: - The default value to use, or [`NoDefault`][pydantic.json_schema.NoDefault] if no default - value is available. - """ - return schema.get('default', NoDefault) + if '$ref' in json_schema: + # Since reference schemas do not support child keys, we wrap the reference schema in a single-case allOf: + return {'allOf': [json_schema], 'default': encoded_default} + else: + json_schema['default'] = encoded_default + return json_schema def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that allows null values. @@ -1247,7 +1021,9 @@ class GenerateJsonSchema: if inner_json_schema == null_schema: return null_schema else: - return self.get_union_of_schemas([inner_json_schema, null_schema]) + # Thanks to the equality check against `null_schema` above, I think 'oneOf' would also be valid here; + # I'll use 'anyOf' for now, but it could be changed it if it would work better with some external tooling + return self.get_flattened_anyof([inner_json_schema, null_schema]) def union_schema(self, schema: core_schema.UnionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that allows values matching any of the given schemas. @@ -1272,43 +1048,7 @@ class GenerateJsonSchema: self.emit_warning('skipped-choice', exc.message) if len(generated) == 1: return generated[0] - return self.get_union_of_schemas(generated) - - def get_union_of_schemas(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: - """Returns the JSON Schema representation for the union of the provided JSON Schemas. - - The result depends on the configured `'union_format'`. - - Args: - schemas: The list of JSON Schemas to be included in the union. - - Returns: - The JSON Schema representing the union of schemas. - """ - if self.union_format == 'primitive_type_array': - types: list[str] = [] - for schema in schemas: - schema_types: list[str] | str | None = schema.get('type') - if schema_types is None: - # No type, meaning it can be a ref or an empty schema. - break - if not isinstance(schema_types, list): - schema_types = [schema_types] - if not all(t in _PRIMITIVE_JSON_SCHEMA_TYPES for t in schema_types): - break - if len(schema) != 1: - # We only want to include types that don't have any constraints. For instance, - # if `schemas = [{'type': 'string', 'maxLength': 3}, {'type': 'string', 'minLength': 5}]`, - # we don't want to produce `{'type': 'string', 'maxLength': 3, 'minLength': 5}`. - # Same if we have some metadata (e.g. `title`) on a specific union member, we want to preserve it. - break - - types.extend(schema_types) - else: - # If we got there, all the schemas where valid to be used with the `'primitive_type_array` format - return {'type': list(dict.fromkeys(types))} - - return self.get_flattened_anyof(schemas) + return self.get_flattened_anyof(generated) def tagged_union_schema(self, schema: core_schema.TaggedUnionSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that allows values matching any of the given schemas, where @@ -1375,14 +1115,9 @@ class GenerateJsonSchema: continue # this means that the "alias" does not represent a field alias_is_present_on_all_choices = True for choice in one_of_choices: - try: - choice = self.resolve_ref_schema(choice) - except RuntimeError as exc: - # TODO: fixme - this is a workaround for the fact that we can't always resolve refs - # for tagged union choices at this point in the schema gen process, we might need to do - # another pass at the end like we do for core schemas - self.emit_warning('skipped-discriminator', str(exc)) - choice = {} + while '$ref' in choice: + assert isinstance(choice['$ref'], str) + choice = self.get_schema_from_definitions(JsonRef(choice['$ref'])) or {} properties = choice.get('properties', {}) if not isinstance(properties, dict) or alias not in properties: alias_is_present_on_all_choices = False @@ -1458,35 +1193,18 @@ class GenerateJsonSchema: ] if self.mode == 'serialization': named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) - cls = schema.get('cls') - config = _get_typed_dict_config(cls) + + config = _get_typed_dict_config(schema) with self._config_wrapper_stack.push(config): json_schema = self._named_required_fields_schema(named_required_fields) - # There's some duplication between `extra_behavior` and - # the config's `extra`/core config's `extra_fields_behavior`. - # However, it is common to manually create TypedDictSchemas, - # where you don't necessarily have a class. - # At runtime, `extra_behavior` takes priority over the config - # for validation, so follow the same for the JSON Schema: - if schema.get('extra_behavior') == 'forbid': + extra = schema.get('extra_behavior') + if extra is None: + extra = config.get('extra', 'ignore') + if extra == 'forbid': json_schema['additionalProperties'] = False - elif schema.get('extra_behavior') == 'allow': - if 'extras_schema' in schema and schema['extras_schema'] != {'type': 'any'}: - json_schema['additionalProperties'] = self.generate_inner(schema['extras_schema']) - else: - json_schema['additionalProperties'] = True - - if cls is not None: - # `_update_class_schema()` will not override - # `additionalProperties` if already present: - self._update_class_schema(json_schema, cls, config) - elif 'additionalProperties' not in json_schema: - extra = schema.get('config', {}).get('extra_fields_behavior') - if extra == 'forbid': - json_schema['additionalProperties'] = False - elif extra == 'allow': - json_schema['additionalProperties'] = True + elif extra == 'allow': + json_schema['additionalProperties'] = True return json_schema @@ -1599,56 +1317,13 @@ class GenerateJsonSchema: # because it could lead to inconsistent refs handling, etc. cls = cast('type[BaseModel]', schema['cls']) config = cls.model_config + title = config.get('title') with self._config_wrapper_stack.push(config): json_schema = self.generate_inner(schema['schema']) - self._update_class_schema(json_schema, cls, config) - - return json_schema - - def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None: - """Update json_schema with the following, extracted from `config` and `cls`: - - * title - * description - * additional properties - * json_schema_extra - * deprecated - - Done in place, hence there's no return value as the original json_schema is mutated. - No ref resolving is involved here, as that's not appropriate for simple updates. - """ - from .main import BaseModel - from .root_model import RootModel - - if (config_title := config.get('title')) is not None: - json_schema.setdefault('title', config_title) - elif model_title_generator := config.get('model_title_generator'): - title = model_title_generator(cls) - if not isinstance(title, str): - raise TypeError(f'model_title_generator {model_title_generator} must return str, not {title.__class__}') - json_schema.setdefault('title', title) - if 'title' not in json_schema: - json_schema['title'] = cls.__name__ - - # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default - docstring = None if cls is BaseModel or dataclasses.is_dataclass(cls) else cls.__doc__ - - if docstring: - json_schema.setdefault('description', inspect.cleandoc(docstring)) - elif issubclass(cls, RootModel) and (root_description := cls.__pydantic_fields__['root'].description): - json_schema.setdefault('description', root_description) - - extra = config.get('extra') - if 'additionalProperties' not in json_schema: # This check is particularly important for `typed_dict_schema()` - if extra == 'allow': - json_schema['additionalProperties'] = True - elif extra == 'forbid': - json_schema['additionalProperties'] = False - json_schema_extra = config.get('json_schema_extra') - if issubclass(cls, BaseModel) and cls.__pydantic_root_model__: + if cls.__pydantic_root_model__: root_json_schema_extra = cls.model_fields['root'].json_schema_extra if json_schema_extra and root_json_schema_extra: raise ValueError( @@ -1658,27 +1333,52 @@ class GenerateJsonSchema: if root_json_schema_extra: json_schema_extra = root_json_schema_extra + json_schema = self._update_class_schema(json_schema, title, config.get('extra', None), cls, json_schema_extra) + + return json_schema + + def _update_class_schema( + self, + json_schema: JsonSchemaValue, + title: str | None, + extra: Literal['allow', 'ignore', 'forbid'] | None, + cls: type[Any], + json_schema_extra: JsonDict | JsonSchemaExtraCallable | None, + ) -> JsonSchemaValue: + if '$ref' in json_schema: + schema_to_update = self.get_schema_from_definitions(JsonRef(json_schema['$ref'])) or json_schema + else: + schema_to_update = json_schema + + if title is not None: + # referenced_schema['title'] = title + schema_to_update.setdefault('title', title) + + if 'additionalProperties' not in schema_to_update: + if extra == 'allow': + schema_to_update['additionalProperties'] = True + elif extra == 'forbid': + schema_to_update['additionalProperties'] = False + if isinstance(json_schema_extra, (staticmethod, classmethod)): # In older versions of python, this is necessary to ensure staticmethod/classmethods are callable json_schema_extra = json_schema_extra.__get__(cls) if isinstance(json_schema_extra, dict): - json_schema.update(json_schema_extra) + schema_to_update.update(json_schema_extra) elif callable(json_schema_extra): - # FIXME: why are there type ignores here? We support two signatures for json_schema_extra callables... if len(inspect.signature(json_schema_extra).parameters) > 1: - json_schema_extra(json_schema, cls) # type: ignore + json_schema_extra(schema_to_update, cls) # type: ignore else: - json_schema_extra(json_schema) # type: ignore + json_schema_extra(schema_to_update) # type: ignore elif json_schema_extra is not None: raise ValueError( f"model_config['json_schema_extra']={json_schema_extra} should be a dict, callable, or None" ) - if hasattr(cls, '__deprecated__'): - json_schema['deprecated'] = True + return json_schema - def resolve_ref_schema(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: + def resolve_schema_to_update(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: """Resolve a JsonSchemaValue to the non-ref schema if it is a $ref schema. Args: @@ -1686,17 +1386,15 @@ class GenerateJsonSchema: Returns: The resolved schema. - - Raises: - RuntimeError: If the schema reference can't be found in definitions. """ - while '$ref' in json_schema: - ref = json_schema['$ref'] - schema_to_update = self.get_schema_from_definitions(JsonRef(ref)) + if '$ref' in json_schema: + schema_to_update = self.get_schema_from_definitions(JsonRef(json_schema['$ref'])) if schema_to_update is None: - raise RuntimeError(f'Cannot update undefined schema for $ref={ref}') - json_schema = schema_to_update - return json_schema + raise RuntimeError(f'Cannot update undefined schema for $ref={json_schema["$ref"]}') + return self.resolve_schema_to_update(schema_to_update) + else: + schema_to_update = json_schema + return schema_to_update def model_fields_schema(self, schema: core_schema.ModelFieldsSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that defines a model's fields. @@ -1717,7 +1415,7 @@ class GenerateJsonSchema: json_schema = self._named_required_fields_schema(named_required_fields) extras_schema = schema.get('extras_schema', None) if extras_schema is not None: - schema_to_update = self.resolve_ref_schema(json_schema) + schema_to_update = self.resolve_schema_to_update(json_schema) schema_to_update['additionalProperties'] = self.generate_inner(extras_schema) return json_schema @@ -1756,19 +1454,13 @@ class GenerateJsonSchema: Returns: `True` if the field should be marked as required in the generated JSON schema, `False` otherwise. """ - if field['type'] == 'typed-dict-field': - required = field.get('required', total) + if self.mode == 'serialization' and self._config.json_schema_serialization_defaults_required: + return not field.get('serialization_exclude') else: - required = field['schema']['type'] != 'default' - - if self.mode == 'serialization': - has_exclude_if = field.get('serialization_exclude_if') is not None - if self._config.json_schema_serialization_defaults_required: - return not has_exclude_if + if field['type'] == 'typed-dict-field': + return field.get('required', total) else: - return required and not has_exclude_if - else: - return required + return field['schema']['type'] != 'default' def dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that defines a dataclass's constructor arguments. @@ -1797,18 +1489,18 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - from ._internal._dataclasses import is_stdlib_dataclass - cls = schema['cls'] config: ConfigDict = getattr(cls, '__pydantic_config__', cast('ConfigDict', {})) + title = config.get('title') or cls.__name__ with self._config_wrapper_stack.push(config): json_schema = self.generate_inner(schema['schema']).copy() - self._update_class_schema(json_schema, cls, config) + json_schema_extra = config.get('json_schema_extra') + json_schema = self._update_class_schema(json_schema, title, config.get('extra', None), cls, json_schema_extra) # Dataclass-specific handling of description - if is_stdlib_dataclass(cls): + if is_dataclass(cls) and not hasattr(cls, '__pydantic_validator__'): # vanilla dataclass; don't use cls.__doc__ as it will contain the class signature by default description = None else: @@ -1827,7 +1519,8 @@ class GenerateJsonSchema: Returns: The generated JSON schema. """ - prefer_positional = schema.get('metadata', {}).get('pydantic_js_prefer_positional_arguments') + metadata = _core_metadata.CoreMetadataHandler(schema).metadata + prefer_positional = metadata.get('pydantic_js_prefer_positional_arguments') arguments = schema['arguments_schema'] kw_only_arguments = [a for a in arguments if a.get('mode') == 'keyword_only'] @@ -1850,7 +1543,9 @@ class GenerateJsonSchema: if positional_possible: return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema) - raise PydanticInvalidForJsonSchema( + # TODO: When support for Python 3.7 is dropped, uncomment the block on `test_json_schema` + # to cover this test case. + raise PydanticInvalidForJsonSchema( # pragma: no cover 'Unable to generate JSON schema for arguments validator with positional-only and keyword-only arguments' ) @@ -1870,8 +1565,7 @@ class GenerateJsonSchema: for argument in arguments: name = self.get_argument_name(argument) argument_schema = self.generate_inner(argument['schema']).copy() - if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): - argument_schema['title'] = self.get_title_from_name(name) + argument_schema['title'] = self.get_title_from_name(name) properties[name] = argument_schema if argument['schema']['type'] != 'default': @@ -1910,8 +1604,7 @@ class GenerateJsonSchema: name = self.get_argument_name(argument) argument_schema = self.generate_inner(argument['schema']).copy() - if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): - argument_schema['title'] = self.get_title_from_name(name) + argument_schema['title'] = self.get_title_from_name(name) prefix_items.append(argument_schema) if argument['schema']['type'] != 'default': @@ -1920,9 +1613,7 @@ class GenerateJsonSchema: # I believe this is true, but I am not 100% sure min_items += 1 - json_schema: JsonSchemaValue = {'type': 'array'} - if prefix_items: - json_schema['prefixItems'] = prefix_items + json_schema: JsonSchemaValue = {'type': 'array', 'prefixItems': prefix_items} if min_items: json_schema['minItems'] = min_items @@ -1935,7 +1626,7 @@ class GenerateJsonSchema: return json_schema - def get_argument_name(self, argument: core_schema.ArgumentsParameter | core_schema.ArgumentsV3Parameter) -> str: + def get_argument_name(self, argument: core_schema.ArgumentsParameter) -> str: """Retrieves the name of an argument. Args: @@ -1953,45 +1644,6 @@ class GenerateJsonSchema: pass # might want to do something else? return name - def arguments_v3_schema(self, schema: core_schema.ArgumentsV3Schema) -> JsonSchemaValue: - """Generates a JSON schema that matches a schema that defines a function's arguments. - - Args: - schema: The core schema. - - Returns: - The generated JSON schema. - """ - arguments = schema['arguments_schema'] - properties: dict[str, JsonSchemaValue] = {} - required: list[str] = [] - for argument in arguments: - mode = argument.get('mode', 'positional_or_keyword') - name = self.get_argument_name(argument) - argument_schema = self.generate_inner(argument['schema']).copy() - if mode == 'var_args': - argument_schema = {'type': 'array', 'items': argument_schema} - elif mode == 'var_kwargs_uniform': - argument_schema = {'type': 'object', 'additionalProperties': argument_schema} - - argument_schema.setdefault('title', self.get_title_from_name(name)) - properties[name] = argument_schema - - if ( - (mode == 'var_kwargs_unpacked_typed_dict' and 'required' in argument_schema) - or mode not in {'var_args', 'var_kwargs_uniform', 'var_kwargs_unpacked_typed_dict'} - and argument['schema']['type'] != 'default' - ): - # This assumes that if the argument has a default value, - # the inner schema must be of type WithDefaultSchema. - # I believe this is true, but I am not 100% sure - required.append(name) - - json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties} - if required: - json_schema['required'] = required - return json_schema - def call_schema(self, schema: core_schema.CallSchema) -> JsonSchemaValue: """Generates a JSON schema that matches a schema that defines a function call. @@ -2081,7 +1733,7 @@ class GenerateJsonSchema: for definition in schema['definitions']: try: self.generate_inner(definition) - except PydanticInvalidForJsonSchema as e: # noqa: PERF203 + except PydanticInvalidForJsonSchema as e: core_ref: CoreRef = CoreRef(definition['ref']) # type: ignore self._core_defs_invalid_for_json_schema[self.get_defs_ref((core_ref, self.mode))] = e continue @@ -2125,22 +1777,6 @@ class GenerateJsonSchema: return self.generate_inner(schema['schema']) return None - def complex_schema(self, schema: core_schema.ComplexSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches a complex number. - - JSON has no standard way to represent complex numbers. Complex number is not a numeric - type. Here we represent complex number as strings following the rule defined by Python. - For instance, '1+2j' is an accepted complex string. Details can be found in - [Python's `complex` documentation][complex]. - - Args: - schema: The core schema. - - Returns: - The generated JSON schema. - """ - return {'type': 'string'} - # ### Utility methods def get_title_from_name(self, name: str) -> str: @@ -2152,7 +1788,7 @@ class GenerateJsonSchema: Returns: The title. """ - return name.title().replace('_', ' ').strip() + return name.title().replace('_', ' ') def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool: """Returns true if a field with the given schema should have a title set based on the field name. @@ -2277,13 +1913,14 @@ class GenerateJsonSchema: return defs_ref, ref_json_schema def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: - """Remove any sibling keys that are redundant with the referenced schema. + """It is not valid for a schema with a top-level $ref to have sibling keys. - Args: - json_schema: The schema to remove redundant sibling keys from. + During our own schema generation, we treat sibling keys as overrides to the referenced schema, + but this is not how the official JSON schema spec works. - Returns: - The schema with redundant sibling keys removed. + Because of this, we first remove any sibling keys that are redundant with the referenced schema, then if + any remain, we transform the schema from a top-level '$ref' to use allOf to move the $ref out of the top level. + (See bottom of https://swagger.io/docs/specification/using-ref/ for a reference about this behavior) """ if '$ref' in json_schema: # prevent modifications to the input; this copy may be safe to drop if there is significant overhead @@ -2294,25 +1931,33 @@ class GenerateJsonSchema: # This can happen when building schemas for models with not-yet-defined references. # It may be a good idea to do a recursive pass at the end of the generation to remove # any redundant override keys. + if len(json_schema) > 1: + # Make it an allOf to at least resolve the sibling keys issue + json_schema = json_schema.copy() + json_schema.setdefault('allOf', []) + json_schema['allOf'].append({'$ref': json_schema['$ref']}) + del json_schema['$ref'] + return json_schema for k, v in list(json_schema.items()): if k == '$ref': continue if k in referenced_json_schema and referenced_json_schema[k] == v: del json_schema[k] # redundant key + if len(json_schema) > 1: + # There is a remaining "override" key, so we need to move $ref out of the top level + json_ref = JsonRef(json_schema['$ref']) + del json_schema['$ref'] + assert 'allOf' not in json_schema # this should never happen, but just in case + json_schema['allOf'] = [{'$ref': json_ref}] return json_schema def get_schema_from_definitions(self, json_ref: JsonRef) -> JsonSchemaValue | None: - try: - def_ref = self.json_to_defs_refs[json_ref] - if def_ref in self._core_defs_invalid_for_json_schema: - raise self._core_defs_invalid_for_json_schema[def_ref] - return self.definitions.get(def_ref, None) - except KeyError: - if json_ref.startswith(('http://', 'https://')): - return None - raise + def_ref = self.json_to_defs_refs[json_ref] + if def_ref in self._core_defs_invalid_for_json_schema: + raise self._core_defs_invalid_for_json_schema[def_ref] + return self.definitions.get(def_ref, None) def encode_default(self, dft: Any) -> Any: """Encode a default value to a JSON-serializable value. @@ -2325,22 +1970,11 @@ class GenerateJsonSchema: Returns: The encoded default value. """ - from .type_adapter import TypeAdapter, _type_has_config - config = self._config - try: - default = ( - dft - if _type_has_config(type(dft)) - else TypeAdapter(type(dft), config=config.config_dict).dump_python( - dft, by_alias=self.by_alias, mode='json' - ) - ) - except PydanticSchemaGenerationError: - raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}') - return pydantic_core.to_jsonable_python( - default, timedelta_mode=config.ser_json_timedelta, bytes_mode=config.ser_json_bytes, by_alias=self.by_alias + dft, + timedelta_mode=config.ser_json_timedelta, + bytes_mode=config.ser_json_bytes, ) def update_with_validations( @@ -2389,6 +2023,12 @@ class GenerateJsonSchema: 'min_length': 'minProperties', 'max_length': 'maxProperties', } + date = { + 'le': 'maximum', + 'ge': 'minimum', + 'lt': 'exclusiveMaximum', + 'gt': 'exclusiveMinimum', + } def get_flattened_anyof(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: members = [] @@ -2416,20 +2056,12 @@ class GenerateJsonSchema: json_refs[json_ref] += 1 if already_visited: return # prevent recursion on a definition that was already visited - try: - defs_ref = self.json_to_defs_refs[json_ref] - if defs_ref in self._core_defs_invalid_for_json_schema: - raise self._core_defs_invalid_for_json_schema[defs_ref] - _add_json_refs(self.definitions[defs_ref]) - except KeyError: - if not json_ref.startswith(('http://', 'https://')): - raise + defs_ref = self.json_to_defs_refs[json_ref] + if defs_ref in self._core_defs_invalid_for_json_schema: + raise self._core_defs_invalid_for_json_schema[defs_ref] + _add_json_refs(self.definitions[defs_ref]) - for k, v in schema.items(): - if k == 'examples' and isinstance(v, list): - # Skip examples that may contain arbitrary values and references - # (see the comment in `_get_all_json_refs` for more details). - continue + for v in schema.values(): _add_json_refs(v) elif isinstance(schema, list): for v in schema: @@ -2484,15 +2116,11 @@ class GenerateJsonSchema: unvisited_json_refs = _get_all_json_refs(schema) while unvisited_json_refs: next_json_ref = unvisited_json_refs.pop() - try: - next_defs_ref = self.json_to_defs_refs[next_json_ref] - if next_defs_ref in visited_defs_refs: - continue - visited_defs_refs.add(next_defs_ref) - unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref])) - except KeyError: - if not next_json_ref.startswith(('http://', 'https://')): - raise + next_defs_ref = self.json_to_defs_refs[next_json_ref] + if next_defs_ref in visited_defs_refs: + continue + visited_defs_refs.add(next_defs_ref) + unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref])) self.definitions = {k: v for k, v in self.definitions.items() if k in visited_defs_refs} @@ -2504,7 +2132,6 @@ def model_json_schema( cls: type[BaseModel] | type[PydanticDataclass], by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, mode: JsonSchemaMode = 'validation', ) -> dict[str, Any]: @@ -2515,14 +2142,6 @@ def model_json_schema( by_alias: If `True` (the default), fields will be serialized according to their alias. If `False`, fields will be serialized according to their attribute name. ref_template: The template to use for generating JSON Schema references. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. schema_generator: The class to use for generating the JSON Schema. mode: The mode to use for generating the JSON Schema. It can be one of the following: @@ -2532,19 +2151,10 @@ def model_json_schema( Returns: The generated JSON Schema. """ - from .main import BaseModel - - schema_generator_instance = schema_generator( - by_alias=by_alias, ref_template=ref_template, union_format=union_format - ) - - if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): - cls.__pydantic_core_schema__.rebuild() - - if cls is BaseModel: - raise AttributeError('model_json_schema() must be called on a subclass of BaseModel, not BaseModel itself.') - - assert not isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it' + schema_generator_instance = schema_generator(by_alias=by_alias, ref_template=ref_template) + if isinstance(cls.__pydantic_validator__, _mock_val_ser.MockValSer): + cls.__pydantic_validator__.rebuild() + assert '__pydantic_core_schema__' in cls.__dict__, 'this is a bug! please report it' return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode) @@ -2555,7 +2165,6 @@ def models_json_schema( title: str | None = None, description: str | None = None, ref_template: str = DEFAULT_REF_TEMPLATE, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, ) -> tuple[dict[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]: """Utility function to generate a JSON Schema for multiple models. @@ -2566,14 +2175,6 @@ def models_json_schema( title: The title of the generated JSON Schema. description: The description of the generated JSON Schema. ref_template: The reference template to use for generating JSON Schema references. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. schema_generator: The schema generator to use for generating the JSON Schema. Returns: @@ -2585,13 +2186,11 @@ def models_json_schema( element, along with the optional title and description keys. """ for cls, _ in models: - if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): - cls.__pydantic_core_schema__.rebuild() + if isinstance(cls.__pydantic_validator__, _mock_val_ser.MockValSer): + cls.__pydantic_validator__.rebuild() - instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format) - inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [ - (m, mode, m.__pydantic_core_schema__) for m, mode in models - ] + instance = schema_generator(by_alias=by_alias, ref_template=ref_template) + inputs = [(m, mode, m.__pydantic_core_schema__) for m, mode in models] json_schemas_map, definitions = instance.generate_definitions(inputs) json_schema: dict[str, Any] = {} @@ -2609,7 +2208,7 @@ def models_json_schema( _HashableJsonValue: TypeAlias = Union[ - int, float, str, bool, None, tuple['_HashableJsonValue', ...], tuple[tuple[str, '_HashableJsonValue'], ...] + int, float, str, bool, None, Tuple['_HashableJsonValue', ...], Tuple[Tuple[str, '_HashableJsonValue'], ...] ] @@ -2626,12 +2225,27 @@ def _make_json_hashable(value: JsonValue) -> _HashableJsonValue: return value +def _sort_json_schema(value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue: + if isinstance(value, dict): + sorted_dict: dict[str, JsonSchemaValue] = {} + keys = value.keys() + if (parent_key != 'properties') and (parent_key != 'default'): + keys = sorted(keys) + for key in keys: + sorted_dict[key] = _sort_json_schema(value[key], parent_key=key) + return sorted_dict + elif isinstance(value, list): + sorted_list: list[JsonSchemaValue] = [] + for item in value: # type: ignore + sorted_list.append(_sort_json_schema(item, parent_key)) + return sorted_list # type: ignore + else: + return value + + @dataclasses.dataclass(**_internal_dataclass.slots_true) class WithJsonSchema: - """!!! abstract "Usage Documentation" - [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation) - - Add this as an annotation on a field to override the (base) JSON schema that would be generated for that field. + """Add this as an annotation on a field to override the (base) JSON schema that would be generated for that field. This provides a way to set a JSON schema for types that would otherwise raise errors when producing a JSON schema, such as Callable, or types that have an is-instance core schema, without needing to go so far as creating a custom subclass of pydantic.json_schema.GenerateJsonSchema. @@ -2655,42 +2269,25 @@ class WithJsonSchema: # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema raise PydanticOmit else: - return self.json_schema.copy() + return self.json_schema def __hash__(self) -> int: return hash(type(self.mode)) +@dataclasses.dataclass(**_internal_dataclass.slots_true) class Examples: """Add examples to a JSON schema. - If the JSON Schema already contains examples, the provided examples - will be appended. + Examples should be a map of example names (strings) + to example values (any valid JSON). If `mode` is set this will only apply to that schema generation mode, allowing you to add different examples for validation and serialization. """ - @overload - @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.') - def __init__( - self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None - ) -> None: ... - - @overload - def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ... - - def __init__( - self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None - ) -> None: - if isinstance(examples, dict): - warnings.warn( - 'Using a dict for `examples` is deprecated, use a list instead.', - PydanticDeprecatedSince29, - stacklevel=2, - ) - self.examples = examples - self.mode = mode + examples: dict[str, Any] + mode: Literal['validation', 'serialization'] | None = None def __get_pydantic_json_schema__( self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler @@ -2699,36 +2296,9 @@ class Examples: json_schema = handler(core_schema) if mode != handler.mode: return json_schema - examples = json_schema.get('examples') - if examples is None: - json_schema['examples'] = to_jsonable_python(self.examples) - if isinstance(examples, dict): - if isinstance(self.examples, list): - warnings.warn( - 'Updating existing JSON Schema examples of type dict with examples of type list. ' - 'Only the existing examples values will be retained. Note that dict support for ' - 'examples is deprecated and will be removed in v3.0.', - UserWarning, - ) - json_schema['examples'] = to_jsonable_python( - [ex for value in examples.values() for ex in value] + self.examples - ) - else: - json_schema['examples'] = to_jsonable_python({**examples, **self.examples}) - if isinstance(examples, list): - if isinstance(self.examples, list): - json_schema['examples'] = to_jsonable_python(examples + self.examples) - elif isinstance(self.examples, dict): - warnings.warn( - 'Updating existing JSON Schema examples of type list with examples of type dict. ' - 'Only the examples values will be retained. Note that dict support for ' - 'examples is deprecated and will be removed in v3.0.', - UserWarning, - ) - json_schema['examples'] = to_jsonable_python( - examples + [ex for value in self.examples.values() for ex in value] - ) - + examples = json_schema.get('examples', {}) + examples.update(to_jsonable_python(self.examples)) + json_schema['examples'] = examples return json_schema def __hash__(self) -> int: @@ -2738,28 +2308,19 @@ class Examples: def _get_all_json_refs(item: Any) -> set[JsonRef]: """Get all the definitions references from a JSON schema.""" refs: set[JsonRef] = set() - stack = [item] - - while stack: - current = stack.pop() - if isinstance(current, dict): - for key, value in current.items(): - if key == 'examples' and isinstance(value, list): - # Skip examples that may contain arbitrary values and references - # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value - # of type list is necessary to avoid skipping valid portions of the schema, - # for instance when "examples" is used as a property key. A more robust solution - # could be found, but would require more advanced JSON Schema parsing logic. - continue - if key == '$ref' and isinstance(value, str): - refs.add(JsonRef(value)) - elif isinstance(value, dict): - stack.append(value) - elif isinstance(value, list): - stack.extend(value) - elif isinstance(current, list): - stack.extend(current) - + if isinstance(item, dict): + for key, value in item.items(): + if key == '$ref' and isinstance(value, str): + # the isinstance check ensures that '$ref' isn't the name of a property, etc. + refs.add(JsonRef(value)) + elif isinstance(value, dict): + refs.update(_get_all_json_refs(value)) + elif isinstance(value, list): + for item in value: + refs.update(_get_all_json_refs(item)) + elif isinstance(item, list): + for item in item: + refs.update(_get_all_json_refs(item)) return refs @@ -2771,51 +2332,20 @@ else: @dataclasses.dataclass(**_internal_dataclass.slots_true) class SkipJsonSchema: - """!!! abstract "Usage Documentation" - [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation) - - Add this as an annotation on a field to skip generating a JSON schema for that field. + """Add this as an annotation on a field to skip generating a JSON schema for that field. Example: - ```python - from pprint import pprint - from typing import Union - + ```py from pydantic import BaseModel from pydantic.json_schema import SkipJsonSchema class Model(BaseModel): - a: Union[int, None] = None # (1)! - b: Union[int, SkipJsonSchema[None]] = None # (2)! - c: SkipJsonSchema[Union[int, None]] = None # (3)! + a: int | SkipJsonSchema[None] = None - pprint(Model.model_json_schema()) - ''' - { - 'properties': { - 'a': { - 'anyOf': [ - {'type': 'integer'}, - {'type': 'null'} - ], - 'default': None, - 'title': 'A' - }, - 'b': { - 'default': None, - 'title': 'B', - 'type': 'integer' - } - }, - 'title': 'Model', - 'type': 'object' - } - ''' + + print(Model.model_json_schema()) + #> {'properties': {'a': {'default': None, 'title': 'A', 'type': 'integer'}}, 'title': 'Model', 'type': 'object'} ``` - - 1. The integer and null types are both included in the schema for `a`. - 2. The integer type is the only type included in the schema for `b`. - 3. The entirety of the `c` field is omitted from the schema. """ def __class_getitem__(cls, item: AnyType) -> AnyType: @@ -2830,25 +2360,12 @@ else: return hash(type(self)) -def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict: +def _get_typed_dict_config(schema: core_schema.TypedDictSchema) -> ConfigDict: + metadata = _core_metadata.CoreMetadataHandler(schema).metadata + cls = metadata.get('pydantic_typed_dict_cls') if cls is not None: try: return _decorators.get_attribute_from_bases(cls, '__pydantic_config__') except AttributeError: pass return {} - - -def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None: - """Get a `'function-plain'` serialization schema that can be used to serialize a default value. - - This takes into account having the serialization schema nested under validation schema(s). - """ - if ( - (ser_schema := schema.get('serialization')) - and ser_schema['type'] == 'function-plain' - and not ser_schema.get('info_arg') - ): - return ser_schema - if _core_utils.is_function_with_inner_schema(schema): - return _get_ser_schema_for_default_value(schema['schema']) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/main.py b/Backend/venv/lib/python3.12/site-packages/pydantic/main.py index 2b3148ed..2e716ac1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/main.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/main.py @@ -1,38 +1,17 @@ """Logic for creating models.""" - -# Because `dict` is in the local namespace of the `BaseModel` class, we use `Dict` for annotations. -# TODO v3 fallback to `dict` when the deprecated `dict` method gets removed. -# ruff: noqa: UP035 - from __future__ import annotations as _annotations -import operator import sys import types +import typing import warnings -from collections.abc import Generator, Mapping from copy import copy, deepcopy -from functools import cached_property -from typing import ( - TYPE_CHECKING, - Any, - Callable, - ClassVar, - Dict, - Generic, - Literal, - TypeVar, - Union, - cast, - overload, -) +from typing import Any, ClassVar import pydantic_core import typing_extensions -from pydantic_core import PydanticUndefined, ValidationError -from typing_extensions import Self, TypeAlias, Unpack +from pydantic_core import PydanticUndefined -from . import PydanticDeprecatedSince20, PydanticDeprecatedSince211 from ._internal import ( _config, _decorators, @@ -41,93 +20,55 @@ from ._internal import ( _generics, _mock_val_ser, _model_construction, - _namespace_utils, _repr, _typing_extra, _utils, ) from ._migration import getattr_migration -from .aliases import AliasChoices, AliasPath from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler -from .config import ConfigDict, ExtraValues +from .config import ConfigDict from .errors import PydanticUndefinedAnnotation, PydanticUserError from .json_schema import DEFAULT_REF_TEMPLATE, GenerateJsonSchema, JsonSchemaMode, JsonSchemaValue, model_json_schema -from .plugin._schema_validator import PluggableSchemaValidator +from .warnings import PydanticDeprecatedSince20 -if TYPE_CHECKING: +if typing.TYPE_CHECKING: from inspect import Signature from pathlib import Path from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator + from typing_extensions import Literal, Unpack - from ._internal._namespace_utils import MappingNamespace from ._internal._utils import AbstractSetIntStr, MappingIntStrAny from .deprecated.parse import Protocol as DeprecatedParseProtocol from .fields import ComputedFieldInfo, FieldInfo, ModelPrivateAttr + from .fields import Field as _Field + TupleGenerator = typing.Generator[typing.Tuple[str, Any], None, None] + Model = typing.TypeVar('Model', bound='BaseModel') + # should be `set[int] | set[str] | dict[int, IncEx] | dict[str, IncEx] | None`, but mypy can't cope + IncEx: typing_extensions.TypeAlias = 'set[int] | set[str] | dict[int, Any] | dict[str, Any] | None' +else: + # See PyCharm issues https://youtrack.jetbrains.com/issue/PY-21915 + # and https://youtrack.jetbrains.com/issue/PY-51428 + DeprecationWarning = PydanticDeprecatedSince20 __all__ = 'BaseModel', 'create_model' -# Keep these type aliases available at runtime: -TupleGenerator: TypeAlias = Generator[tuple[str, Any], None, None] -# NOTE: In reality, `bool` should be replaced by `Literal[True]` but mypy fails to correctly apply bidirectional -# type inference (e.g. when using `{'a': {'b': True}}`): -# NOTE: Keep this type alias in sync with the stub definition in `pydantic-core`: -IncEx: TypeAlias = Union[set[int], set[str], Mapping[int, Union['IncEx', bool]], Mapping[str, Union['IncEx', bool]]] - _object_setattr = _model_construction.object_setattr -def _check_frozen(model_cls: type[BaseModel], name: str, value: Any) -> None: - if model_cls.model_config.get('frozen'): - error_type = 'frozen_instance' - elif getattr(model_cls.__pydantic_fields__.get(name), 'frozen', False): - error_type = 'frozen_field' - else: - return - - raise ValidationError.from_exception_data( - model_cls.__name__, [{'type': error_type, 'loc': (name,), 'input': value}] - ) - - -def _model_field_setattr_handler(model: BaseModel, name: str, val: Any) -> None: - model.__dict__[name] = val - model.__pydantic_fields_set__.add(name) - - -def _private_setattr_handler(model: BaseModel, name: str, val: Any) -> None: - if getattr(model, '__pydantic_private__', None) is None: - # While the attribute should be present at this point, this may not be the case if - # users do unusual stuff with `model_post_init()` (which is where the `__pydantic_private__` - # is initialized, by wrapping the user-defined `model_post_init()`), e.g. if they mock - # the `model_post_init()` call. Ideally we should find a better way to init private attrs. - object.__setattr__(model, '__pydantic_private__', {}) - model.__pydantic_private__[name] = val # pyright: ignore[reportOptionalSubscript] - - -_SIMPLE_SETATTR_HANDLERS: Mapping[str, Callable[[BaseModel, str, Any], None]] = { - 'model_field': _model_field_setattr_handler, - 'validate_assignment': lambda model, name, val: model.__pydantic_validator__.validate_assignment(model, name, val), # pyright: ignore[reportAssignmentType] - 'private': _private_setattr_handler, - 'cached_property': lambda model, name, val: model.__dict__.__setitem__(name, val), - 'extra_known': lambda model, name, val: _object_setattr(model, name, val), -} - - class BaseModel(metaclass=_model_construction.ModelMetaclass): - """!!! abstract "Usage Documentation" - [Models](../concepts/models.md) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/models/ A base class for creating Pydantic models. Attributes: - __class_vars__: The names of the class variables defined on the model. + __class_vars__: The names of classvars defined on the model. __private_attributes__: Metadata about the private attributes of the model. - __signature__: The synthesized `__init__` [`Signature`][inspect.Signature] of the model. + __signature__: The signature for instantiating the model. __pydantic_complete__: Whether model building is completed, or if there are still undefined fields. - __pydantic_core_schema__: The core schema of the model. + __pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer. __pydantic_custom_init__: Whether the model has a custom `__init__` function. __pydantic_decorators__: Metadata containing the decorators defined on the model. This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1. @@ -135,95 +76,63 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these. __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models. __pydantic_post_init__: The name of the post-init method for the model, if defined. - __pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel]. - __pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model. - __pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model. + __pydantic_root_model__: Whether the model is a `RootModel`. + __pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the model. + __pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the model. - __pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. - __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects. - - __pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] - is set to `'allow'`. - __pydantic_fields_set__: The names of fields explicitly set during instantiation. - __pydantic_private__: Values of private attributes set on the model instance. + __pydantic_extra__: An instance attribute with the values of extra fields from validation when + `model_config['extra'] == 'allow'`. + __pydantic_fields_set__: An instance attribute with the names of fields explicitly set. + __pydantic_private__: Instance attribute with the values of private attributes set on the model instance. """ - # Note: Many of the below class vars are defined in the metaclass, but we define them here for type checking purposes. + if typing.TYPE_CHECKING: + # Here we provide annotations for the attributes of BaseModel. + # Many of these are populated by the metaclass, which is why this section is in a `TYPE_CHECKING` block. + # However, for the sake of easy review, we have included type annotations of all class and instance attributes + # of `BaseModel` here: - model_config: ClassVar[ConfigDict] = ConfigDict() - """ - Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. - """ + # Class attributes + model_config: ClassVar[ConfigDict] + """ + Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. + """ - __class_vars__: ClassVar[set[str]] - """The names of the class variables defined on the model.""" + model_fields: ClassVar[dict[str, FieldInfo]] + """ + Metadata about the fields defined on the model, + mapping of field names to [`FieldInfo`][pydantic.fields.FieldInfo]. - __private_attributes__: ClassVar[Dict[str, ModelPrivateAttr]] # noqa: UP006 - """Metadata about the private attributes of the model.""" + This replaces `Model.__fields__` from Pydantic V1. + """ - __signature__: ClassVar[Signature] - """The synthesized `__init__` [`Signature`][inspect.Signature] of the model.""" + __class_vars__: ClassVar[set[str]] + __private_attributes__: ClassVar[dict[str, ModelPrivateAttr]] + __signature__: ClassVar[Signature] - __pydantic_complete__: ClassVar[bool] = False - """Whether model building is completed, or if there are still undefined fields.""" + __pydantic_complete__: ClassVar[bool] + __pydantic_core_schema__: ClassVar[CoreSchema] + __pydantic_custom_init__: ClassVar[bool] + __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] + __pydantic_generic_metadata__: ClassVar[_generics.PydanticGenericMetadata] + __pydantic_parent_namespace__: ClassVar[dict[str, Any] | None] + __pydantic_post_init__: ClassVar[None | Literal['model_post_init']] + __pydantic_root_model__: ClassVar[bool] + __pydantic_serializer__: ClassVar[SchemaSerializer] + __pydantic_validator__: ClassVar[SchemaValidator] - __pydantic_core_schema__: ClassVar[CoreSchema] - """The core schema of the model.""" - - __pydantic_custom_init__: ClassVar[bool] - """Whether the model has a custom `__init__` method.""" - - # Must be set for `GenerateSchema.model_schema` to work for a plain `BaseModel` annotation. - __pydantic_decorators__: ClassVar[_decorators.DecoratorInfos] = _decorators.DecoratorInfos() - """Metadata containing the decorators defined on the model. - This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.""" - - __pydantic_generic_metadata__: ClassVar[_generics.PydanticGenericMetadata] - """Metadata for generic models; contains data used for a similar purpose to - __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.""" - - __pydantic_parent_namespace__: ClassVar[Dict[str, Any] | None] = None # noqa: UP006 - """Parent namespace of the model, used for automatic rebuilding of models.""" - - __pydantic_post_init__: ClassVar[None | Literal['model_post_init']] - """The name of the post-init method for the model, if defined.""" - - __pydantic_root_model__: ClassVar[bool] = False - """Whether the model is a [`RootModel`][pydantic.root_model.RootModel].""" - - __pydantic_serializer__: ClassVar[SchemaSerializer] - """The `pydantic-core` `SchemaSerializer` used to dump instances of the model.""" - - __pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator] - """The `pydantic-core` `SchemaValidator` used to validate instances of the model.""" - - __pydantic_fields__: ClassVar[Dict[str, FieldInfo]] # noqa: UP006 - """A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects. - This replaces `Model.__fields__` from Pydantic V1. - """ - - __pydantic_setattr_handlers__: ClassVar[Dict[str, Callable[[BaseModel, str, Any], None]]] # noqa: UP006 - """`__setattr__` handlers. Memoizing the handlers leads to a dramatic performance improvement in `__setattr__`""" - - __pydantic_computed_fields__: ClassVar[Dict[str, ComputedFieldInfo]] # noqa: UP006 - """A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.""" - - __pydantic_extra__: Dict[str, Any] | None = _model_construction.NoInitField(init=False) # noqa: UP006 - """A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra] is set to `'allow'`.""" - - __pydantic_fields_set__: set[str] = _model_construction.NoInitField(init=False) - """The names of fields explicitly set during instantiation.""" - - __pydantic_private__: Dict[str, Any] | None = _model_construction.NoInitField(init=False) # noqa: UP006 - """Values of private attributes set on the model instance.""" - - if not TYPE_CHECKING: - # Prevent `BaseModel` from being instantiated directly - # (defined in an `if not TYPE_CHECKING` block for clarity and to avoid type checking errors): - __pydantic_core_schema__ = _mock_val_ser.MockCoreSchema( - 'Pydantic models should inherit from BaseModel, BaseModel cannot be instantiated directly', - code='base-model-instantiated', - ) + # Instance attributes + # Note: we use the non-existent kwarg `init=False` in pydantic.fields.Field below so that @dataclass_transform + # doesn't think these are valid as keyword arguments to the class initializer. + __pydantic_extra__: dict[str, Any] | None = _Field(init=False) # type: ignore + __pydantic_fields_set__: set[str] = _Field(init=False) # type: ignore + __pydantic_private__: dict[str, Any] | None = _Field(init=False) # type: ignore + else: + # `model_fields` and `__pydantic_decorators__` must be set for + # pydantic._internal._generate_schema.GenerateSchema.model_schema to work for a plain BaseModel annotation + model_fields = {} + __pydantic_decorators__ = _decorators.DecoratorInfos() + # Prevent `BaseModel` from being instantiated directly: __pydantic_validator__ = _mock_val_ser.MockValSer( 'Pydantic models should inherit from BaseModel, BaseModel cannot be instantiated directly', val_or_ser='validator', @@ -237,49 +146,34 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): __slots__ = '__dict__', '__pydantic_fields_set__', '__pydantic_extra__', '__pydantic_private__' - def __init__(self, /, **data: Any) -> None: + model_config = ConfigDict() + __pydantic_complete__ = False + __pydantic_root_model__ = False + + def __init__(__pydantic_self__, **data: Any) -> None: # type: ignore """Create a new model by parsing and validating input data from keyword arguments. Raises [`ValidationError`][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model. - `self` is explicitly positional-only to allow `self` as a field name. + `__init__` uses `__pydantic_self__` instead of the more common `self` for the first arg to + allow `self` as a field name. """ # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks __tracebackhide__ = True - validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self) - if self is not validated_self: - warnings.warn( - 'A custom validator is returning a value other than `self`.\n' - "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n" - 'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.', - stacklevel=2, - ) + __pydantic_self__.__pydantic_validator__.validate_python(data, self_instance=__pydantic_self__) # The following line sets a flag that we use to determine when `__init__` gets overridden by the user - __init__.__pydantic_base_init__ = True # pyright: ignore[reportFunctionMemberAccess] + __init__.__pydantic_base_init__ = True - @_utils.deprecated_instance_property - @classmethod - def model_fields(cls) -> dict[str, FieldInfo]: - """A mapping of field names to their respective [`FieldInfo`][pydantic.fields.FieldInfo] instances. + @property + def model_computed_fields(self) -> dict[str, ComputedFieldInfo]: + """Get the computed fields of this model instance. - !!! warning - Accessing this attribute from a model instance is deprecated, and will not work in Pydantic V3. - Instead, you should access this attribute from the model class. + Returns: + A dictionary of computed field names and their corresponding `ComputedFieldInfo` objects. """ - return getattr(cls, '__pydantic_fields__', {}) - - @_utils.deprecated_instance_property - @classmethod - def model_computed_fields(cls) -> dict[str, ComputedFieldInfo]: - """A mapping of computed field names to their respective [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] instances. - - !!! warning - Accessing this attribute from a model instance is deprecated, and will not work in Pydantic V3. - Instead, you should access this attribute from the model class. - """ - return getattr(cls, '__pydantic_computed_fields__', {}) + return {k: v.info for k, v in self.__pydantic_decorators__.computed_fields.items()} @property def model_extra(self) -> dict[str, Any] | None: @@ -301,23 +195,15 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return self.__pydantic_fields_set__ @classmethod - def model_construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: # noqa: C901 + def model_construct(cls: type[Model], _fields_set: set[str] | None = None, **values: Any) -> Model: """Creates a new instance of the `Model` class with validated data. Creates a new model setting `__dict__` and `__pydantic_fields_set__` from trusted or pre-validated data. Default values are respected, but no other validation is performed. - - !!! note - `model_construct()` generally respects the `model_config.extra` setting on the provided model. - That is, if `model_config.extra == 'allow'`, then all extra passed values are added to the model instance's `__dict__` - and `__pydantic_extra__` fields. If `model_config.extra == 'ignore'` (the default), then all extra passed values are ignored. - Because no validation is performed with a call to `model_construct()`, having `model_config.extra == 'forbid'` does not result in - an error if extra values are passed, but they will be ignored. + Behaves as if `Config.extra = 'allow'` was set since it adds all passed values Args: - _fields_set: A set of field names that were originally explicitly set during instantiation. If provided, - this is directly used for the [`model_fields_set`][pydantic.BaseModel.model_fields_set] attribute. - Otherwise, the field names from the `values` argument will be used. + _fields_set: The set of field names accepted for the Model instance. values: Trusted or pre-validated data dictionary. Returns: @@ -325,42 +211,25 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): """ m = cls.__new__(cls) fields_values: dict[str, Any] = {} - fields_set = set() - - for name, field in cls.__pydantic_fields__.items(): - if field.alias is not None and field.alias in values: + defaults: dict[str, Any] = {} # keeping this separate from `fields_values` helps us compute `_fields_set` + for name, field in cls.model_fields.items(): + if field.alias and field.alias in values: fields_values[name] = values.pop(field.alias) - fields_set.add(name) - - if (name not in fields_set) and (field.validation_alias is not None): - validation_aliases: list[str | AliasPath] = ( - field.validation_alias.choices - if isinstance(field.validation_alias, AliasChoices) - else [field.validation_alias] - ) - - for alias in validation_aliases: - if isinstance(alias, str) and alias in values: - fields_values[name] = values.pop(alias) - fields_set.add(name) - break - elif isinstance(alias, AliasPath): - value = alias.search_dict_for_path(values) - if value is not PydanticUndefined: - fields_values[name] = value - fields_set.add(name) - break - - if name not in fields_set: - if name in values: - fields_values[name] = values.pop(name) - fields_set.add(name) - elif not field.is_required(): - fields_values[name] = field.get_default(call_default_factory=True, validated_data=fields_values) + elif name in values: + fields_values[name] = values.pop(name) + elif not field.is_required(): + defaults[name] = field.get_default(call_default_factory=True) if _fields_set is None: - _fields_set = fields_set + _fields_set = set(fields_values.keys()) + fields_values.update(defaults) - _extra: dict[str, Any] | None = values if cls.model_config.get('extra') == 'allow' else None + _extra: dict[str, Any] | None = None + if cls.model_config.get('extra') == 'allow': + _extra = {} + for k, v in values.items(): + _extra[k] = v + else: + fields_values.update(values) _object_setattr(m, '__dict__', fields_values) _object_setattr(m, '__pydantic_fields_set__', _fields_set) if not cls.__pydantic_root_model__: @@ -368,12 +237,6 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): if cls.__pydantic_post_init__: m.model_post_init(None) - # update private attributes with values set - if hasattr(m, '__pydantic_private__') and m.__pydantic_private__ is not None: - for k, v in values.items(): - if k in m.__private_attributes__: - m.__pydantic_private__[k] = v - elif not cls.__pydantic_root_model__: # Note: if there are any private attributes, cls.__pydantic_post_init__ would exist # Since it doesn't, that means that `__pydantic_private__` should be set to None @@ -381,17 +244,11 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return m - def model_copy(self, *, update: Mapping[str, Any] | None = None, deep: bool = False) -> Self: - """!!! abstract "Usage Documentation" - [`model_copy`](../concepts/models.md#model-copy) + def model_copy(self: Model, *, update: dict[str, Any] | None = None, deep: bool = False) -> Model: + """Usage docs: https://docs.pydantic.dev/2.5/concepts/serialization/#model_copy Returns a copy of the model. - !!! note - The underlying instance's [`__dict__`][object.__dict__] attribute is copied. This - might have unexpected side effects if you store anything in it, on top of the model - fields (e.g. the value of [cached properties][functools.cached_property]). - Args: update: Values to change/add in the new model. Note: the data is not validated before creating the new model. You should trust this data. @@ -404,7 +261,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): if update: if self.model_config.get('extra') == 'allow': for k, v in update.items(): - if k in self.__pydantic_fields__: + if k in self.model_fields: copied.__dict__[k] = v else: if copied.__pydantic_extra__ is None: @@ -419,44 +276,31 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): self, *, mode: Literal['json', 'python'] | str = 'python', - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool | None = None, + include: IncEx = None, + exclude: IncEx = None, + by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, + warnings: bool = True, ) -> dict[str, Any]: - """!!! abstract "Usage Documentation" - [`model_dump`](../concepts/serialization.md#python-mode) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/serialization/#modelmodel_dump Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. Args: mode: The mode in which `to_python` should run. - If mode is 'json', the output will only contain JSON serializable types. - If mode is 'python', the output may contain non-JSON-serializable Python objects. - include: A set of fields to include in the output. - exclude: A set of fields to exclude from the output. - context: Additional context to pass to the serializer. + If mode is 'json', the dictionary will only contain JSON serializable types. + If mode is 'python', the dictionary may contain any Python objects. + include: A list of fields to include in the output. + exclude: A list of fields to exclude from the output. by_alias: Whether to use the field's alias in the dictionary key if defined. exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that are set to their default value. - exclude_none: Whether to exclude fields that have a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. - While this can be useful for round-tripping, it is usually recommended to use the dedicated - `round_trip` parameter instead. - round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. - warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. - fallback: A function to call when an unknown value is encountered. If not provided, - a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + exclude_defaults: Whether to exclude fields that are set to their default value from the output. + exclude_none: Whether to exclude fields that have a value of `None` from the output. + round_trip: Whether to enable serialization and deserialization round-trip support. + warnings: Whether to log warnings when invalid fields are encountered. Returns: A dictionary representation of the model. @@ -467,60 +311,40 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): by_alias=by_alias, include=include, exclude=exclude, - context=context, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, - exclude_computed_fields=exclude_computed_fields, round_trip=round_trip, warnings=warnings, - fallback=fallback, - serialize_as_any=serialize_as_any, ) def model_dump_json( self, *, indent: int | None = None, - ensure_ascii: bool = False, - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, - by_alias: bool | None = None, + include: IncEx = None, + exclude: IncEx = None, + by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, + warnings: bool = True, ) -> str: - """!!! abstract "Usage Documentation" - [`model_dump_json`](../concepts/serialization.md#json-mode) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/serialization/#modelmodel_dump_json Generates a JSON representation of the model using Pydantic's `to_json` method. Args: indent: Indentation to use in the JSON output. If None is passed, the output will be compact. - ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. - If `False` (the default), these characters will be output as-is. - include: Field(s) to include in the JSON output. - exclude: Field(s) to exclude from the JSON output. - context: Additional context to pass to the serializer. + include: Field(s) to include in the JSON output. Can take either a string or set of strings. + exclude: Field(s) to exclude from the JSON output. Can take either a string or set of strings. by_alias: Whether to serialize using field aliases. exclude_unset: Whether to exclude fields that have not been explicitly set. - exclude_defaults: Whether to exclude fields that are set to their default value. + exclude_defaults: Whether to exclude fields that have the default value. exclude_none: Whether to exclude fields that have a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. - While this can be useful for round-tripping, it is usually recommended to use the dedicated - `round_trip` parameter instead. - round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T]. - warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. - fallback: A function to call when an unknown value is encountered. If not provided, - a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. + round_trip: Whether to use serialization/deserialization between JSON and class instance. + warnings: Whether to show any warnings that occurred during serialization. Returns: A JSON string representation of the model. @@ -528,19 +352,14 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return self.__pydantic_serializer__.to_json( self, indent=indent, - ensure_ascii=ensure_ascii, include=include, exclude=exclude, - context=context, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, - exclude_computed_fields=exclude_computed_fields, round_trip=round_trip, warnings=warnings, - fallback=fallback, - serialize_as_any=serialize_as_any, ).decode() @classmethod @@ -550,22 +369,12 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): ref_template: str = DEFAULT_REF_TEMPLATE, schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, mode: JsonSchemaMode = 'validation', - *, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', ) -> dict[str, Any]: """Generates a JSON schema for a model class. Args: by_alias: Whether to use attribute aliases or not. ref_template: The reference template. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. schema_generator: To override the logic used to generate the JSON schema, as a subclass of `GenerateJsonSchema` with your desired modifications mode: The mode in which to generate the schema. @@ -574,12 +383,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): The JSON schema for the given model class. """ return model_json_schema( - cls, - by_alias=by_alias, - ref_template=ref_template, - union_format=union_format, - schema_generator=schema_generator, - mode=mode, + cls, by_alias=by_alias, ref_template=ref_template, schema_generator=schema_generator, mode=mode ) @classmethod @@ -599,7 +403,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): Raises: TypeError: Raised when trying to generate concrete names for non-generic models. """ - if not issubclass(cls, Generic): + if not issubclass(cls, typing.Generic): raise TypeError('Concrete names should only be generated for generic models.') # Any strings received should represent forward references, so we handle them specially below. @@ -609,10 +413,11 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): params_component = ', '.join(param_names) return f'{cls.__name__}[{params_component}]' - def model_post_init(self, context: Any, /) -> None: + def model_post_init(self, __context: Any) -> None: """Override this method to perform additional initialization after `__init__` and `model_construct`. This is useful if you want to do some validation that requires the entire model to be initialized. """ + pass @classmethod def model_rebuild( @@ -621,7 +426,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): force: bool = False, raise_errors: bool = True, _parent_namespace_depth: int = 2, - _types_namespace: MappingNamespace | None = None, + _types_namespace: dict[str, Any] | None = None, ) -> bool | None: """Try to rebuild the pydantic-core schema for the model. @@ -638,65 +443,54 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): Returns `None` if the schema is already "complete" and rebuilding was not required. If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. """ - already_complete = cls.__pydantic_complete__ - if already_complete and not force: + if not force and cls.__pydantic_complete__: return None - - cls.__pydantic_complete__ = False - - for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): - if attr in cls.__dict__ and not isinstance(getattr(cls, attr), _mock_val_ser.MockValSer): - # Deleting the validator/serializer is necessary as otherwise they can get reused in - # pydantic-core. We do so only if they aren't mock instances, otherwise — as `model_rebuild()` - # isn't thread-safe — concurrent model instantiations can lead to the parent validator being used. - # Same applies for the core schema that can be reused in schema generation. - delattr(cls, attr) - - if _types_namespace is not None: - rebuild_ns = _types_namespace - elif _parent_namespace_depth > 0: - rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} else: - rebuild_ns = {} + if '__pydantic_core_schema__' in cls.__dict__: + delattr(cls, '__pydantic_core_schema__') # delete cached value to ensure full rebuild happens + if _types_namespace is not None: + types_namespace: dict[str, Any] | None = _types_namespace.copy() + else: + if _parent_namespace_depth > 0: + frame_parent_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth) or {} + cls_parent_ns = ( + _model_construction.unpack_lenient_weakvaluedict(cls.__pydantic_parent_namespace__) or {} + ) + types_namespace = {**cls_parent_ns, **frame_parent_ns} + cls.__pydantic_parent_namespace__ = _model_construction.build_lenient_weakvaluedict(types_namespace) + else: + types_namespace = _model_construction.unpack_lenient_weakvaluedict( + cls.__pydantic_parent_namespace__ + ) - parent_ns = _model_construction.unpack_lenient_weakvaluedict(cls.__pydantic_parent_namespace__) or {} + types_namespace = _typing_extra.get_cls_types_namespace(cls, types_namespace) - ns_resolver = _namespace_utils.NsResolver( - parent_namespace={**rebuild_ns, **parent_ns}, - ) - - return _model_construction.complete_model_class( - cls, - _config.ConfigWrapper(cls.model_config, check=False), - ns_resolver, - raise_errors=raise_errors, - # If the model was already complete, we don't need to call the hook again. - call_on_complete_hook=not already_complete, - ) + # manually override defer_build so complete_model_class doesn't skip building the model again + config = {**cls.model_config, 'defer_build': False} + return _model_construction.complete_model_class( + cls, + cls.__name__, + _config.ConfigWrapper(config, check=False), + raise_errors=raise_errors, + types_namespace=types_namespace, + ) @classmethod def model_validate( - cls, + cls: type[Model], obj: Any, *, strict: bool | None = None, - extra: ExtraValues | None = None, from_attributes: bool | None = None, - context: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, - ) -> Self: + context: dict[str, Any] | None = None, + ) -> Model: """Validate a pydantic model instance. Args: obj: The object to validate. - strict: Whether to enforce types strictly. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. + strict: Whether to raise an exception on invalid fields. from_attributes: Whether to extract data from object attributes. context: Additional context to pass to the validator. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Raises: ValidationError: If the object could not be validated. @@ -706,140 +500,97 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): """ # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks __tracebackhide__ = True - - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) - return cls.__pydantic_validator__.validate_python( - obj, - strict=strict, - extra=extra, - from_attributes=from_attributes, - context=context, - by_alias=by_alias, - by_name=by_name, + obj, strict=strict, from_attributes=from_attributes, context=context ) @classmethod def model_validate_json( - cls, + cls: type[Model], json_data: str | bytes | bytearray, *, strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, - ) -> Self: - """!!! abstract "Usage Documentation" - [JSON Parsing](../concepts/json.md#json-parsing) + context: dict[str, Any] | None = None, + ) -> Model: + """Usage docs: https://docs.pydantic.dev/2.5/concepts/json/#json-parsing Validate the given JSON data against the Pydantic model. Args: json_data: The JSON data to validate. strict: Whether to enforce types strictly. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. context: Extra variables to pass to the validator. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Returns: The validated Pydantic model. Raises: - ValidationError: If `json_data` is not a JSON string or the object could not be validated. + ValueError: If `json_data` is not a JSON string. """ # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks __tracebackhide__ = True - - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) - - return cls.__pydantic_validator__.validate_json( - json_data, strict=strict, extra=extra, context=context, by_alias=by_alias, by_name=by_name - ) + return cls.__pydantic_validator__.validate_json(json_data, strict=strict, context=context) @classmethod def model_validate_strings( - cls, + cls: type[Model], obj: Any, *, strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, - ) -> Self: - """Validate the given object with string data against the Pydantic model. + context: dict[str, Any] | None = None, + ) -> Model: + """Validate the given object contains string data against the Pydantic model. Args: - obj: The object containing string data to validate. + obj: The object contains string data to validate. strict: Whether to enforce types strictly. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. context: Extra variables to pass to the validator. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Returns: The validated Pydantic model. """ # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks __tracebackhide__ = True - - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) - - return cls.__pydantic_validator__.validate_strings( - obj, strict=strict, extra=extra, context=context, by_alias=by_alias, by_name=by_name - ) + return cls.__pydantic_validator__.validate_strings(obj, strict=strict, context=context) @classmethod - def __get_pydantic_core_schema__(cls, source: type[BaseModel], handler: GetCoreSchemaHandler, /) -> CoreSchema: - # This warning is only emitted when calling `super().__get_pydantic_core_schema__` from a model subclass. - # In the generate schema logic, this method (`BaseModel.__get_pydantic_core_schema__`) is special cased to - # *not* be called if not overridden. - warnings.warn( - 'The `__get_pydantic_core_schema__` method of the `BaseModel` class is deprecated. If you are calling ' - '`super().__get_pydantic_core_schema__` when overriding the method on a Pydantic model, consider using ' - '`handler(source)` instead. However, note that overriding this method on models can lead to unexpected ' - 'side effects.', - PydanticDeprecatedSince211, - stacklevel=2, - ) - # Logic copied over from `GenerateSchema._model_schema`: - schema = cls.__dict__.get('__pydantic_core_schema__') - if schema is not None and not isinstance(schema, _mock_val_ser.MockCoreSchema): - return cls.__pydantic_core_schema__ + def __get_pydantic_core_schema__(cls, __source: type[BaseModel], __handler: GetCoreSchemaHandler) -> CoreSchema: + """Hook into generating the model's CoreSchema. - return handler(source) + Args: + __source: The class we are generating a schema for. + This will generally be the same as the `cls` argument if this is a classmethod. + __handler: Call into Pydantic's internal JSON schema generation. + A callable that calls into Pydantic's internal CoreSchema generation logic. + + Returns: + A `pydantic-core` `CoreSchema`. + """ + # Only use the cached value from this _exact_ class; we don't want one from a parent class + # This is why we check `cls.__dict__` and don't use `cls.__pydantic_core_schema__` or similar. + if '__pydantic_core_schema__' in cls.__dict__: + # Due to the way generic classes are built, it's possible that an invalid schema may be temporarily + # set on generic classes. I think we could resolve this to ensure that we get proper schema caching + # for generics, but for simplicity for now, we just always rebuild if the class has a generic origin. + if not cls.__pydantic_generic_metadata__['origin']: + return cls.__pydantic_core_schema__ + + return __handler(__source) @classmethod def __get_pydantic_json_schema__( cls, - core_schema: CoreSchema, - handler: GetJsonSchemaHandler, - /, + __core_schema: CoreSchema, + __handler: GetJsonSchemaHandler, ) -> JsonSchemaValue: """Hook into generating the model's JSON schema. Args: - core_schema: A `pydantic-core` CoreSchema. + __core_schema: A `pydantic-core` CoreSchema. You can ignore this argument and call the handler with a new CoreSchema, wrap this CoreSchema (`{'type': 'nullable', 'schema': current_schema}`), or just call the handler with the original schema. - handler: Call into Pydantic's internal JSON schema generation. + __handler: Call into Pydantic's internal JSON schema generation. This will raise a `pydantic.errors.PydanticInvalidForJsonSchema` if JSON schema generation fails. Since this gets called by `BaseModel.model_json_schema` you can override the @@ -849,41 +600,26 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): Returns: A JSON schema, as a Python object. """ - return handler(core_schema) + return __handler(__core_schema) @classmethod def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: """This is intended to behave just like `__init_subclass__`, but is called by `ModelMetaclass` - only after basic class initialization is complete. In particular, attributes like `model_fields` will - be present when this is called, but forward annotations are not guaranteed to be resolved yet, - meaning that creating an instance of the class may fail. + only after the class is actually fully initialized. In particular, attributes like `model_fields` will + be present when this is called. This is necessary because `__init_subclass__` will always be called by `type.__new__`, and it would require a prohibitively large refactor to the `ModelMetaclass` to ensure that `type.__new__` was called in such a manner that the class would already be sufficiently initialized. This will receive the same `kwargs` that would be passed to the standard `__init_subclass__`, namely, - any kwargs passed to the class definition that aren't used internally by Pydantic. + any kwargs passed to the class definition that aren't used internally by pydantic. Args: **kwargs: Any keyword arguments passed to the class definition that aren't used internally - by Pydantic. - - Note: - You may want to override [`__pydantic_on_complete__()`][pydantic.main.BaseModel.__pydantic_on_complete__] - instead, which is called once the class and its fields are fully initialized and ready for validation. - """ - - @classmethod - def __pydantic_on_complete__(cls) -> None: - """This is called once the class and its fields are fully initialized and ready to be used. - - This typically happens when the class is created (just before - [`__pydantic_init_subclass__()`][pydantic.main.BaseModel.__pydantic_init_subclass__] is called on the superclass), - except when forward annotations are used that could not immediately be resolved. - In that case, it will be called later, when the model is rebuilt automatically or explicitly using - [`model_rebuild()`][pydantic.main.BaseModel.model_rebuild]. + by pydantic. """ + pass def __class_getitem__( cls, typevar_values: type[Any] | tuple[type[Any], ...] @@ -896,17 +632,17 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): raise TypeError('Type parameters should be placed on typing.Generic, not BaseModel') if not hasattr(cls, '__parameters__'): raise TypeError(f'{cls} cannot be parametrized because it does not inherit from typing.Generic') - if not cls.__pydantic_generic_metadata__['parameters'] and Generic not in cls.__bases__: + if not cls.__pydantic_generic_metadata__['parameters'] and typing.Generic not in cls.__bases__: raise TypeError(f'{cls} is not a generic class') if not isinstance(typevar_values, tuple): typevar_values = (typevar_values,) + _generics.check_parameters_count(cls, typevar_values) - # For a model `class Model[T, U, V = int](BaseModel): ...` parametrized with `(str, bool)`, - # this gives us `{T: str, U: bool, V: int}`: - typevars_map = _generics.map_generic_model_arguments(cls, typevar_values) - # We also update the provided args to use defaults values (`(str, bool)` becomes `(str, bool, int)`): - typevar_values = tuple(v for v in typevars_map.values()) + # Build map from generic typevars to passed params + typevars_map: dict[_typing_extra.TypeVarType, type[Any]] = dict( + zip(cls.__pydantic_generic_metadata__['parameters'], typevar_values) + ) if _utils.all_identical(typevars_map.keys(), typevars_map.values()) and typevars_map: submodel = cls # if arguments are equal to parameters it's the same object @@ -921,38 +657,35 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): origin = cls.__pydantic_generic_metadata__['origin'] or cls model_name = origin.model_parametrized_name(args) params = tuple( - dict.fromkeys(_generics.iter_contained_typevars(typevars_map.values())) + {param: None for param in _generics.iter_contained_typevars(typevars_map.values())} ) # use dict as ordered set with _generics.generic_recursion_self_type(origin, args) as maybe_self_type: + if maybe_self_type is not None: + return maybe_self_type + cached = _generics.get_cached_generic_type_late(cls, typevar_values, origin, args) if cached is not None: return cached - if maybe_self_type is not None: - return maybe_self_type - # Attempt to rebuild the origin in case new types have been defined try: - # depth 2 gets you above this __class_getitem__ call. - # Note that we explicitly provide the parent ns, otherwise - # `model_rebuild` will use the parent ns no matter if it is the ns of a module. - # We don't want this here, as this has unexpected effects when a model - # is being parametrized during a forward annotation evaluation. - parent_ns = _typing_extra.parent_frame_namespace(parent_depth=2) or {} - origin.model_rebuild(_types_namespace=parent_ns) + # depth 3 gets you above this __class_getitem__ call + origin.model_rebuild(_parent_namespace_depth=3) except PydanticUndefinedAnnotation: # It's okay if it fails, it just means there are still undefined types # that could be evaluated later. + # TODO: Make sure validation fails if there are still undefined types, perhaps using MockValidator pass submodel = _generics.create_generic_submodel(model_name, origin, args, params) + # Update cache _generics.set_cached_generic_type(cls, typevar_values, submodel, origin, args) return submodel - def __copy__(self) -> Self: + def __copy__(self: Model) -> Model: """Returns a shallow copy of the model.""" cls = type(self) m = cls.__new__(cls) @@ -960,7 +693,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): _object_setattr(m, '__pydantic_extra__', copy(self.__pydantic_extra__)) _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) - if not hasattr(self, '__pydantic_private__') or self.__pydantic_private__ is None: + if self.__pydantic_private__ is None: _object_setattr(m, '__pydantic_private__', None) else: _object_setattr( @@ -971,7 +704,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return m - def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Self: + def __deepcopy__(self: Model, memo: dict[int, Any] | None = None) -> Model: """Returns a deep copy of the model.""" cls = type(self) m = cls.__new__(cls) @@ -981,7 +714,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): # and attempting a deepcopy would be marginally slower. _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) - if not hasattr(self, '__pydantic_private__') or self.__pydantic_private__ is None: + if self.__pydantic_private__ is None: _object_setattr(m, '__pydantic_private__', None) else: _object_setattr( @@ -992,9 +725,8 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return m - if not TYPE_CHECKING: + if not typing.TYPE_CHECKING: # We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access - # The same goes for __setattr__ and __delattr__, see: https://github.com/pydantic/pydantic/issues/8643 def __getattr__(self, item: str) -> Any: private_attributes = object.__getattribute__(self, '__private_attributes__') @@ -1016,8 +748,11 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): except AttributeError: pydantic_extra = None - if pydantic_extra and item in pydantic_extra: - return pydantic_extra[item] + if pydantic_extra is not None: + try: + return pydantic_extra[item] + except KeyError as exc: + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc else: if hasattr(self.__class__, item): return super().__getattribute__(item) # Raises AttributeError if appropriate @@ -1025,105 +760,88 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): # this is the current error raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') - def __setattr__(self, name: str, value: Any) -> None: - if (setattr_handler := self.__pydantic_setattr_handlers__.get(name)) is not None: - setattr_handler(self, name, value) - # if None is returned from _setattr_handler, the attribute was set directly - elif (setattr_handler := self._setattr_handler(name, value)) is not None: - setattr_handler(self, name, value) # call here to not memo on possibly unknown fields - self.__pydantic_setattr_handlers__[name] = setattr_handler # memoize the handler for faster access - - def _setattr_handler(self, name: str, value: Any) -> Callable[[BaseModel, str, Any], None] | None: - """Get a handler for setting an attribute on the model instance. - - Returns: - A handler for setting an attribute on the model instance. Used for memoization of the handler. - Memoizing the handlers leads to a dramatic performance improvement in `__setattr__` - Returns `None` when memoization is not safe, then the attribute is set directly. - """ - cls = self.__class__ - if name in cls.__class_vars__: - raise AttributeError( - f'{name!r} is a ClassVar of `{cls.__name__}` and cannot be set on an instance. ' - f'If you want to set a value on the class, use `{cls.__name__}.{name} = value`.' - ) - elif not _fields.is_valid_field_name(name): - if (attribute := cls.__private_attributes__.get(name)) is not None: - if hasattr(attribute, '__set__'): - return lambda model, _name, val: attribute.__set__(model, val) - else: - return _SIMPLE_SETATTR_HANDLERS['private'] - else: - _object_setattr(self, name, value) - return None # Can not return memoized handler with possibly freeform attr names - - attr = getattr(cls, name, None) - # NOTE: We currently special case properties and `cached_property`, but we might need - # to generalize this to all data/non-data descriptors at some point. For non-data descriptors - # (such as `cached_property`), it isn't obvious though. `cached_property` caches the value - # to the instance's `__dict__`, but other non-data descriptors might do things differently. - if isinstance(attr, cached_property): - return _SIMPLE_SETATTR_HANDLERS['cached_property'] - - _check_frozen(cls, name, value) - - # We allow properties to be set only on non frozen models for now (to match dataclasses). - # This can be changed if it ever gets requested. - if isinstance(attr, property): - return lambda model, _name, val: attr.__set__(model, val) - elif cls.model_config.get('validate_assignment'): - return _SIMPLE_SETATTR_HANDLERS['validate_assignment'] - elif name not in cls.__pydantic_fields__: - if cls.model_config.get('extra') != 'allow': - # TODO - matching error - raise ValueError(f'"{cls.__name__}" object has no field "{name}"') - elif attr is None: - # attribute does not exist, so put it in extra - self.__pydantic_extra__[name] = value - return None # Can not return memoized handler with possibly freeform attr names - else: - # attribute _does_ exist, and was not in extra, so update it - return _SIMPLE_SETATTR_HANDLERS['extra_known'] + def __setattr__(self, name: str, value: Any) -> None: + if name in self.__class_vars__: + raise AttributeError( + f'{name!r} is a ClassVar of `{self.__class__.__name__}` and cannot be set on an instance. ' + f'If you want to set a value on the class, use `{self.__class__.__name__}.{name} = value`.' + ) + elif not _fields.is_valid_field_name(name): + if self.__pydantic_private__ is None or name not in self.__private_attributes__: + _object_setattr(self, name, value) else: - return _SIMPLE_SETATTR_HANDLERS['model_field'] + attribute = self.__private_attributes__[name] + if hasattr(attribute, '__set__'): + attribute.__set__(self, value) # type: ignore + else: + self.__pydantic_private__[name] = value + return - def __delattr__(self, item: str) -> Any: - cls = self.__class__ + self._check_frozen(name, value) - if item in self.__private_attributes__: - attribute = self.__private_attributes__[item] - if hasattr(attribute, '__delete__'): - attribute.__delete__(self) # type: ignore - return - - try: - # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items - del self.__pydantic_private__[item] # type: ignore - return - except KeyError as exc: - raise AttributeError(f'{cls.__name__!r} object has no attribute {item!r}') from exc - - # Allow cached properties to be deleted (even if the class is frozen): - attr = getattr(cls, item, None) - if isinstance(attr, cached_property): - return object.__delattr__(self, item) - - _check_frozen(cls, name=item, value=None) - - if item in self.__pydantic_fields__: - object.__delattr__(self, item) - elif self.__pydantic_extra__ is not None and item in self.__pydantic_extra__: - del self.__pydantic_extra__[item] + attr = getattr(self.__class__, name, None) + if isinstance(attr, property): + attr.__set__(self, value) + elif self.model_config.get('validate_assignment', None): + self.__pydantic_validator__.validate_assignment(self, name, value) + elif self.model_config.get('extra') != 'allow' and name not in self.model_fields: + # TODO - matching error + raise ValueError(f'"{self.__class__.__name__}" object has no field "{name}"') + elif self.model_config.get('extra') == 'allow' and name not in self.model_fields: + if self.model_extra and name in self.model_extra: + self.__pydantic_extra__[name] = value # type: ignore else: try: - object.__delattr__(self, item) + getattr(self, name) except AttributeError: - raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') + # attribute does not already exist on instance, so put it in extra + self.__pydantic_extra__[name] = value # type: ignore + else: + # attribute _does_ already exist on instance, and was not in extra, so update it + _object_setattr(self, name, value) + else: + self.__dict__[name] = value + self.__pydantic_fields_set__.add(name) - # Because we make use of `@dataclass_transform()`, `__replace__` is already synthesized by - # type checkers, so we define the implementation in this `if not TYPE_CHECKING:` block: - def __replace__(self, **changes: Any) -> Self: - return self.model_copy(update=changes) + def __delattr__(self, item: str) -> Any: + if item in self.__private_attributes__: + attribute = self.__private_attributes__[item] + if hasattr(attribute, '__delete__'): + attribute.__delete__(self) # type: ignore + return + + try: + # Note: self.__pydantic_private__ cannot be None if self.__private_attributes__ has items + del self.__pydantic_private__[item] # type: ignore + return + except KeyError as exc: + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') from exc + + self._check_frozen(item, None) + + if item in self.model_fields: + object.__delattr__(self, item) + elif self.__pydantic_extra__ is not None and item in self.__pydantic_extra__: + del self.__pydantic_extra__[item] + else: + try: + object.__delattr__(self, item) + except AttributeError: + raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}') + + def _check_frozen(self, name: str, value: Any) -> None: + if self.model_config.get('frozen', None): + typ = 'frozen_instance' + elif getattr(self.model_fields.get(name), 'frozen', False): + typ = 'frozen_field' + else: + return + error: pydantic_core.InitErrorDetails = { + 'type': typ, + 'loc': (name,), + 'input': value, + } + raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error]) def __getstate__(self) -> dict[Any, Any]: private = self.__pydantic_private__ @@ -1137,69 +855,29 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): } def __setstate__(self, state: dict[Any, Any]) -> None: - _object_setattr(self, '__pydantic_fields_set__', state.get('__pydantic_fields_set__', {})) - _object_setattr(self, '__pydantic_extra__', state.get('__pydantic_extra__', {})) - _object_setattr(self, '__pydantic_private__', state.get('__pydantic_private__', {})) - _object_setattr(self, '__dict__', state.get('__dict__', {})) + _object_setattr(self, '__pydantic_fields_set__', state['__pydantic_fields_set__']) + _object_setattr(self, '__pydantic_extra__', state['__pydantic_extra__']) + _object_setattr(self, '__pydantic_private__', state['__pydantic_private__']) + _object_setattr(self, '__dict__', state['__dict__']) - if not TYPE_CHECKING: + def __eq__(self, other: Any) -> bool: + if isinstance(other, BaseModel): + # When comparing instances of generic types for equality, as long as all field values are equal, + # only require their generic origin types to be equal, rather than exact type equality. + # This prevents headaches like MyGeneric(x=1) != MyGeneric[Any](x=1). + self_type = self.__pydantic_generic_metadata__['origin'] or self.__class__ + other_type = other.__pydantic_generic_metadata__['origin'] or other.__class__ - def __eq__(self, other: Any) -> bool: - if isinstance(other, BaseModel): - # When comparing instances of generic types for equality, as long as all field values are equal, - # only require their generic origin types to be equal, rather than exact type equality. - # This prevents headaches like MyGeneric(x=1) != MyGeneric[Any](x=1). - self_type = self.__pydantic_generic_metadata__['origin'] or self.__class__ - other_type = other.__pydantic_generic_metadata__['origin'] or other.__class__ + return ( + self_type == other_type + and self.__dict__ == other.__dict__ + and self.__pydantic_private__ == other.__pydantic_private__ + and self.__pydantic_extra__ == other.__pydantic_extra__ + ) + else: + return NotImplemented # delegate to the other item in the comparison - # Perform common checks first - if not ( - self_type == other_type - and getattr(self, '__pydantic_private__', None) == getattr(other, '__pydantic_private__', None) - and self.__pydantic_extra__ == other.__pydantic_extra__ - ): - return False - - # We only want to compare pydantic fields but ignoring fields is costly. - # We'll perform a fast check first, and fallback only when needed - # See GH-7444 and GH-7825 for rationale and a performance benchmark - - # First, do the fast (and sometimes faulty) __dict__ comparison - if self.__dict__ == other.__dict__: - # If the check above passes, then pydantic fields are equal, we can return early - return True - - # We don't want to trigger unnecessary costly filtering of __dict__ on all unequal objects, so we return - # early if there are no keys to ignore (we would just return False later on anyway) - model_fields = type(self).__pydantic_fields__.keys() - if self.__dict__.keys() <= model_fields and other.__dict__.keys() <= model_fields: - return False - - # If we reach here, there are non-pydantic-fields keys, mapped to unequal values, that we need to ignore - # Resort to costly filtering of the __dict__ objects - # We use operator.itemgetter because it is much faster than dict comprehensions - # NOTE: Contrary to standard python class and instances, when the Model class has a default value for an - # attribute and the model instance doesn't have a corresponding attribute, accessing the missing attribute - # raises an error in BaseModel.__getattr__ instead of returning the class attribute - # So we can use operator.itemgetter() instead of operator.attrgetter() - getter = operator.itemgetter(*model_fields) if model_fields else lambda _: _utils._SENTINEL - try: - return getter(self.__dict__) == getter(other.__dict__) - except KeyError: - # In rare cases (such as when using the deprecated BaseModel.copy() method), - # the __dict__ may not contain all model fields, which is how we can get here. - # getter(self.__dict__) is much faster than any 'safe' method that accounts - # for missing keys, and wrapping it in a `try` doesn't slow things down much - # in the common case. - self_fields_proxy = _utils.SafeGetItemProxy(self.__dict__) - other_fields_proxy = _utils.SafeGetItemProxy(other.__dict__) - return getter(self_fields_proxy) == getter(other_fields_proxy) - - # other instance is not a BaseModel - else: - return NotImplemented # delegate to the other item in the comparison - - if TYPE_CHECKING: + if typing.TYPE_CHECKING: # We put `__init_subclass__` in a TYPE_CHECKING block because, even though we want the type-checking benefits # described in the signature of `__init_subclass__` below, we don't want to modify the default behavior of # subclass initialization. @@ -1208,10 +886,11 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): """This signature is included purely to help type-checkers check arguments to class declaration, which provides a way to conveniently set model_config key/value pairs. - ```python + ```py from pydantic import BaseModel - class MyModel(BaseModel, extra='allow'): ... + class MyModel(BaseModel, extra='allow'): + ... ``` However, this may be deceiving, since the _actual_ calls to `__init_subclass__` will not receive any @@ -1237,20 +916,11 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return f'{self.__repr_name__()}({self.__repr_str__(", ")})' def __repr_args__(self) -> _repr.ReprArgs: - # Eagerly create the repr of computed fields, as this may trigger access of cached properties and as such - # modify the instance's `__dict__`. If we don't do it now, it could happen when iterating over the `__dict__` - # below if the instance happens to be referenced in a field, and would modify the `__dict__` size *during* iteration. - computed_fields_repr_args = [ - (k, getattr(self, k)) for k, v in self.__pydantic_computed_fields__.items() if v.repr - ] - for k, v in self.__dict__.items(): - field = self.__pydantic_fields__.get(k) + field = self.model_fields.get(k) if field and field.repr: - if v is not self: - yield k, v - else: - yield k, self.__repr_recursion__(v) + yield k, v + # `__pydantic_extra__` can fail to be set if the model is not yet fully initialized. # This can happen if a `ValidationError` is raised during initialization and the instance's # repr is generated as part of the exception handling. Therefore, we use `getattr` here @@ -1262,11 +932,10 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): if pydantic_extra is not None: yield from ((k, v) for k, v in pydantic_extra.items()) - yield from computed_fields_repr_args + yield from ((k, getattr(self, k)) for k, v in self.model_computed_fields.items() if v.repr) # take logic from `_repr.Representation` without the side effects of inheritance, see #5740 __repr_name__ = _repr.Representation.__repr_name__ - __repr_recursion__ = _repr.Representation.__repr_recursion__ __repr_str__ = _repr.Representation.__repr_str__ __pretty__ = _repr.Representation.__pretty__ __rich_repr__ = _repr.Representation.__rich_repr__ @@ -1277,45 +946,37 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): # ##### Deprecated methods from v1 ##### @property @typing_extensions.deprecated( - 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', category=None + 'The `__fields__` attribute is deprecated, use `model_fields` instead.', category=PydanticDeprecatedSince20 ) def __fields__(self) -> dict[str, FieldInfo]: - warnings.warn( - 'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) - return getattr(type(self), '__pydantic_fields__', {}) + warnings.warn('The `__fields__` attribute is deprecated, use `model_fields` instead.', DeprecationWarning) + return self.model_fields @property @typing_extensions.deprecated( 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', - category=None, + category=PydanticDeprecatedSince20, ) def __fields_set__(self) -> set[str]: warnings.warn( - 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The `__fields_set__` attribute is deprecated, use `model_fields_set` instead.', DeprecationWarning ) return self.__pydantic_fields_set__ - @typing_extensions.deprecated('The `dict` method is deprecated; use `model_dump` instead.', category=None) + @typing_extensions.deprecated( + 'The `dict` method is deprecated; use `model_dump` instead.', category=PydanticDeprecatedSince20 + ) def dict( # noqa: D102 self, *, - include: IncEx | None = None, - exclude: IncEx | None = None, + include: IncEx = None, + exclude: IncEx = None, by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - ) -> Dict[str, Any]: # noqa UP006 - warnings.warn( - 'The `dict` method is deprecated; use `model_dump` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + ) -> typing.Dict[str, Any]: # noqa UP006 + warnings.warn('The `dict` method is deprecated; use `model_dump` instead.', DeprecationWarning) return self.model_dump( include=include, exclude=exclude, @@ -1325,25 +986,23 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): exclude_none=exclude_none, ) - @typing_extensions.deprecated('The `json` method is deprecated; use `model_dump_json` instead.', category=None) + @typing_extensions.deprecated( + 'The `json` method is deprecated; use `model_dump_json` instead.', category=PydanticDeprecatedSince20 + ) def json( # noqa: D102 self, *, - include: IncEx | None = None, - exclude: IncEx | None = None, + include: IncEx = None, + exclude: IncEx = None, by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - encoder: Callable[[Any], Any] | None = PydanticUndefined, # type: ignore[assignment] + encoder: typing.Callable[[Any], Any] | None = PydanticUndefined, # type: ignore[assignment] models_as_dict: bool = PydanticUndefined, # type: ignore[assignment] **dumps_kwargs: Any, ) -> str: - warnings.warn( - 'The `json` method is deprecated; use `model_dump_json` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + warnings.warn('The `json` method is deprecated; use `model_dump_json` instead.', DeprecationWarning) if encoder is not PydanticUndefined: raise TypeError('The `encoder` argument is no longer supported; use field serializers instead.') if models_as_dict is not PydanticUndefined: @@ -1360,35 +1019,32 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): ) @classmethod - @typing_extensions.deprecated('The `parse_obj` method is deprecated; use `model_validate` instead.', category=None) - def parse_obj(cls, obj: Any) -> Self: # noqa: D102 - warnings.warn( - 'The `parse_obj` method is deprecated; use `model_validate` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + @typing_extensions.deprecated( + 'The `parse_obj` method is deprecated; use `model_validate` instead.', category=PydanticDeprecatedSince20 + ) + def parse_obj(cls: type[Model], obj: Any) -> Model: # noqa: D102 + warnings.warn('The `parse_obj` method is deprecated; use `model_validate` instead.', DeprecationWarning) return cls.model_validate(obj) @classmethod @typing_extensions.deprecated( 'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, ' 'otherwise load the data then use `model_validate` instead.', - category=None, + category=PydanticDeprecatedSince20, ) def parse_raw( # noqa: D102 - cls, + cls: type[Model], b: str | bytes, *, content_type: str | None = None, encoding: str = 'utf8', proto: DeprecatedParseProtocol | None = None, allow_pickle: bool = False, - ) -> Self: # pragma: no cover + ) -> Model: # pragma: no cover warnings.warn( 'The `parse_raw` method is deprecated; if your data is JSON use `model_validate_json`, ' 'otherwise load the data then use `model_validate` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + DeprecationWarning, ) from .deprecated import parse @@ -1427,22 +1083,21 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): @typing_extensions.deprecated( 'The `parse_file` method is deprecated; load the data from file, then if your data is JSON ' 'use `model_validate_json`, otherwise `model_validate` instead.', - category=None, + category=PydanticDeprecatedSince20, ) def parse_file( # noqa: D102 - cls, + cls: type[Model], path: str | Path, *, content_type: str | None = None, encoding: str = 'utf8', proto: DeprecatedParseProtocol | None = None, allow_pickle: bool = False, - ) -> Self: + ) -> Model: warnings.warn( 'The `parse_file` method is deprecated; load the data from file, then if your data is JSON ' - 'use `model_validate_json`, otherwise `model_validate` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'use `model_validate_json` otherwise `model_validate` instead.', + DeprecationWarning, ) from .deprecated import parse @@ -1459,14 +1114,13 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): @typing_extensions.deprecated( 'The `from_orm` method is deprecated; set ' "`model_config['from_attributes']=True` and use `model_validate` instead.", - category=None, + category=PydanticDeprecatedSince20, ) - def from_orm(cls, obj: Any) -> Self: # noqa: D102 + def from_orm(cls: type[Model], obj: Any) -> Model: # noqa: D102 warnings.warn( - 'The `from_orm` method is deprecated; set ' - "`model_config['from_attributes']=True` and use `model_validate` instead.", - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The `from_orm` method is deprecated; set `model_config["from_attributes"]=True` ' + 'and use `model_validate` instead.', + DeprecationWarning, ) if not cls.model_config.get('from_attributes', None): raise PydanticUserError( @@ -1475,28 +1129,24 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return cls.model_validate(obj) @classmethod - @typing_extensions.deprecated('The `construct` method is deprecated; use `model_construct` instead.', category=None) - def construct(cls, _fields_set: set[str] | None = None, **values: Any) -> Self: # noqa: D102 - warnings.warn( - 'The `construct` method is deprecated; use `model_construct` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + @typing_extensions.deprecated( + 'The `construct` method is deprecated; use `model_construct` instead.', category=PydanticDeprecatedSince20 + ) + def construct(cls: type[Model], _fields_set: set[str] | None = None, **values: Any) -> Model: # noqa: D102 + warnings.warn('The `construct` method is deprecated; use `model_construct` instead.', DeprecationWarning) return cls.model_construct(_fields_set=_fields_set, **values) @typing_extensions.deprecated( - 'The `copy` method is deprecated; use `model_copy` instead. ' - 'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.', - category=None, + 'The copy method is deprecated; use `model_copy` instead.', category=PydanticDeprecatedSince20 ) def copy( - self, + self: Model, *, include: AbstractSetIntStr | MappingIntStrAny | None = None, exclude: AbstractSetIntStr | MappingIntStrAny | None = None, - update: Dict[str, Any] | None = None, # noqa UP006 + update: typing.Dict[str, Any] | None = None, # noqa UP006 deep: bool = False, - ) -> Self: # pragma: no cover + ) -> Model: # pragma: no cover """Returns a copy of the model. !!! warning "Deprecated" @@ -1504,17 +1154,20 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): If you need `include` or `exclude`, use: - ```python {test="skip" lint="skip"} + ```py data = self.model_dump(include=include, exclude=exclude, round_trip=True) data = {**data, **(update or {})} copied = self.model_validate(data) ``` Args: - include: Optional set or mapping specifying which fields to include in the copied model. - exclude: Optional set or mapping specifying which fields to exclude in the copied model. - update: Optional dictionary of field-value pairs to override field values in the copied model. - deep: If True, the values of fields that are Pydantic models will be deep-copied. + include: Optional set or mapping + specifying which fields to include in the copied model. + exclude: Optional set or mapping + specifying which fields to exclude in the copied model. + update: Optional dictionary of field-value pairs to override field values + in the copied model. + deep: If True, the values of fields that are Pydantic models will be deep copied. Returns: A copy of the model with included, excluded and updated fields as specified. @@ -1522,8 +1175,7 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): warnings.warn( 'The `copy` method is deprecated; use `model_copy` instead. ' 'See the docstring of `BaseModel.copy` for details about how to handle `include` and `exclude`.', - category=PydanticDeprecatedSince20, - stacklevel=2, + DeprecationWarning, ) from .deprecated import copy_internals @@ -1562,32 +1214,29 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): return copy_internals._copy_and_set_values(self, values, fields_set, extra, private, deep=deep) @classmethod - @typing_extensions.deprecated('The `schema` method is deprecated; use `model_json_schema` instead.', category=None) + @typing_extensions.deprecated( + 'The `schema` method is deprecated; use `model_json_schema` instead.', category=PydanticDeprecatedSince20 + ) def schema( # noqa: D102 cls, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE - ) -> Dict[str, Any]: # noqa UP006 - warnings.warn( - 'The `schema` method is deprecated; use `model_json_schema` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + ) -> typing.Dict[str, Any]: # noqa UP006 + warnings.warn('The `schema` method is deprecated; use `model_json_schema` instead.', DeprecationWarning) return cls.model_json_schema(by_alias=by_alias, ref_template=ref_template) @classmethod @typing_extensions.deprecated( 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', - category=None, + category=PydanticDeprecatedSince20, ) def schema_json( # noqa: D102 cls, *, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, **dumps_kwargs: Any ) -> str: # pragma: no cover - warnings.warn( - 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) import json + warnings.warn( + 'The `schema_json` method is deprecated; use `model_json_schema` and json.dumps instead.', + DeprecationWarning, + ) from .deprecated.json import pydantic_encoder return json.dumps( @@ -1597,52 +1246,44 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): ) @classmethod - @typing_extensions.deprecated('The `validate` method is deprecated; use `model_validate` instead.', category=None) - def validate(cls, value: Any) -> Self: # noqa: D102 - warnings.warn( - 'The `validate` method is deprecated; use `model_validate` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + @typing_extensions.deprecated( + 'The `validate` method is deprecated; use `model_validate` instead.', category=PydanticDeprecatedSince20 + ) + def validate(cls: type[Model], value: Any) -> Model: # noqa: D102 + warnings.warn('The `validate` method is deprecated; use `model_validate` instead.', DeprecationWarning) return cls.model_validate(value) @classmethod @typing_extensions.deprecated( 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', - category=None, + category=PydanticDeprecatedSince20, ) def update_forward_refs(cls, **localns: Any) -> None: # noqa: D102 warnings.warn( - 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The `update_forward_refs` method is deprecated; use `model_rebuild` instead.', DeprecationWarning ) if localns: # pragma: no cover raise TypeError('`localns` arguments are not longer accepted.') cls.model_rebuild(force=True) @typing_extensions.deprecated( - 'The private method `_iter` will be removed and should no longer be used.', category=None + 'The private method `_iter` will be removed and should no longer be used.', category=PydanticDeprecatedSince20 ) def _iter(self, *args: Any, **kwargs: Any) -> Any: - warnings.warn( - 'The private method `_iter` will be removed and should no longer be used.', - category=PydanticDeprecatedSince20, - stacklevel=2, - ) + warnings.warn('The private method `_iter` will be removed and should no longer be used.', DeprecationWarning) + from .deprecated import copy_internals return copy_internals._iter(self, *args, **kwargs) @typing_extensions.deprecated( 'The private method `_copy_and_set_values` will be removed and should no longer be used.', - category=None, + category=PydanticDeprecatedSince20, ) def _copy_and_set_values(self, *args: Any, **kwargs: Any) -> Any: warnings.warn( - 'The private method `_copy_and_set_values` will be removed and should no longer be used.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The private method `_copy_and_set_values` will be removed and should no longer be used.', + DeprecationWarning, ) from .deprecated import copy_internals @@ -1651,110 +1292,88 @@ class BaseModel(metaclass=_model_construction.ModelMetaclass): @classmethod @typing_extensions.deprecated( 'The private method `_get_value` will be removed and should no longer be used.', - category=None, + category=PydanticDeprecatedSince20, ) def _get_value(cls, *args: Any, **kwargs: Any) -> Any: warnings.warn( - 'The private method `_get_value` will be removed and should no longer be used.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The private method `_get_value` will be removed and should no longer be used.', DeprecationWarning ) + from .deprecated import copy_internals return copy_internals._get_value(cls, *args, **kwargs) @typing_extensions.deprecated( 'The private method `_calculate_keys` will be removed and should no longer be used.', - category=None, + category=PydanticDeprecatedSince20, ) def _calculate_keys(self, *args: Any, **kwargs: Any) -> Any: warnings.warn( - 'The private method `_calculate_keys` will be removed and should no longer be used.', - category=PydanticDeprecatedSince20, - stacklevel=2, + 'The private method `_calculate_keys` will be removed and should no longer be used.', DeprecationWarning ) + from .deprecated import copy_internals return copy_internals._calculate_keys(self, *args, **kwargs) -ModelT = TypeVar('ModelT', bound=BaseModel) - - -@overload +@typing.overload def create_model( - model_name: str, - /, + __model_name: str, *, __config__: ConfigDict | None = None, __doc__: str | None = None, __base__: None = None, __module__: str = __name__, - __validators__: dict[str, Callable[..., Any]] | None = None, + __validators__: dict[str, classmethod] | None = None, __cls_kwargs__: dict[str, Any] | None = None, - __qualname__: str | None = None, - **field_definitions: Any | tuple[str, Any], -) -> type[BaseModel]: ... + **field_definitions: Any, +) -> type[BaseModel]: + ... -@overload +@typing.overload def create_model( - model_name: str, - /, + __model_name: str, *, __config__: ConfigDict | None = None, __doc__: str | None = None, - __base__: type[ModelT] | tuple[type[ModelT], ...], + __base__: type[Model] | tuple[type[Model], ...], __module__: str = __name__, - __validators__: dict[str, Callable[..., Any]] | None = None, + __validators__: dict[str, classmethod] | None = None, __cls_kwargs__: dict[str, Any] | None = None, - __qualname__: str | None = None, - **field_definitions: Any | tuple[str, Any], -) -> type[ModelT]: ... + **field_definitions: Any, +) -> type[Model]: + ... def create_model( # noqa: C901 - model_name: str, - /, + __model_name: str, *, __config__: ConfigDict | None = None, __doc__: str | None = None, - __base__: type[ModelT] | tuple[type[ModelT], ...] | None = None, + __base__: type[Model] | tuple[type[Model], ...] | None = None, __module__: str | None = None, - __validators__: dict[str, Callable[..., Any]] | None = None, + __validators__: dict[str, classmethod] | None = None, __cls_kwargs__: dict[str, Any] | None = None, - __qualname__: str | None = None, - # TODO PEP 747: replace `Any` by the TypeForm: - **field_definitions: Any | tuple[str, Any], -) -> type[ModelT]: - """!!! abstract "Usage Documentation" - [Dynamic Model Creation](../concepts/models.md#dynamic-model-creation) - - Dynamically creates and returns a new Pydantic model, in other words, `create_model` dynamically creates a + __slots__: tuple[str, ...] | None = None, + **field_definitions: Any, +) -> type[Model]: + """Dynamically creates and returns a new Pydantic model, in other words, `create_model` dynamically creates a subclass of [`BaseModel`][pydantic.BaseModel]. - !!! warning - This function may execute arbitrary code contained in field annotations, if string references need to be evaluated. - - See [Security implications of introspecting annotations](https://docs.python.org/3/library/annotationlib.html#annotationlib-security) for more information. - Args: - model_name: The name of the newly created model. + __model_name: The name of the newly created model. __config__: The configuration of the new model. __doc__: The docstring of the new model. - __base__: The base class or classes for the new model. - __module__: The name of the module that the model belongs to; - if `None`, the value is taken from `sys._getframe(1)` - __validators__: A dictionary of methods that validate fields. The keys are the names of the validation methods to - be added to the model, and the values are the validation methods themselves. You can read more about functional - validators [here](https://docs.pydantic.dev/2.9/concepts/validators/#field-validators). - __cls_kwargs__: A dictionary of keyword arguments for class creation, such as `metaclass`. - __qualname__: The qualified name of the newly created model. - **field_definitions: Field definitions of the new model. Either: - - - a single element, representing the type annotation of the field. - - a two-tuple, the first element being the type and the second element the assigned value - (either a default or the [`Field()`][pydantic.Field] function). + __base__: The base class for the new model. + __module__: The name of the module that the model belongs to, + if `None` the value is taken from `sys._getframe(1)` + __validators__: A dictionary of methods that validate fields. + __cls_kwargs__: A dictionary of keyword arguments for class creation. + __slots__: Deprecated. Should not be passed to `create_model`. + **field_definitions: Attributes of the new model. They should be passed in the format: + `=(, )` or `=(, )`. Returns: The new [model][pydantic.BaseModel]. @@ -1762,29 +1381,44 @@ def create_model( # noqa: C901 Raises: PydanticUserError: If `__base__` and `__config__` are both passed. """ - if __base__ is None: - __base__ = (cast('type[ModelT]', BaseModel),) - elif not isinstance(__base__, tuple): - __base__ = (__base__,) + if __slots__ is not None: + # __slots__ will be ignored from here on + warnings.warn('__slots__ should not be passed to create_model', RuntimeWarning) + + if __base__ is not None: + if __config__ is not None: + raise PydanticUserError( + 'to avoid confusion `__config__` and `__base__` cannot be used together', + code='create-model-config-base', + ) + if not isinstance(__base__, tuple): + __base__ = (__base__,) + else: + __base__ = (typing.cast(typing.Type['Model'], BaseModel),) __cls_kwargs__ = __cls_kwargs__ or {} - fields: dict[str, Any] = {} - annotations: dict[str, Any] = {} + fields = {} + annotations = {} for f_name, f_def in field_definitions.items(): + if not _fields.is_valid_field_name(f_name): + warnings.warn(f'fields may not start with an underscore, ignoring "{f_name}"', RuntimeWarning) if isinstance(f_def, tuple): - if len(f_def) != 2: + f_def = typing.cast('tuple[str, Any]', f_def) + try: + f_annotation, f_value = f_def + except ValueError as e: raise PydanticUserError( - f'Field definition for {f_name!r} should a single element representing the type or a two-tuple, the first element ' - 'being the type and the second element the assigned value (either a default or the `Field()` function).', + 'Field definitions should be a `(, )`.', code='create-model-field-definitions', - ) - - annotations[f_name] = f_def[0] - fields[f_name] = f_def[1] + ) from e else: - annotations[f_name] = f_def + f_annotation, f_value = None, f_def + + if f_annotation: + annotations[f_name] = f_annotation + fields[f_name] = f_value if __module__ is None: f = sys._getframe(1) @@ -1792,22 +1426,20 @@ def create_model( # noqa: C901 namespace: dict[str, Any] = {'__annotations__': annotations, '__module__': __module__} if __doc__: - namespace['__doc__'] = __doc__ - if __qualname__ is not None: - namespace['__qualname__'] = __qualname__ + namespace.update({'__doc__': __doc__}) if __validators__: namespace.update(__validators__) namespace.update(fields) if __config__: - namespace['model_config'] = __config__ + namespace['model_config'] = _config.ConfigWrapper(__config__).config_dict resolved_bases = types.resolve_bases(__base__) - meta, ns, kwds = types.prepare_class(model_name, resolved_bases, kwds=__cls_kwargs__) + meta, ns, kwds = types.prepare_class(__model_name, resolved_bases, kwds=__cls_kwargs__) if resolved_bases is not __base__: ns['__orig_bases__'] = __base__ namespace.update(ns) return meta( - model_name, + __model_name, resolved_bases, namespace, __pydantic_reset_parent_namespace__=False, diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/mypy.py b/Backend/venv/lib/python3.12/site-packages/pydantic/mypy.py index 6e8228ef..c4b6e2a6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/mypy.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/mypy.py @@ -3,9 +3,8 @@ from __future__ import annotations import sys -from collections.abc import Iterator from configparser import ConfigParser -from typing import Any, Callable +from typing import Any, Callable, Iterator from mypy.errorcodes import ErrorCode from mypy.expandtype import expand_type, expand_type_by_instance @@ -15,7 +14,6 @@ from mypy.nodes import ( ARG_OPT, ARG_POS, ARG_STAR2, - INVARIANT, MDEF, Argument, AssignmentStmt, @@ -47,24 +45,26 @@ from mypy.options import Options from mypy.plugin import ( CheckerPluginInterface, ClassDefContext, + FunctionContext, MethodContext, Plugin, ReportConfigContext, SemanticAnalyzerPluginInterface, ) +from mypy.plugins import dataclasses from mypy.plugins.common import ( deserialize_and_fixup_type, ) from mypy.semanal import set_callable_name from mypy.server.trigger import make_wildcard_trigger from mypy.state import state -from mypy.type_visitor import TypeTranslator from mypy.typeops import map_type_from_supertype from mypy.types import ( AnyType, CallableType, Instance, NoneType, + Overloaded, Type, TypeOfAny, TypeType, @@ -79,6 +79,12 @@ from mypy.version import __version__ as mypy_version from pydantic._internal import _fields from pydantic.version import parse_mypy_version +try: + from mypy.types import TypeVarDef # type: ignore[attr-defined] +except ImportError: # pragma: no cover + # Backward-compatible with TypeVarDef from Mypy 0.930. + from mypy.types import TypeVarType as TypeVarDef + CONFIGFILE_KEY = 'pydantic-mypy' METADATA_KEY = 'pydantic-mypy-metadata' BASEMODEL_FULLNAME = 'pydantic.main.BaseModel' @@ -96,11 +102,10 @@ DECORATOR_FULLNAMES = { 'pydantic.deprecated.class_validators.validator', 'pydantic.deprecated.class_validators.root_validator', } -IMPLICIT_CLASSMETHOD_DECORATOR_FULLNAMES = DECORATOR_FULLNAMES - {'pydantic.functional_serializers.model_serializer'} MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version) -BUILTINS_NAME = 'builtins' +BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0, 930) else '__builtins__' # Increment version if plugin changes and mypy caches should be invalidated __version__ = 2 @@ -129,12 +134,12 @@ class PydanticPlugin(Plugin): self._plugin_data = self.plugin_config.to_data() super().__init__(options) - def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: + def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], bool] | None: """Update Pydantic model class.""" sym = self.lookup_fully_qualified(fullname) if sym and isinstance(sym.node, TypeInfo): # pragma: no branch # No branching may occur if the mypy cache has not been cleared - if sym.node.has_base(BASEMODEL_FULLNAME): + if any(base.fullname == BASEMODEL_FULLNAME for base in sym.node.mro): return self._pydantic_model_class_maker_callback return None @@ -144,12 +149,28 @@ class PydanticPlugin(Plugin): return self._pydantic_model_metaclass_marker_callback return None + def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None: + """Adjust the return type of the `Field` function.""" + sym = self.lookup_fully_qualified(fullname) + if sym and sym.fullname == FIELD_FULLNAME: + return self._pydantic_field_callback + return None + def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None: """Adjust return type of `from_orm` method call.""" if fullname.endswith('.from_orm'): return from_attributes_callback return None + def get_class_decorator_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: + """Mark pydantic.dataclasses as dataclass. + + Mypy version 1.1.1 added support for `@dataclass_transform` decorator. + """ + if fullname == DATACLASS_FULLNAME and MYPY_VERSION_TUPLE < (1, 1): + return dataclasses.dataclass_class_maker_callback # type: ignore[return-value] + return None + def report_config_data(self, ctx: ReportConfigContext) -> dict[str, Any]: """Return all plugin config data. @@ -157,9 +178,9 @@ class PydanticPlugin(Plugin): """ return self._plugin_data - def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> None: + def _pydantic_model_class_maker_callback(self, ctx: ClassDefContext) -> bool: transformer = PydanticModelTransformer(ctx.cls, ctx.reason, ctx.api, self.plugin_config) - transformer.transform() + return transformer.transform() def _pydantic_model_metaclass_marker_callback(self, ctx: ClassDefContext) -> None: """Reset dataclass_transform_spec attribute of ModelMetaclass. @@ -174,6 +195,54 @@ class PydanticPlugin(Plugin): if getattr(info_metaclass.type, 'dataclass_transform_spec', None): info_metaclass.type.dataclass_transform_spec = None + def _pydantic_field_callback(self, ctx: FunctionContext) -> Type: + """Extract the type of the `default` argument from the Field function, and use it as the return type. + + In particular: + * Check whether the default and default_factory argument is specified. + * Output an error if both are specified. + * Retrieve the type of the argument which is specified, and use it as return type for the function. + """ + default_any_type = ctx.default_return_type + + assert ctx.callee_arg_names[0] == 'default', '"default" is no longer first argument in Field()' + assert ctx.callee_arg_names[1] == 'default_factory', '"default_factory" is no longer second argument in Field()' + default_args = ctx.args[0] + default_factory_args = ctx.args[1] + + if default_args and default_factory_args: + error_default_and_default_factory_specified(ctx.api, ctx.context) + return default_any_type + + if default_args: + default_type = ctx.arg_types[0][0] + default_arg = default_args[0] + + # Fallback to default Any type if the field is required + if not isinstance(default_arg, EllipsisExpr): + return default_type + + elif default_factory_args: + default_factory_type = ctx.arg_types[1][0] + + # Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter + # Pydantic calls the default factory without any argument, so we retrieve the first item + if isinstance(default_factory_type, Overloaded): + default_factory_type = default_factory_type.items[0] + + if isinstance(default_factory_type, CallableType): + ret_type = default_factory_type.ret_type + # mypy doesn't think `ret_type` has `args`, you'd think mypy should know, + # add this check in case it varies by version + args = getattr(ret_type, 'args', None) + if args: + if all(isinstance(arg, TypeVarType) for arg in args): + # Looks like the default factory is a type like `list` or `dict`, replace all args with `Any` + ret_type.args = tuple(default_any_type for _ in args) # type: ignore[attr-defined] + return ret_type + + return default_any_type + class PydanticPluginConfig: """A Pydantic mypy plugin config holder. @@ -238,9 +307,6 @@ def from_attributes_callback(ctx: MethodContext) -> Type: pydantic_metadata = model_type.type.metadata.get(METADATA_KEY) if pydantic_metadata is None: return ctx.default_return_type - if not model_type.type.has_base(BASEMODEL_FULLNAME): - # not a Pydantic v2 model - return ctx.default_return_type from_attributes = pydantic_metadata.get('config', {}).get('from_attributes') if from_attributes is not True: error_from_attributes(model_type.type.name, ctx.api, ctx.context) @@ -254,10 +320,8 @@ class PydanticModelField: self, name: str, alias: str | None, - is_frozen: bool, has_dynamic_alias: bool, has_default: bool, - strict: bool | None, line: int, column: int, type: Type | None, @@ -265,103 +329,41 @@ class PydanticModelField: ): self.name = name self.alias = alias - self.is_frozen = is_frozen self.has_dynamic_alias = has_dynamic_alias self.has_default = has_default - self.strict = strict self.line = line self.column = column self.type = type self.info = info - def to_argument( - self, - current_info: TypeInfo, - typed: bool, - model_strict: bool, - force_optional: bool, - use_alias: bool, - api: SemanticAnalyzerPluginInterface, - force_typevars_invariant: bool, - is_root_model_root: bool, - ) -> Argument: + def to_argument(self, current_info: TypeInfo, typed: bool, force_optional: bool, use_alias: bool) -> Argument: """Based on mypy.plugins.dataclasses.DataclassAttribute.to_argument.""" - variable = self.to_var(current_info, api, use_alias, force_typevars_invariant) - - strict = model_strict if self.strict is None else self.strict - if typed or strict: - type_annotation = self.expand_type(current_info, api, include_root_type=True) - else: - type_annotation = AnyType(TypeOfAny.explicit) - return Argument( - variable=variable, - type_annotation=type_annotation, + variable=self.to_var(current_info, use_alias), + type_annotation=self.expand_type(current_info) if typed else AnyType(TypeOfAny.explicit), initializer=None, - kind=ARG_OPT - if is_root_model_root - else (ARG_NAMED_OPT if force_optional or self.has_default else ARG_NAMED), + kind=ARG_NAMED_OPT if force_optional or self.has_default else ARG_NAMED, ) - def expand_type( - self, - current_info: TypeInfo, - api: SemanticAnalyzerPluginInterface, - force_typevars_invariant: bool = False, - include_root_type: bool = False, - ) -> Type | None: + def expand_type(self, current_info: TypeInfo) -> Type | None: """Based on mypy.plugins.dataclasses.DataclassAttribute.expand_type.""" - if force_typevars_invariant: - # In some cases, mypy will emit an error "Cannot use a covariant type variable as a parameter" - # To prevent that, we add an option to replace typevars with invariant ones while building certain - # method signatures (in particular, `__init__`). There may be a better way to do this, if this causes - # us problems in the future, we should look into why the dataclasses plugin doesn't have this issue. - if isinstance(self.type, TypeVarType): - modified_type = self.type.copy_modified() - modified_type.variance = INVARIANT - self.type = modified_type - - if self.type is not None and self.info.self_type is not None: - # In general, it is not safe to call `expand_type()` during semantic analysis, + # The getattr in the next line is used to prevent errors in legacy versions of mypy without this attribute + if self.type is not None and getattr(self.info, 'self_type', None) is not None: + # In general, it is not safe to call `expand_type()` during semantic analyzis, # however this plugin is called very late, so all types should be fully ready. # Also, it is tricky to avoid eager expansion of Self types here (e.g. because # we serialize attributes). - with state.strict_optional_set(api.options.strict_optional): - filled_with_typevars = fill_typevars(current_info) - # Cannot be TupleType as current_info represents a Pydantic model: - assert isinstance(filled_with_typevars, Instance) - if force_typevars_invariant: - for arg in filled_with_typevars.args: - if isinstance(arg, TypeVarType): - arg.variance = INVARIANT - - expanded_type = expand_type(self.type, {self.info.self_type.id: filled_with_typevars}) - if include_root_type and isinstance(expanded_type, Instance) and is_root_model(expanded_type.type): - # When a root model is used as a field, Pydantic allows both an instance of the root model - # as well as instances of the `root` field type: - root_type = expanded_type.type['root'].type - if root_type is None: - # Happens if the hint for 'root' has unsolved forward references - return expanded_type - expanded_root_type = expand_type_by_instance(root_type, expanded_type) - expanded_type = UnionType([expanded_type, expanded_root_type]) - return expanded_type + return expand_type(self.type, {self.info.self_type.id: fill_typevars(current_info)}) return self.type - def to_var( - self, - current_info: TypeInfo, - api: SemanticAnalyzerPluginInterface, - use_alias: bool, - force_typevars_invariant: bool = False, - ) -> Var: + def to_var(self, current_info: TypeInfo, use_alias: bool) -> Var: """Based on mypy.plugins.dataclasses.DataclassAttribute.to_var.""" if use_alias and self.alias is not None: name = self.alias else: name = self.name - return Var(name, self.expand_type(current_info, api, force_typevars_invariant)) + return Var(name, self.expand_type(current_info)) def serialize(self) -> JsonDict: """Based on mypy.plugins.dataclasses.DataclassAttribute.serialize.""" @@ -369,10 +371,8 @@ class PydanticModelField: return { 'name': self.name, 'alias': self.alias, - 'is_frozen': self.is_frozen, 'has_dynamic_alias': self.has_dynamic_alias, 'has_default': self.has_default, - 'strict': self.strict, 'line': self.line, 'column': self.column, 'type': self.type.serialize(), @@ -385,38 +385,12 @@ class PydanticModelField: typ = deserialize_and_fixup_type(data.pop('type'), api) return cls(type=typ, info=info, **data) - def expand_typevar_from_subtype(self, sub_type: TypeInfo, api: SemanticAnalyzerPluginInterface) -> None: + def expand_typevar_from_subtype(self, sub_type: TypeInfo) -> None: """Expands type vars in the context of a subtype when an attribute is inherited from a generic super type. """ if self.type is not None: - with state.strict_optional_set(api.options.strict_optional): - self.type = map_type_from_supertype(self.type, sub_type, self.info) - - -class PydanticModelClassVar: - """Based on mypy.plugins.dataclasses.DataclassAttribute. - - ClassVars are ignored by subclasses. - - Attributes: - name: the ClassVar name - """ - - def __init__(self, name): - self.name = name - - @classmethod - def deserialize(cls, data: JsonDict) -> PydanticModelClassVar: - """Based on mypy.plugins.dataclasses.DataclassAttribute.deserialize.""" - data = data.copy() - return cls(**data) - - def serialize(self) -> JsonDict: - """Based on mypy.plugins.dataclasses.DataclassAttribute.serialize.""" - return { - 'name': self.name, - } + self.type = map_type_from_supertype(self.type, sub_type, self.info) class PydanticModelTransformer: @@ -431,10 +405,7 @@ class PydanticModelTransformer: 'frozen', 'from_attributes', 'populate_by_name', - 'validate_by_alias', - 'validate_by_name', 'alias_generator', - 'strict', } def __init__( @@ -461,26 +432,25 @@ class PydanticModelTransformer: * stores the fields, config, and if the class is settings in the mypy metadata for access by subclasses """ info = self._cls.info - is_a_root_model = is_root_model(info) + is_root_model = any(ROOT_MODEL_FULLNAME in base.fullname for base in info.mro[:-1]) config = self.collect_config() - fields, class_vars = self.collect_fields_and_class_vars(config, is_a_root_model) - if fields is None or class_vars is None: + fields = self.collect_fields(config, is_root_model) + if fields is None: # Some definitions are not ready. We need another pass. return False for field in fields: if field.type is None: return False - is_settings = info.has_base(BASESETTINGS_FULLNAME) - self.add_initializer(fields, config, is_settings, is_a_root_model) - self.add_model_construct_method(fields, config, is_settings, is_a_root_model) - self.set_frozen(fields, self._api, frozen=config.frozen is True) + is_settings = any(base.fullname == BASESETTINGS_FULLNAME for base in info.mro[:-1]) + self.add_initializer(fields, config, is_settings, is_root_model) + self.add_model_construct_method(fields, config, is_settings) + self.set_frozen(fields, frozen=config.frozen is True) self.adjust_decorator_signatures() info.metadata[METADATA_KEY] = { 'fields': {field.name: field.serialize() for field in fields}, - 'class_vars': {class_var.name: class_var.serialize() for class_var in class_vars}, 'config': config.get_values_dict(), } @@ -494,13 +464,13 @@ class PydanticModelTransformer: Teach mypy this by marking any function whose outermost decorator is a `validator()`, `field_validator()` or `serializer()` call as a `classmethod`. """ - for sym in self._cls.info.names.values(): + for name, sym in self._cls.info.names.items(): if isinstance(sym.node, Decorator): first_dec = sym.node.original_decorators[0] if ( isinstance(first_dec, CallExpr) and isinstance(first_dec.callee, NameExpr) - and first_dec.callee.fullname in IMPLICIT_CLASSMETHOD_DECORATOR_FULLNAMES + and first_dec.callee.fullname in DECORATOR_FULLNAMES # @model_validator(mode="after") is an exception, it expects a regular method and not ( first_dec.callee.fullname == MODEL_VALIDATOR_FULLNAME @@ -543,7 +513,7 @@ class PydanticModelTransformer: for arg_name, arg in zip(stmt.rvalue.arg_names, stmt.rvalue.args): if arg_name is None: continue - config.update(self.get_config_update(arg_name, arg, lax_extra=True)) + config.update(self.get_config_update(arg_name, arg)) elif isinstance(stmt.rvalue, DictExpr): # dict literals for key_expr, value_expr in stmt.rvalue.items: if not isinstance(key_expr, StrExpr): @@ -574,7 +544,7 @@ class PydanticModelTransformer: if ( stmt and config.has_alias_generator - and not (config.validate_by_name or config.populate_by_name) + and not config.populate_by_name and self.plugin_config.warn_required_dynamic_aliases ): error_required_dynamic_aliases(self._api, stmt) @@ -589,13 +559,11 @@ class PydanticModelTransformer: config.setdefault(name, value) return config - def collect_fields_and_class_vars( - self, model_config: ModelConfigData, is_root_model: bool - ) -> tuple[list[PydanticModelField] | None, list[PydanticModelClassVar] | None]: + def collect_fields(self, model_config: ModelConfigData, is_root_model: bool) -> list[PydanticModelField] | None: """Collects the fields for the model, accounting for parent classes.""" cls = self._cls - # First, collect fields and ClassVars belonging to any class in the MRO, ignoring duplicates. + # First, collect fields belonging to any class in the MRO, ignoring duplicates. # # We iterate through the MRO in reverse because attrs defined in the parent must appear # earlier in the attributes list than attrs defined in the child. See: @@ -605,11 +573,10 @@ class PydanticModelTransformer: # in the parent. We can implement this via a dict without disrupting the attr order # because dicts preserve insertion order in Python 3.7+. found_fields: dict[str, PydanticModelField] = {} - found_class_vars: dict[str, PydanticModelClassVar] = {} for info in reversed(cls.info.mro[1:-1]): # 0 is the current class, -2 is BaseModel, -1 is object # if BASEMODEL_METADATA_TAG_KEY in info.metadata and BASEMODEL_METADATA_KEY not in info.metadata: # # We haven't processed the base class yet. Need another pass. - # return None, None + # return None if METADATA_KEY not in info.metadata: continue @@ -622,7 +589,8 @@ class PydanticModelTransformer: # TODO: We shouldn't be performing type operations during the main # semantic analysis pass, since some TypeInfo attributes might # still be in flux. This should be performed in a later phase. - field.expand_typevar_from_subtype(cls.info, self._api) + with state.strict_optional_set(self._api.options.strict_optional): + field.expand_typevar_from_subtype(cls.info) found_fields[name] = field sym_node = cls.info.names.get(name) @@ -631,31 +599,20 @@ class PydanticModelTransformer: 'BaseModel field may only be overridden by another field', sym_node.node, ) - # Collect ClassVars - for name, data in info.metadata[METADATA_KEY]['class_vars'].items(): - found_class_vars[name] = PydanticModelClassVar.deserialize(data) - # Second, collect fields and ClassVars belonging to the current class. + # Second, collect fields belonging to the current class. current_field_names: set[str] = set() - current_class_vars_names: set[str] = set() for stmt in self._get_assignment_statements_from_block(cls.defs): - maybe_field = self.collect_field_or_class_var_from_stmt(stmt, model_config, found_class_vars) - if maybe_field is None: - continue - - lhs = stmt.lvalues[0] - assert isinstance(lhs, NameExpr) # collect_field_or_class_var_from_stmt guarantees this - if isinstance(maybe_field, PydanticModelField): + maybe_field = self.collect_field_from_stmt(stmt, model_config) + if maybe_field is not None: + lhs = stmt.lvalues[0] if is_root_model and lhs.name != 'root': error_extra_fields_on_root_model(self._api, stmt) else: current_field_names.add(lhs.name) found_fields[lhs.name] = maybe_field - elif isinstance(maybe_field, PydanticModelClassVar): - current_class_vars_names.add(lhs.name) - found_class_vars[lhs.name] = maybe_field - return list(found_fields.values()), list(found_class_vars.values()) + return list(found_fields.values()) def _get_assignment_statements_from_if_statement(self, stmt: IfStmt) -> Iterator[AssignmentStmt]: for body in stmt.body: @@ -671,15 +628,14 @@ class PydanticModelTransformer: elif isinstance(stmt, IfStmt): yield from self._get_assignment_statements_from_if_statement(stmt) - def collect_field_or_class_var_from_stmt( # noqa C901 - self, stmt: AssignmentStmt, model_config: ModelConfigData, class_vars: dict[str, PydanticModelClassVar] - ) -> PydanticModelField | PydanticModelClassVar | None: + def collect_field_from_stmt( # noqa C901 + self, stmt: AssignmentStmt, model_config: ModelConfigData + ) -> PydanticModelField | None: """Get pydantic model field from statement. Args: stmt: The statement. model_config: Configuration settings for the model. - class_vars: ClassVars already known to be defined on the model. Returns: A pydantic model field if it could find the field in statement. Otherwise, `None`. @@ -702,10 +658,6 @@ class PydanticModelTransformer: # Eventually, we may want to attempt to respect model_config['ignored_types'] return None - if lhs.name in class_vars: - # Class vars are not fields and are not required to be annotated - return None - # The assignment does not have an annotation, and it's not anything else we recognize error_untyped_fields(self._api, stmt) return None @@ -750,7 +702,7 @@ class PydanticModelTransformer: # x: ClassVar[int] is not a field if node.is_classvar: - return PydanticModelClassVar(lhs.name) + return None # x: InitVar[int] is not supported in BaseModel node_type = get_proper_type(node.type) @@ -761,7 +713,6 @@ class PydanticModelTransformer: ) has_default = self.get_has_default(stmt) - strict = self.get_strict(stmt) if sym.type is None and node.is_final and node.is_inferred: # This follows the logic from the dataclasses plugin. The following comment is taken verbatim: @@ -781,27 +732,16 @@ class PydanticModelTransformer: ) node.type = AnyType(TypeOfAny.from_error) - if node.is_final and has_default: - # TODO this path should be removed (see https://github.com/pydantic/pydantic/issues/11119) - return PydanticModelClassVar(lhs.name) - alias, has_dynamic_alias = self.get_alias_info(stmt) - if ( - has_dynamic_alias - and not (model_config.validate_by_name or model_config.populate_by_name) - and self.plugin_config.warn_required_dynamic_aliases - ): + if has_dynamic_alias and not model_config.populate_by_name and self.plugin_config.warn_required_dynamic_aliases: error_required_dynamic_aliases(self._api, stmt) - is_frozen = self.is_field_frozen(stmt) init_type = self._infer_dataclass_attr_init_type(sym, lhs.name, stmt) return PydanticModelField( name=lhs.name, has_dynamic_alias=has_dynamic_alias, has_default=has_default, - strict=strict, alias=alias, - is_frozen=is_frozen, line=stmt.line, column=stmt.column, type=init_type, @@ -857,42 +797,32 @@ class PydanticModelTransformer: return # Don't generate an __init__ if one already exists typed = self.plugin_config.init_typed - model_strict = bool(config.strict) - use_alias = not (config.validate_by_name or config.populate_by_name) and config.validate_by_alias is not False - requires_dynamic_aliases = bool(config.has_alias_generator and not config.validate_by_name) - args = self.get_field_arguments( - fields, - typed=typed, - model_strict=model_strict, - requires_dynamic_aliases=requires_dynamic_aliases, - use_alias=use_alias, - is_settings=is_settings, - is_root_model=is_root_model, - force_typevars_invariant=True, - ) + use_alias = config.populate_by_name is not True + requires_dynamic_aliases = bool(config.has_alias_generator and not config.populate_by_name) + with state.strict_optional_set(self._api.options.strict_optional): + args = self.get_field_arguments( + fields, + typed=typed, + requires_dynamic_aliases=requires_dynamic_aliases, + use_alias=use_alias, + is_settings=is_settings, + ) + if is_root_model: + # convert root argument to positional argument + args[0].kind = ARG_POS if args[0].kind == ARG_NAMED else ARG_OPT - if is_settings: - base_settings_node = self._api.lookup_fully_qualified(BASESETTINGS_FULLNAME).node - assert isinstance(base_settings_node, TypeInfo) - if '__init__' in base_settings_node.names: - base_settings_init_node = base_settings_node.names['__init__'].node - assert isinstance(base_settings_init_node, FuncDef) - if base_settings_init_node is not None and base_settings_init_node.type is not None: - func_type = base_settings_init_node.type - assert isinstance(func_type, CallableType) - for arg_idx, arg_name in enumerate(func_type.arg_names): - if arg_name is None or arg_name.startswith('__') or not arg_name.startswith('_'): - continue - analyzed_variable_type = self._api.anal_type(func_type.arg_types[arg_idx]) - if analyzed_variable_type is not None and arg_name == '_cli_settings_source': - # _cli_settings_source is defined as CliSettingsSource[Any], and as such - # the Any causes issues with --disallow-any-explicit. As a workaround, change - # the Any type (as if CliSettingsSource was left unparameterized): - analyzed_variable_type = analyzed_variable_type.accept( - ChangeExplicitTypeOfAny(TypeOfAny.from_omitted_generics) - ) - variable = Var(arg_name, analyzed_variable_type) - args.append(Argument(variable, analyzed_variable_type, None, ARG_OPT)) + if is_settings: + base_settings_node = self._api.lookup_fully_qualified(BASESETTINGS_FULLNAME).node + if '__init__' in base_settings_node.names: + base_settings_init_node = base_settings_node.names['__init__'].node + if base_settings_init_node is not None and base_settings_init_node.type is not None: + func_type = base_settings_init_node.type + for arg_idx, arg_name in enumerate(func_type.arg_names): + if arg_name.startswith('__') or not arg_name.startswith('_'): + continue + analyzed_variable_type = self._api.anal_type(func_type.arg_types[arg_idx]) + variable = Var(arg_name, analyzed_variable_type) + args.append(Argument(variable, analyzed_variable_type, None, ARG_OPT)) if not self.should_init_forbid_extra(fields, config): var = Var('kwargs') @@ -901,11 +831,7 @@ class PydanticModelTransformer: add_method(self._api, self._cls, '__init__', args=args, return_type=NoneType()) def add_model_construct_method( - self, - fields: list[PydanticModelField], - config: ModelConfigData, - is_settings: bool, - is_root_model: bool, + self, fields: list[PydanticModelField], config: ModelConfigData, is_settings: bool ) -> None: """Adds a fully typed `model_construct` classmethod to the class. @@ -917,19 +843,13 @@ class PydanticModelTransformer: fields_set_argument = Argument(Var('_fields_set', optional_set_str), optional_set_str, None, ARG_OPT) with state.strict_optional_set(self._api.options.strict_optional): args = self.get_field_arguments( - fields, - typed=True, - model_strict=bool(config.strict), - requires_dynamic_aliases=False, - use_alias=False, - is_settings=is_settings, - is_root_model=is_root_model, + fields, typed=True, requires_dynamic_aliases=False, use_alias=False, is_settings=is_settings ) if not self.should_init_forbid_extra(fields, config): var = Var('kwargs') args.append(Argument(var, AnyType(TypeOfAny.explicit), None, ARG_STAR2)) - args = args + [fields_set_argument] if is_root_model else [fields_set_argument] + args + args = [fields_set_argument] + args add_method( self._api, @@ -940,7 +860,7 @@ class PydanticModelTransformer: is_classmethod=True, ) - def set_frozen(self, fields: list[PydanticModelField], api: SemanticAnalyzerPluginInterface, frozen: bool) -> None: + def set_frozen(self, fields: list[PydanticModelField], frozen: bool) -> None: """Marks all fields as properties so that attempts to set them trigger mypy errors. This is the same approach used by the attrs and dataclasses plugins. @@ -951,21 +871,27 @@ class PydanticModelTransformer: if sym_node is not None: var = sym_node.node if isinstance(var, Var): - var.is_property = frozen or field.is_frozen + var.is_property = frozen elif isinstance(var, PlaceholderNode) and not self._api.final_iteration: # See https://github.com/pydantic/pydantic/issues/5191 to hit this branch for test coverage self._api.defer() - # `var` can also be a FuncDef or Decorator node (e.g. when overriding a field with a function or property). - # In that case, we don't want to do anything. Mypy will already raise an error that a field was not properly - # overridden. + else: # pragma: no cover + # I don't know whether it's possible to hit this branch, but I've added it for safety + try: + var_str = str(var) + except TypeError: + # This happens for PlaceholderNode; perhaps it will happen for other types in the future.. + var_str = repr(var) + detail = f'sym_node.node: {var_str} (of type {var.__class__})' + error_unexpected_behavior(detail, self._api, self._cls) else: - var = field.to_var(info, api, use_alias=False) + var = field.to_var(info, use_alias=False) var.info = info var.is_property = frozen var._fullname = info.fullname + '.' + var.name info.names[var.name] = SymbolTableNode(MDEF, var) - def get_config_update(self, name: str, arg: Expression, lax_extra: bool = False) -> ModelConfigData | None: + def get_config_update(self, name: str, arg: Expression) -> ModelConfigData | None: """Determines the config update due to a single kwarg in the ConfigDict definition. Warns if a tracked config attribute is set to a value the plugin doesn't know how to interpret (e.g., an int) @@ -978,16 +904,7 @@ class PydanticModelTransformer: elif isinstance(arg, MemberExpr): forbid_extra = arg.name == 'forbid' else: - if not lax_extra: - # Only emit an error for other types of `arg` (e.g., `NameExpr`, `ConditionalExpr`, etc.) when - # reading from a config class, etc. If a ConfigDict is used, then we don't want to emit an error - # because you'll get type checking from the ConfigDict itself. - # - # It would be nice if we could introspect the types better otherwise, but I don't know what the API - # is to evaluate an expr into its type and then check if that type is compatible with the expected - # type. Note that you can still get proper type checking via: `model_config = ConfigDict(...)`, just - # if you don't use an explicit string, the plugin won't be able to infer whether extra is forbidden. - error_invalid_config_value(name, self._api, arg) + error_invalid_config_value(name, self._api, arg) return None return ModelConfigData(forbid_extra=forbid_extra) if name == 'alias_generator': @@ -1022,22 +939,6 @@ class PydanticModelTransformer: # Has no default if the "default value" is Ellipsis (i.e., `field_name: Annotation = ...`) return not isinstance(expr, EllipsisExpr) - @staticmethod - def get_strict(stmt: AssignmentStmt) -> bool | None: - """Returns a the `strict` value of a field if defined, otherwise `None`.""" - expr = stmt.rvalue - if isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME: - for arg, name in zip(expr.args, expr.arg_names): - if name != 'strict': - continue - if isinstance(arg, NameExpr): - if arg.fullname == 'builtins.True': - return True - elif arg.fullname == 'builtins.False': - return False - return None - return None - @staticmethod def get_alias_info(stmt: AssignmentStmt) -> tuple[str | None, bool]: """Returns a pair (alias, has_dynamic_alias), extracted from the declaration of the field defined in `stmt`. @@ -1056,53 +957,23 @@ class PydanticModelTransformer: # Assigned value is not a call to pydantic.fields.Field return None, False - if 'validation_alias' in expr.arg_names: - arg = expr.args[expr.arg_names.index('validation_alias')] - elif 'alias' in expr.arg_names: - arg = expr.args[expr.arg_names.index('alias')] - else: - return None, False - - if isinstance(arg, StrExpr): - return arg.value, False - else: - return None, True - - @staticmethod - def is_field_frozen(stmt: AssignmentStmt) -> bool: - """Returns whether the field is frozen, extracted from the declaration of the field defined in `stmt`. - - Note that this is only whether the field was declared to be frozen in a ` = Field(frozen=True)` - sense; this does not determine whether the field is frozen because the entire model is frozen; that is - handled separately. - """ - expr = stmt.rvalue - if isinstance(expr, TempNode): - # TempNode means annotation-only - return False - - if not ( - isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr) and expr.callee.fullname == FIELD_FULLNAME - ): - # Assigned value is not a call to pydantic.fields.Field - return False - for i, arg_name in enumerate(expr.arg_names): - if arg_name == 'frozen': - arg = expr.args[i] - return isinstance(arg, NameExpr) and arg.fullname == 'builtins.True' - return False + if arg_name != 'alias': + continue + arg = expr.args[i] + if isinstance(arg, StrExpr): + return arg.value, False + else: + return None, True + return None, False def get_field_arguments( self, fields: list[PydanticModelField], typed: bool, - model_strict: bool, use_alias: bool, requires_dynamic_aliases: bool, is_settings: bool, - is_root_model: bool, - force_typevars_invariant: bool = False, ) -> list[Argument]: """Helper function used during the construction of the `__init__` and `model_construct` method signatures. @@ -1111,14 +982,7 @@ class PydanticModelTransformer: info = self._cls.info arguments = [ field.to_argument( - info, - typed=typed, - model_strict=model_strict, - force_optional=requires_dynamic_aliases or is_settings, - use_alias=use_alias, - api=self._api, - force_typevars_invariant=force_typevars_invariant, - is_root_model_root=is_root_model and field.name == 'root', + info, typed=typed, force_optional=requires_dynamic_aliases or is_settings, use_alias=use_alias ) for field in fields if not (use_alias and field.has_dynamic_alias) @@ -1131,7 +995,7 @@ class PydanticModelTransformer: We disallow arbitrary kwargs if the extra config setting is "forbid", or if the plugin config says to, *unless* a required dynamic alias is present (since then we can't determine a valid signature). """ - if not (config.validate_by_name or config.populate_by_name): + if not config.populate_by_name: if self.is_dynamic_alias_present(fields, bool(config.has_alias_generator)): return False if config.forbid_extra: @@ -1153,20 +1017,6 @@ class PydanticModelTransformer: return False -class ChangeExplicitTypeOfAny(TypeTranslator): - """A type translator used to change type of Any's, if explicit.""" - - def __init__(self, type_of_any: int) -> None: - self._type_of_any = type_of_any - super().__init__() - - def visit_any(self, t: AnyType) -> Type: # noqa: D102 - if t.type_of_any == TypeOfAny.explicit: - return t.copy_modified(type_of_any=self._type_of_any) - else: - return t - - class ModelConfigData: """Pydantic mypy plugin model config class.""" @@ -1176,19 +1026,13 @@ class ModelConfigData: frozen: bool | None = None, from_attributes: bool | None = None, populate_by_name: bool | None = None, - validate_by_alias: bool | None = None, - validate_by_name: bool | None = None, has_alias_generator: bool | None = None, - strict: bool | None = None, ): self.forbid_extra = forbid_extra self.frozen = frozen self.from_attributes = from_attributes self.populate_by_name = populate_by_name - self.validate_by_alias = validate_by_alias - self.validate_by_name = validate_by_name self.has_alias_generator = has_alias_generator - self.strict = strict def get_values_dict(self) -> dict[str, Any]: """Returns a dict of Pydantic model config names to their values. @@ -1210,11 +1054,6 @@ class ModelConfigData: setattr(self, key, value) -def is_root_model(info: TypeInfo) -> bool: - """Return whether the type info is a root model subclass (or the `RootModel` class itself).""" - return info.has_base(ROOT_MODEL_FULLNAME) - - ERROR_ORM = ErrorCode('pydantic-orm', 'Invalid from_attributes call', 'Pydantic') ERROR_CONFIG = ErrorCode('pydantic-config', 'Invalid config value', 'Pydantic') ERROR_ALIAS = ErrorCode('pydantic-alias', 'Dynamic alias disallowed', 'Pydantic') @@ -1263,6 +1102,11 @@ def error_extra_fields_on_root_model(api: CheckerPluginInterface, context: Conte api.fail('Only `root` is allowed as a field of a `RootModel`', context, code=ERROR_EXTRA_FIELD_ROOT_MODEL) +def error_default_and_default_factory_specified(api: CheckerPluginInterface, context: Context) -> None: + """Emits an error when `Field` has both `default` and `default_factory` together.""" + api.fail('Field default and default_factory cannot be specified together', context, code=ERROR_FIELD_DEFAULTS) + + def add_method( api: SemanticAnalyzerPluginInterface | CheckerPluginInterface, cls: ClassDef, @@ -1270,7 +1114,7 @@ def add_method( args: list[Argument], return_type: Type, self_type: Type | None = None, - tvar_def: TypeVarType | None = None, + tvar_def: TypeVarDef | None = None, is_classmethod: bool = False, ) -> None: """Very closely related to `mypy.plugins.common.add_method_to_class`, with a few pydantic-specific changes.""" @@ -1293,16 +1137,6 @@ def add_method( first = [Argument(Var('_cls'), self_type, None, ARG_POS, True)] else: self_type = self_type or fill_typevars(info) - # `self` is positional *ONLY* here, but this can't be expressed - # fully in the mypy internal API. ARG_POS is the closest we can get. - # Using ARG_POS will, however, give mypy errors if a `self` field - # is present on a model: - # - # Name "self" already defined (possibly by an import) [no-redef] - # - # As a workaround, we give this argument a name that will - # never conflict. By its positional nature, this name will not - # be used or exposed to users. first = [Argument(Var('__pydantic_self__'), self_type, None, ARG_POS)] args = first + args @@ -1313,9 +1147,9 @@ def add_method( arg_names.append(arg.variable.name) arg_kinds.append(arg.kind) - signature = CallableType( - arg_types, arg_kinds, arg_names, return_type, function_type, variables=[tvar_def] if tvar_def else None - ) + signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) + if tvar_def: + signature.variables = [tvar_def] func = FuncDef(name, args, Block([PassStmt()])) func.info = info @@ -1367,7 +1201,7 @@ def parse_toml(config_file: str) -> dict[str, Any] | None: except ImportError: # pragma: no cover import warnings - warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.', stacklevel=2) + warnings.warn('No TOML parser installed, cannot read configuration from `pyproject.toml`.') return None with open(config_file, 'rb') as rf: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/networks.py b/Backend/venv/lib/python3.12/site-packages/pydantic/networks.py index 04a7cac6..e9f25ea3 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/networks.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/networks.py @@ -1,33 +1,18 @@ """The networks module contains types for common network-related fields.""" - from __future__ import annotations as _annotations import dataclasses as _dataclasses import re -from dataclasses import fields -from functools import lru_cache -from importlib.metadata import version from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network -from typing import TYPE_CHECKING, Annotated, Any, ClassVar +from typing import TYPE_CHECKING, Any -from pydantic_core import ( - MultiHostHost, - PydanticCustomError, - PydanticSerializationUnexpectedValue, - SchemaSerializer, - core_schema, -) -from pydantic_core import MultiHostUrl as _CoreMultiHostUrl -from pydantic_core import Url as _CoreUrl -from typing_extensions import Self, TypeAlias +from pydantic_core import MultiHostUrl, PydanticCustomError, Url, core_schema +from typing_extensions import Annotated, TypeAlias -from pydantic.errors import PydanticUserError - -from ._internal import _repr, _schema_generation_shared +from ._internal import _fields, _repr, _schema_generation_shared from ._migration import getattr_migration from .annotated_handlers import GetCoreSchemaHandler from .json_schema import JsonSchemaValue -from .type_adapter import TypeAdapter if TYPE_CHECKING: import email_validator @@ -42,10 +27,7 @@ __all__ = [ 'AnyUrl', 'AnyHttpUrl', 'FileUrl', - 'FtpUrl', 'HttpUrl', - 'WebsocketUrl', - 'AnyWebsocketUrl', 'UrlConstraints', 'EmailStr', 'NameEmail', @@ -58,17 +40,14 @@ __all__ = [ 'RedisDsn', 'MongoDsn', 'KafkaDsn', - 'NatsDsn', 'validate_email', 'MySQLDsn', 'MariaDBDsn', - 'ClickHouseDsn', - 'SnowflakeDsn', ] @_dataclasses.dataclass -class UrlConstraints: +class UrlConstraints(_fields.PydanticMetadata): """Url constraints. Attributes: @@ -78,7 +57,6 @@ class UrlConstraints: default_host: The default host. Defaults to `None`. default_port: The default port. Defaults to `None`. default_path: The default path. Defaults to `None`. - preserve_empty_path: Whether to preserve empty URL paths. Defaults to `None`. """ max_length: int | None = None @@ -87,7 +65,6 @@ class UrlConstraints: default_host: str | None = None default_port: int | None = None default_path: str | None = None - preserve_empty_path: bool | None = None def __hash__(self) -> int: return hash( @@ -98,659 +75,118 @@ class UrlConstraints: self.default_host, self.default_port, self.default_path, - self.preserve_empty_path, ) ) - @property - def defined_constraints(self) -> dict[str, Any]: - """Fetch a key / value mapping of constraints to values that are not None. Used for core schema updates.""" - return {field.name: value for field in fields(self) if (value := getattr(self, field.name)) is not None} - def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - schema = handler(source) - - # for function-wrap schemas, url constraints is applied to the inner schema - # because when we generate schemas for urls, we wrap a core_schema.url_schema() with a function-wrap schema - # that helps with validation on initialization, see _BaseUrl and _BaseMultiHostUrl below. - schema_to_mutate = schema['schema'] if schema['type'] == 'function-wrap' else schema - if annotated_type := schema_to_mutate['type'] not in ('url', 'multi-host-url'): - raise PydanticUserError( - f"'UrlConstraints' cannot annotate '{annotated_type}'.", code='invalid-annotated-type' - ) - for constraint_key, constraint_value in self.defined_constraints.items(): - schema_to_mutate[constraint_key] = constraint_value - return schema - - -class _BaseUrl: - _constraints: ClassVar[UrlConstraints] = UrlConstraints() - _url: _CoreUrl - - def __init__(self, url: str | _CoreUrl | _BaseUrl) -> None: - self._url = _build_type_adapter(self.__class__).validate_python(url)._url - - @property - def scheme(self) -> str: - """The scheme part of the URL. - - e.g. `https` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.scheme - - @property - def username(self) -> str | None: - """The username part of the URL, or `None`. - - e.g. `user` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.username - - @property - def password(self) -> str | None: - """The password part of the URL, or `None`. - - e.g. `pass` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.password - - @property - def host(self) -> str | None: - """The host part of the URL, or `None`. - - If the URL must be punycode encoded, this is the encoded host, e.g if the input URL is `https://£££.com`, - `host` will be `xn--9aaa.com` - """ - return self._url.host - - def unicode_host(self) -> str | None: - """The host part of the URL as a unicode string, or `None`. - - e.g. `host` in `https://user:pass@host:port/path?query#fragment` - - If the URL must be punycode encoded, this is the decoded host, e.g if the input URL is `https://£££.com`, - `unicode_host()` will be `£££.com` - """ - return self._url.unicode_host() - - @property - def port(self) -> int | None: - """The port part of the URL, or `None`. - - e.g. `port` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.port - - @property - def path(self) -> str | None: - """The path part of the URL, or `None`. - - e.g. `/path` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.path - - @property - def query(self) -> str | None: - """The query part of the URL, or `None`. - - e.g. `query` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.query - - def query_params(self) -> list[tuple[str, str]]: - """The query part of the URL as a list of key-value pairs. - - e.g. `[('foo', 'bar')]` in `https://user:pass@host:port/path?foo=bar#fragment` - """ - return self._url.query_params() - - @property - def fragment(self) -> str | None: - """The fragment part of the URL, or `None`. - - e.g. `fragment` in `https://user:pass@host:port/path?query#fragment` - """ - return self._url.fragment - - def unicode_string(self) -> str: - """The URL as a unicode string, unlike `__str__()` this will not punycode encode the host. - - If the URL must be punycode encoded, this is the decoded string, e.g if the input URL is `https://£££.com`, - `unicode_string()` will be `https://£££.com` - """ - return self._url.unicode_string() - - def encoded_string(self) -> str: - """The URL's encoded string representation via __str__(). - - This returns the punycode-encoded host version of the URL as a string. - """ - return str(self) - - def __str__(self) -> str: - """The URL as a string, this will punycode encode the host if required.""" - return str(self._url) - - def __repr__(self) -> str: - return f'{self.__class__.__name__}({str(self._url)!r})' - - def __deepcopy__(self, memo: dict) -> Self: - return self.__class__(self._url) - - def __eq__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url == other._url - - def __lt__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url < other._url - - def __gt__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url > other._url - - def __le__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url <= other._url - - def __ge__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url >= other._url - - def __hash__(self) -> int: - return hash(self._url) - - def __len__(self) -> int: - return len(str(self._url)) - - @classmethod - def build( - cls, - *, - scheme: str, - username: str | None = None, - password: str | None = None, - host: str, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, - ) -> Self: - """Build a new `Url` instance from its component parts. - - Args: - scheme: The scheme part of the URL. - username: The username part of the URL, or omit for no username. - password: The password part of the URL, or omit for no password. - host: The host part of the URL. - port: The port part of the URL, or omit for no port. - path: The path part of the URL, or omit for no path. - query: The query part of the URL, or omit for no query. - fragment: The fragment part of the URL, or omit for no fragment. - - Returns: - An instance of URL - """ - return cls( - _CoreUrl.build( - scheme=scheme, - username=username, - password=password, - host=host, - port=port, - path=path, - query=query, - fragment=fragment, - ) - ) - - @classmethod - def serialize_url(cls, url: Any, info: core_schema.SerializationInfo) -> str | Self: - if not isinstance(url, cls): - raise PydanticSerializationUnexpectedValue( - f"Expected `{cls}` but got `{type(url)}` with value `'{url}'` - serialized value may not be as expected." - ) - if info.mode == 'json': - return str(url) - return url - - @classmethod - def __get_pydantic_core_schema__( - cls, source: type[_BaseUrl], handler: GetCoreSchemaHandler - ) -> core_schema.CoreSchema: - def wrap_val(v, h): - if isinstance(v, source): - return v - if isinstance(v, _BaseUrl): - v = str(v) - core_url = h(v) - instance = source.__new__(source) - instance._url = core_url - return instance - - return core_schema.no_info_wrap_validator_function( - wrap_val, - schema=core_schema.url_schema(**cls._constraints.defined_constraints), - serialization=core_schema.plain_serializer_function_ser_schema( - cls.serialize_url, info_arg=True, when_used='always' - ), - ) - - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler - ) -> JsonSchemaValue: - # we use the url schema for json schema generation, but we might have to extract it from - # the function-wrap schema we use as a tool for validation on initialization - inner_schema = core_schema['schema'] if core_schema['type'] == 'function-wrap' else core_schema - return handler(inner_schema) - - __pydantic_serializer__ = SchemaSerializer(core_schema.any_schema(serialization=core_schema.to_string_ser_schema())) - - -class _BaseMultiHostUrl: - _constraints: ClassVar[UrlConstraints] = UrlConstraints() - _url: _CoreMultiHostUrl - - def __init__(self, url: str | _CoreMultiHostUrl | _BaseMultiHostUrl) -> None: - self._url = _build_type_adapter(self.__class__).validate_python(url)._url - - @property - def scheme(self) -> str: - """The scheme part of the URL. - - e.g. `https` in `https://foo.com,bar.com/path?query#fragment` - """ - return self._url.scheme - - @property - def path(self) -> str | None: - """The path part of the URL, or `None`. - - e.g. `/path` in `https://foo.com,bar.com/path?query#fragment` - """ - return self._url.path - - @property - def query(self) -> str | None: - """The query part of the URL, or `None`. - - e.g. `query` in `https://foo.com,bar.com/path?query#fragment` - """ - return self._url.query - - def query_params(self) -> list[tuple[str, str]]: - """The query part of the URL as a list of key-value pairs. - - e.g. `[('foo', 'bar')]` in `https://foo.com,bar.com/path?foo=bar#fragment` - """ - return self._url.query_params() - - @property - def fragment(self) -> str | None: - """The fragment part of the URL, or `None`. - - e.g. `fragment` in `https://foo.com,bar.com/path?query#fragment` - """ - return self._url.fragment - - def hosts(self) -> list[MultiHostHost]: - '''The hosts of the `MultiHostUrl` as [`MultiHostHost`][pydantic_core.MultiHostHost] typed dicts. - - ```python - from pydantic_core import MultiHostUrl - - mhu = MultiHostUrl('https://foo.com:123,foo:bar@bar.com/path') - print(mhu.hosts()) - """ - [ - {'username': None, 'password': None, 'host': 'foo.com', 'port': 123}, - {'username': 'foo', 'password': 'bar', 'host': 'bar.com', 'port': 443} - ] - ``` - Returns: - A list of dicts, each representing a host. - ''' - return self._url.hosts() - - def encoded_string(self) -> str: - """The URL's encoded string representation via __str__(). - - This returns the punycode-encoded host version of the URL as a string. - """ - return str(self) - - def unicode_string(self) -> str: - """The URL as a unicode string, unlike `__str__()` this will not punycode encode the hosts.""" - return self._url.unicode_string() - - def __str__(self) -> str: - """The URL as a string, this will punycode encode the host if required.""" - return str(self._url) - - def __repr__(self) -> str: - return f'{self.__class__.__name__}({str(self._url)!r})' - - def __deepcopy__(self, memo: dict) -> Self: - return self.__class__(self._url) - - def __eq__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self._url == other._url - - def __hash__(self) -> int: - return hash(self._url) - - def __len__(self) -> int: - return len(str(self._url)) - - @classmethod - def build( - cls, - *, - scheme: str, - hosts: list[MultiHostHost] | None = None, - username: str | None = None, - password: str | None = None, - host: str | None = None, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, - ) -> Self: - """Build a new `MultiHostUrl` instance from its component parts. - - This method takes either `hosts` - a list of `MultiHostHost` typed dicts, or the individual components - `username`, `password`, `host` and `port`. - - Args: - scheme: The scheme part of the URL. - hosts: Multiple hosts to build the URL from. - username: The username part of the URL. - password: The password part of the URL. - host: The host part of the URL. - port: The port part of the URL. - path: The path part of the URL. - query: The query part of the URL, or omit for no query. - fragment: The fragment part of the URL, or omit for no fragment. - - Returns: - An instance of `MultiHostUrl` - """ - return cls( - _CoreMultiHostUrl.build( - scheme=scheme, - hosts=hosts, - username=username, - password=password, - host=host, - port=port, - path=path, - query=query, - fragment=fragment, - ) - ) - - @classmethod - def serialize_url(cls, url: Any, info: core_schema.SerializationInfo) -> str | Self: - if not isinstance(url, cls): - raise PydanticSerializationUnexpectedValue( - f"Expected `{cls}` but got `{type(url)}` with value `'{url}'` - serialized value may not be as expected." - ) - if info.mode == 'json': - return str(url) - return url - - @classmethod - def __get_pydantic_core_schema__( - cls, source: type[_BaseMultiHostUrl], handler: GetCoreSchemaHandler - ) -> core_schema.CoreSchema: - def wrap_val(v, h): - if isinstance(v, source): - return v - if isinstance(v, _BaseMultiHostUrl): - v = str(v) - core_url = h(v) - instance = source.__new__(source) - instance._url = core_url - return instance - - return core_schema.no_info_wrap_validator_function( - wrap_val, - schema=core_schema.multi_host_url_schema(**cls._constraints.defined_constraints), - serialization=core_schema.plain_serializer_function_ser_schema( - cls.serialize_url, info_arg=True, when_used='always' - ), - ) - - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler - ) -> JsonSchemaValue: - # we use the url schema for json schema generation, but we might have to extract it from - # the function-wrap schema we use as a tool for validation on initialization - inner_schema = core_schema['schema'] if core_schema['type'] == 'function-wrap' else core_schema - return handler(inner_schema) - - __pydantic_serializer__ = SchemaSerializer(core_schema.any_schema(serialization=core_schema.to_string_ser_schema())) - - -@lru_cache -def _build_type_adapter(cls: type[_BaseUrl | _BaseMultiHostUrl]) -> TypeAdapter: - return TypeAdapter(cls) - - -class AnyUrl(_BaseUrl): - """Base type for all URLs. - - * Any scheme allowed - * Top-level domain (TLD) not required - * Host not required - - Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`, - the types export the following properties: - - - `scheme`: the URL scheme (`http`), always set. - - `host`: the URL host (`example.com`). - - `username`: optional username if included (`samuel`). - - `password`: optional password if included (`pass`). - - `port`: optional port (`8000`). - - `path`: optional path (`/the/path/`). - - `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`). - - `fragment`: optional fragment (`fragment=is;this=bit`). - """ - - -# Note: all single host urls inherit from `AnyUrl` to preserve compatibility with pre-v2.10 code -# Where urls were annotated variants of `AnyUrl`, which was an alias to `pydantic_core.Url` - - -class AnyHttpUrl(AnyUrl): - """A type that will accept any http or https URL. - - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints(allowed_schemes=['http', 'https']) - - -class HttpUrl(AnyUrl): - """A type that will accept any http or https URL. - - * TLD not required - * Host not required - * Max length 2083 - - ```python - from pydantic import BaseModel, HttpUrl, ValidationError - - class MyModel(BaseModel): - url: HttpUrl - - m = MyModel(url='http://www.example.com') # (1)! - print(m.url) - #> http://www.example.com/ - - try: - MyModel(url='ftp://invalid.url') - except ValidationError as e: - print(e) - ''' - 1 validation error for MyModel - url - URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str] - ''' - - try: - MyModel(url='not a url') - except ValidationError as e: - print(e) - ''' - 1 validation error for MyModel - url - Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str] - ''' - ``` - - 1. Note: mypy would prefer `m = MyModel(url=HttpUrl('http://www.example.com'))`, but Pydantic will convert the string to an HttpUrl instance anyway. - - "International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via - [punycode](https://en.wikipedia.org/wiki/Punycode) (see - [this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important): - - ```python - from pydantic import BaseModel, HttpUrl - - class MyModel(BaseModel): - url: HttpUrl - - m1 = MyModel(url='http://puny£code.com') - print(m1.url) - #> http://xn--punycode-eja.com/ - m2 = MyModel(url='https://www.аррӏе.com/') - print(m2.url) - #> https://www.xn--80ak6aa92e.com/ - m3 = MyModel(url='https://www.example.珠宝/') - print(m3.url) - #> https://www.example.xn--pbt977c/ - ``` - - - !!! warning "Underscores in Hostnames" - In Pydantic, underscores are allowed in all parts of a domain except the TLD. - Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can. - - To explain this; consider the following two cases: - - - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore. - - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain. - - Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore - underscores are allowed, but you can always do further validation in a validator if desired. - - Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good - (or at least big) company. - """ - - _constraints = UrlConstraints(max_length=2083, allowed_schemes=['http', 'https']) - - -class AnyWebsocketUrl(AnyUrl): - """A type that will accept any ws or wss URL. - - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints(allowed_schemes=['ws', 'wss']) - - -class WebsocketUrl(AnyUrl): - """A type that will accept any ws or wss URL. - - * TLD not required - * Host not required - * Max length 2083 - """ - - _constraints = UrlConstraints(max_length=2083, allowed_schemes=['ws', 'wss']) - - -class FileUrl(AnyUrl): - """A type that will accept any file URL. - - * Host not required - """ - - _constraints = UrlConstraints(allowed_schemes=['file']) - - -class FtpUrl(AnyUrl): - """A type that will accept ftp URL. - - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints(allowed_schemes=['ftp']) - - -class PostgresDsn(_BaseMultiHostUrl): - """A type that will accept any Postgres DSN. - - * User info required - * TLD not required - * Host required - * Supports multiple hosts - - If further validation is required, these properties can be used by validators to enforce specific behaviour: - - ```python - from pydantic import ( - BaseModel, - HttpUrl, - PostgresDsn, - ValidationError, - field_validator, - ) - - class MyModel(BaseModel): - url: HttpUrl - - m = MyModel(url='http://www.example.com') - - # the repr() method for a url will display all properties of the url - print(repr(m.url)) - #> HttpUrl('http://www.example.com/') - print(m.url.scheme) - #> http - print(m.url.host) - #> www.example.com - print(m.url.port) - #> 80 - - class MyDatabaseModel(BaseModel): - db: PostgresDsn - - @field_validator('db') - def check_db_name(cls, v): - assert v.path and len(v.path) > 1, 'database must be provided' - return v - - m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar') - print(m.db) - #> postgres://user:pass@localhost:5432/foobar - - try: - MyDatabaseModel(db='postgres://user:pass@localhost:5432') - except ValidationError as e: - print(e) - ''' - 1 validation error for MyDatabaseModel - db - Assertion failed, database must be provided - assert (None) - + where None = PostgresDsn('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str] - ''' - ``` - """ - - _constraints = UrlConstraints( +AnyUrl = Url +"""Base type for all URLs. + +* Any scheme allowed +* Top-level domain (TLD) not required +* Host required + +Assuming an input URL of `http://samuel:pass@example.com:8000/the/path/?query=here#fragment=is;this=bit`, +the types export the following properties: + +- `scheme`: the URL scheme (`http`), always set. +- `host`: the URL host (`example.com`), always set. +- `username`: optional username if included (`samuel`). +- `password`: optional password if included (`pass`). +- `port`: optional port (`8000`). +- `path`: optional path (`/the/path/`). +- `query`: optional URL query (for example, `GET` arguments or "search string", such as `query=here`). +- `fragment`: optional fragment (`fragment=is;this=bit`). +""" +AnyHttpUrl = Annotated[Url, UrlConstraints(allowed_schemes=['http', 'https'])] +"""A type that will accept any http or https URL. + +* TLD not required +* Host required +""" +HttpUrl = Annotated[Url, UrlConstraints(max_length=2083, allowed_schemes=['http', 'https'])] +"""A type that will accept any http or https URL. + +* TLD required +* Host required +* Max length 2083 + +```py +from pydantic import BaseModel, HttpUrl, ValidationError + +class MyModel(BaseModel): + url: HttpUrl + +m = MyModel(url='http://www.example.com') +print(m.url) +#> http://www.example.com/ + +try: + MyModel(url='ftp://invalid.url') +except ValidationError as e: + print(e) + ''' + 1 validation error for MyModel + url + URL scheme should be 'http' or 'https' [type=url_scheme, input_value='ftp://invalid.url', input_type=str] + ''' + +try: + MyModel(url='not a url') +except ValidationError as e: + print(e) + ''' + 1 validation error for MyModel + url + Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='not a url', input_type=str] + ''' +``` + +"International domains" (e.g. a URL where the host or TLD includes non-ascii characters) will be encoded via +[punycode](https://en.wikipedia.org/wiki/Punycode) (see +[this article](https://www.xudongz.com/blog/2017/idn-phishing/) for a good description of why this is important): + +```py +from pydantic import BaseModel, HttpUrl + +class MyModel(BaseModel): + url: HttpUrl + +m1 = MyModel(url='http://puny£code.com') +print(m1.url) +#> http://xn--punycode-eja.com/ +m2 = MyModel(url='https://www.аррӏе.com/') +print(m2.url) +#> https://www.xn--80ak6aa92e.com/ +m3 = MyModel(url='https://www.example.珠宝/') +print(m3.url) +#> https://www.example.xn--pbt977c/ +``` + + +!!! warning "Underscores in Hostnames" + In Pydantic, underscores are allowed in all parts of a domain except the TLD. + Technically this might be wrong - in theory the hostname cannot have underscores, but subdomains can. + + To explain this; consider the following two cases: + + - `exam_ple.co.uk`: the hostname is `exam_ple`, which should not be allowed since it contains an underscore. + - `foo_bar.example.com` the hostname is `example`, which should be allowed since the underscore is in the subdomain. + + Without having an exhaustive list of TLDs, it would be impossible to differentiate between these two. Therefore + underscores are allowed, but you can always do further validation in a validator if desired. + + Also, Chrome, Firefox, and Safari all currently accept `http://exam_ple.com` as a URL, so we're in good + (or at least big) company. +""" +FileUrl = Annotated[Url, UrlConstraints(allowed_schemes=['file'])] +"""A type that will accept any file URL. + +* Host not required +""" +PostgresDsn = Annotated[ + MultiHostUrl, + UrlConstraints( host_required=True, allowed_schemes=[ 'postgres', @@ -763,132 +199,119 @@ class PostgresDsn(_BaseMultiHostUrl): 'postgresql+py-postgresql', 'postgresql+pygresql', ], - ) + ), +] +"""A type that will accept any Postgres DSN. - @property - def host(self) -> str: - """The required URL host.""" - return self._url.host # pyright: ignore[reportAttributeAccessIssue] +* User info required +* TLD not required +* Host required +* Supports multiple hosts +If further validation is required, these properties can be used by validators to enforce specific behaviour: -class CockroachDsn(AnyUrl): - """A type that will accept any Cockroach DSN. +```py +from pydantic import ( + BaseModel, + HttpUrl, + PostgresDsn, + ValidationError, + field_validator, +) - * User info required - * TLD not required - * Host required - """ +class MyModel(BaseModel): + url: HttpUrl - _constraints = UrlConstraints( +m = MyModel(url='http://www.example.com') + +# the repr() method for a url will display all properties of the url +print(repr(m.url)) +#> Url('http://www.example.com/') +print(m.url.scheme) +#> http +print(m.url.host) +#> www.example.com +print(m.url.port) +#> 80 + +class MyDatabaseModel(BaseModel): + db: PostgresDsn + + @field_validator('db') + def check_db_name(cls, v): + assert v.path and len(v.path) > 1, 'database must be provided' + return v + +m = MyDatabaseModel(db='postgres://user:pass@localhost:5432/foobar') +print(m.db) +#> postgres://user:pass@localhost:5432/foobar + +try: + MyDatabaseModel(db='postgres://user:pass@localhost:5432') +except ValidationError as e: + print(e) + ''' + 1 validation error for MyDatabaseModel + db + Assertion failed, database must be provided + assert (None) + + where None = MultiHostUrl('postgres://user:pass@localhost:5432').path [type=assertion_error, input_value='postgres://user:pass@localhost:5432', input_type=str] + ''' +``` +""" + +CockroachDsn = Annotated[ + Url, + UrlConstraints( host_required=True, allowed_schemes=[ 'cockroachdb', 'cockroachdb+psycopg2', 'cockroachdb+asyncpg', ], - ) + ), +] +"""A type that will accept any Cockroach DSN. - @property - def host(self) -> str: - """The required URL host.""" - return self._url.host # pyright: ignore[reportReturnType] +* User info required +* TLD not required +* Host required +""" +AmqpDsn = Annotated[Url, UrlConstraints(allowed_schemes=['amqp', 'amqps'])] +"""A type that will accept any AMQP DSN. +* User info required +* TLD not required +* Host required +""" +RedisDsn = Annotated[ + Url, + UrlConstraints(allowed_schemes=['redis', 'rediss'], default_host='localhost', default_port=6379, default_path='/0'), +] +"""A type that will accept any Redis DSN. -class AmqpDsn(AnyUrl): - """A type that will accept any AMQP DSN. +* User info required +* TLD not required +* Host required (e.g., `rediss://:pass@localhost`) +""" +MongoDsn = Annotated[MultiHostUrl, UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017)] +"""A type that will accept any MongoDB DSN. - * User info required - * TLD not required - * Host not required - """ +* User info not required +* Database name not required +* Port not required +* User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`). +""" +KafkaDsn = Annotated[Url, UrlConstraints(allowed_schemes=['kafka'], default_host='localhost', default_port=9092)] +"""A type that will accept any Kafka DSN. - _constraints = UrlConstraints(allowed_schemes=['amqp', 'amqps']) - - -class RedisDsn(AnyUrl): - """A type that will accept any Redis DSN. - - * User info required - * TLD not required - * Host required (e.g., `rediss://:pass@localhost`) - """ - - _constraints = UrlConstraints( - allowed_schemes=['redis', 'rediss'], - default_host='localhost', - default_port=6379, - default_path='/0', - host_required=True, - ) - - @property - def host(self) -> str: - """The required URL host.""" - return self._url.host # pyright: ignore[reportReturnType] - - -class MongoDsn(_BaseMultiHostUrl): - """A type that will accept any MongoDB DSN. - - * User info not required - * Database name not required - * Port not required - * User info may be passed without user part (e.g., `mongodb://mongodb0.example.com:27017`). - - !!! warning - If a port isn't specified, the default MongoDB port `27017` will be used. If this behavior is - undesirable, you can use the following: - - ```python - from typing import Annotated - - from pydantic import UrlConstraints - from pydantic_core import MultiHostUrl - - MongoDsnNoDefaultPort = Annotated[ - MultiHostUrl, - UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv']), - ] - ``` - """ - - _constraints = UrlConstraints(allowed_schemes=['mongodb', 'mongodb+srv'], default_port=27017) - - -class KafkaDsn(AnyUrl): - """A type that will accept any Kafka DSN. - - * User info required - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints(allowed_schemes=['kafka'], default_host='localhost', default_port=9092) - - -class NatsDsn(_BaseMultiHostUrl): - """A type that will accept any NATS DSN. - - NATS is a connective technology built for the ever increasingly hyper-connected world. - It is a single technology that enables applications to securely communicate across - any combination of cloud vendors, on-premise, edge, web and mobile, and devices. - More: https://nats.io - """ - - _constraints = UrlConstraints( - allowed_schemes=['nats', 'tls', 'ws', 'wss'], default_host='localhost', default_port=4222 - ) - - -class MySQLDsn(AnyUrl): - """A type that will accept any MySQL DSN. - - * User info required - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints( +* User info required +* TLD not required +* Host required +""" +MySQLDsn = Annotated[ + Url, + UrlConstraints( allowed_schemes=[ 'mysql', 'mysql+mysqlconnector', @@ -900,63 +323,27 @@ class MySQLDsn(AnyUrl): 'mysql+pyodbc', ], default_port=3306, - host_required=True, - ) + ), +] +"""A type that will accept any MySQL DSN. - -class MariaDBDsn(AnyUrl): - """A type that will accept any MariaDB DSN. - - * User info required - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints( +* User info required +* TLD not required +* Host required +""" +MariaDBDsn = Annotated[ + Url, + UrlConstraints( allowed_schemes=['mariadb', 'mariadb+mariadbconnector', 'mariadb+pymysql'], default_port=3306, - ) + ), +] +"""A type that will accept any MariaDB DSN. - -class ClickHouseDsn(AnyUrl): - """A type that will accept any ClickHouse DSN. - - * User info required - * TLD not required - * Host not required - """ - - _constraints = UrlConstraints( - allowed_schemes=[ - 'clickhouse+native', - 'clickhouse+asynch', - 'clickhouse+http', - 'clickhouse', - 'clickhouses', - 'clickhousedb', - ], - default_host='localhost', - default_port=9000, - ) - - -class SnowflakeDsn(AnyUrl): - """A type that will accept any Snowflake DSN. - - * User info required - * TLD not required - * Host required - """ - - _constraints = UrlConstraints( - allowed_schemes=['snowflake'], - host_required=True, - ) - - @property - def host(self) -> str: - """The required URL host.""" - return self._url.host # pyright: ignore[reportReturnType] +* User info required +* TLD not required +* Host required +""" def import_email_validator() -> None: @@ -964,9 +351,7 @@ def import_email_validator() -> None: try: import email_validator except ImportError as e: - raise ImportError("email-validator is not installed, run `pip install 'pydantic[email]'`") from e - if not version('email-validator').partition('.')[0] == '2': - raise ImportError('email-validator version >= 2.0 required, run pip install -U email-validator') + raise ImportError('email-validator is not installed, run `pip install pydantic[email]`') from e if TYPE_CHECKING: @@ -985,7 +370,7 @@ else: Validate email addresses. - ```python + ```py from pydantic import BaseModel, EmailStr class Model(BaseModel): @@ -1014,8 +399,8 @@ else: return field_schema @classmethod - def _validate(cls, input_value: str, /) -> str: - return validate_email(input_value)[1] + def _validate(cls, __input_value: str) -> str: + return validate_email(__input_value)[1] class NameEmail(_repr.Representation): @@ -1034,7 +419,7 @@ class NameEmail(_repr.Representation): The `NameEmail` has two properties: `name` and `email`. In case the `name` is not provided, it's inferred from the email address. - ```python + ```py from pydantic import BaseModel, NameEmail class User(BaseModel): @@ -1078,197 +463,182 @@ class NameEmail(_repr.Representation): _handler: GetCoreSchemaHandler, ) -> core_schema.CoreSchema: import_email_validator() - return core_schema.no_info_after_validator_function( cls._validate, - core_schema.json_or_python_schema( - json_schema=core_schema.str_schema(), - python_schema=core_schema.union_schema( - [core_schema.is_instance_schema(cls), core_schema.str_schema()], - custom_error_type='name_email_type', - custom_error_message='Input is not a valid NameEmail', - ), - serialization=core_schema.to_string_ser_schema(), + core_schema.union_schema( + [core_schema.is_instance_schema(cls), core_schema.str_schema()], + custom_error_type='name_email_type', + custom_error_message='Input is not a valid NameEmail', ), + serialization=core_schema.to_string_ser_schema(), ) @classmethod - def _validate(cls, input_value: Self | str, /) -> Self: - if isinstance(input_value, str): - name, email = validate_email(input_value) - return cls(name, email) + def _validate(cls, __input_value: NameEmail | str) -> NameEmail: + if isinstance(__input_value, cls): + return __input_value else: - return input_value + name, email = validate_email(__input_value) # type: ignore[arg-type] + return cls(name, email) def __str__(self) -> str: - if '@' in self.name: - return f'"{self.name}" <{self.email}>' - return f'{self.name} <{self.email}>' -IPvAnyAddressType: TypeAlias = 'IPv4Address | IPv6Address' -IPvAnyInterfaceType: TypeAlias = 'IPv4Interface | IPv6Interface' -IPvAnyNetworkType: TypeAlias = 'IPv4Network | IPv6Network' +class IPvAnyAddress: + """Validate an IPv4 or IPv6 address. -if TYPE_CHECKING: - IPvAnyAddress = IPvAnyAddressType - IPvAnyInterface = IPvAnyInterfaceType - IPvAnyNetwork = IPvAnyNetworkType -else: + ```py + from pydantic import BaseModel + from pydantic.networks import IPvAnyAddress - class IPvAnyAddress: - """Validate an IPv4 or IPv6 address. + class IpModel(BaseModel): + ip: IPvAnyAddress - ```python - from pydantic import BaseModel - from pydantic.networks import IPvAnyAddress + print(IpModel(ip='127.0.0.1')) + #> ip=IPv4Address('127.0.0.1') - class IpModel(BaseModel): - ip: IPvAnyAddress + try: + IpModel(ip='http://www.example.com') + except ValueError as e: + print(e.errors()) + ''' + [ + { + 'type': 'ip_any_address', + 'loc': ('ip',), + 'msg': 'value is not a valid IPv4 or IPv6 address', + 'input': 'http://www.example.com', + } + ] + ''' + ``` + """ - print(IpModel(ip='127.0.0.1')) - #> ip=IPv4Address('127.0.0.1') + __slots__ = () + + def __new__(cls, value: Any) -> IPv4Address | IPv6Address: + """Validate an IPv4 or IPv6 address.""" + try: + return IPv4Address(value) + except ValueError: + pass try: - IpModel(ip='http://www.example.com') - except ValueError as e: - print(e.errors()) - ''' - [ - { - 'type': 'ip_any_address', - 'loc': ('ip',), - 'msg': 'value is not a valid IPv4 or IPv6 address', - 'input': 'http://www.example.com', - } - ] - ''' - ``` - """ + return IPv6Address(value) + except ValueError: + raise PydanticCustomError('ip_any_address', 'value is not a valid IPv4 or IPv6 address') - __slots__ = () + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanyaddress') + return field_schema - def __new__(cls, value: Any) -> IPvAnyAddressType: - """Validate an IPv4 or IPv6 address.""" - try: - return IPv4Address(value) - except ValueError: - pass + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) - try: - return IPv6Address(value) - except ValueError: - raise PydanticCustomError('ip_any_address', 'value is not a valid IPv4 or IPv6 address') + @classmethod + def _validate(cls, __input_value: Any) -> IPv4Address | IPv6Address: + return cls(__input_value) # type: ignore[return-value] - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler - ) -> JsonSchemaValue: - field_schema = {} - field_schema.update(type='string', format='ipvanyaddress') - return field_schema - @classmethod - def __get_pydantic_core_schema__( - cls, - _source: type[Any], - _handler: GetCoreSchemaHandler, - ) -> core_schema.CoreSchema: - return core_schema.no_info_plain_validator_function( - cls._validate, serialization=core_schema.to_string_ser_schema() - ) +class IPvAnyInterface: + """Validate an IPv4 or IPv6 interface.""" - @classmethod - def _validate(cls, input_value: Any, /) -> IPvAnyAddressType: - return cls(input_value) # type: ignore[return-value] + __slots__ = () - class IPvAnyInterface: + def __new__(cls, value: NetworkType) -> IPv4Interface | IPv6Interface: """Validate an IPv4 or IPv6 interface.""" + try: + return IPv4Interface(value) + except ValueError: + pass - __slots__ = () + try: + return IPv6Interface(value) + except ValueError: + raise PydanticCustomError('ip_any_interface', 'value is not a valid IPv4 or IPv6 interface') - def __new__(cls, value: NetworkType) -> IPvAnyInterfaceType: - """Validate an IPv4 or IPv6 interface.""" - try: - return IPv4Interface(value) - except ValueError: - pass + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanyinterface') + return field_schema - try: - return IPv6Interface(value) - except ValueError: - raise PydanticCustomError('ip_any_interface', 'value is not a valid IPv4 or IPv6 interface') + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler - ) -> JsonSchemaValue: - field_schema = {} - field_schema.update(type='string', format='ipvanyinterface') - return field_schema + @classmethod + def _validate(cls, __input_value: NetworkType) -> IPv4Interface | IPv6Interface: + return cls(__input_value) # type: ignore[return-value] - @classmethod - def __get_pydantic_core_schema__( - cls, - _source: type[Any], - _handler: GetCoreSchemaHandler, - ) -> core_schema.CoreSchema: - return core_schema.no_info_plain_validator_function( - cls._validate, serialization=core_schema.to_string_ser_schema() - ) - @classmethod - def _validate(cls, input_value: NetworkType, /) -> IPvAnyInterfaceType: - return cls(input_value) # type: ignore[return-value] +class IPvAnyNetwork: + """Validate an IPv4 or IPv6 network.""" - class IPvAnyNetwork: + __slots__ = () + + def __new__(cls, value: NetworkType) -> IPv4Network | IPv6Network: """Validate an IPv4 or IPv6 network.""" + # Assume IP Network is defined with a default value for `strict` argument. + # Define your own class if you want to specify network address check strictness. + try: + return IPv4Network(value) + except ValueError: + pass - __slots__ = () + try: + return IPv6Network(value) + except ValueError: + raise PydanticCustomError('ip_any_network', 'value is not a valid IPv4 or IPv6 network') - def __new__(cls, value: NetworkType) -> IPvAnyNetworkType: - """Validate an IPv4 or IPv6 network.""" - # Assume IP Network is defined with a default value for `strict` argument. - # Define your own class if you want to specify network address check strictness. - try: - return IPv4Network(value) - except ValueError: - pass + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema = {} + field_schema.update(type='string', format='ipvanynetwork') + return field_schema - try: - return IPv6Network(value) - except ValueError: - raise PydanticCustomError('ip_any_network', 'value is not a valid IPv4 or IPv6 network') + @classmethod + def __get_pydantic_core_schema__( + cls, + _source: type[Any], + _handler: GetCoreSchemaHandler, + ) -> core_schema.CoreSchema: + return core_schema.no_info_plain_validator_function( + cls._validate, serialization=core_schema.to_string_ser_schema() + ) - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: core_schema.CoreSchema, handler: _schema_generation_shared.GetJsonSchemaHandler - ) -> JsonSchemaValue: - field_schema = {} - field_schema.update(type='string', format='ipvanynetwork') - return field_schema - - @classmethod - def __get_pydantic_core_schema__( - cls, - _source: type[Any], - _handler: GetCoreSchemaHandler, - ) -> core_schema.CoreSchema: - return core_schema.no_info_plain_validator_function( - cls._validate, serialization=core_schema.to_string_ser_schema() - ) - - @classmethod - def _validate(cls, input_value: NetworkType, /) -> IPvAnyNetworkType: - return cls(input_value) # type: ignore[return-value] + @classmethod + def _validate(cls, __input_value: NetworkType) -> IPv4Network | IPv6Network: + return cls(__input_value) # type: ignore[return-value] def _build_pretty_email_regex() -> re.Pattern[str]: name_chars = r'[\w!#$%&\'*+\-/=?^_`{|}~]' unquoted_name_group = rf'((?:{name_chars}+\s+)*{name_chars}+)' quoted_name_group = r'"((?:[^"]|\")+)"' - email_group = r'<(.+)>' + email_group = r'<\s*(.+)\s*>' return re.compile(rf'\s*(?:{unquoted_name_group}|{quoted_name_group})?\s*{email_group}\s*') @@ -1283,13 +653,6 @@ A somewhat arbitrary but very generous number compared to what is allowed by mos def validate_email(value: str) -> tuple[str, str]: """Email address validation using [email-validator](https://pypi.org/project/email-validator/). - Returns: - A tuple containing the local part of the email (or the name for "pretty" email addresses) - and the normalized email. - - Raises: - PydanticCustomError: If the email is invalid. - Note: Note that: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/parse.py b/Backend/venv/lib/python3.12/site-packages/pydantic/parse.py index 68b7f046..ceee6342 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/parse.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/parse.py @@ -1,5 +1,4 @@ """The `parse` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__init__.py b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__init__.py index 840d20a0..82e729c5 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__init__.py @@ -1,17 +1,13 @@ -"""!!! abstract "Usage Documentation" - [Build a Plugin](../concepts/plugins.md#build-a-plugin) +"""Usage docs: https://docs.pydantic.dev/2.5/concepts/plugins#build-a-plugin Plugin interface for Pydantic plugins, and related types. """ - from __future__ import annotations -from typing import Any, Callable, Literal, NamedTuple +from typing import Any, Callable, NamedTuple from pydantic_core import CoreConfig, CoreSchema, ValidationError -from typing_extensions import Protocol, TypeAlias - -from pydantic.config import ExtraValues +from typing_extensions import Literal, Protocol, TypeAlias __all__ = ( 'PydanticPluginProtocol', @@ -115,26 +111,21 @@ class ValidatePythonHandlerProtocol(BaseValidateHandlerProtocol, Protocol): input: Any, *, strict: bool | None = None, - extra: ExtraValues | None = None, from_attributes: bool | None = None, - context: Any | None = None, + context: dict[str, Any] | None = None, self_instance: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, ) -> None: """Callback to be notified of validation start, and create an instance of the event handler. Args: input: The input to be validated. strict: Whether to validate the object in strict mode. - extra: Whether to ignore, allow, or forbid extra data during model validation. from_attributes: Whether to validate objects as inputs by extracting attributes. context: The context to use for validation, this is passed to functional validators. self_instance: An instance of a model to set attributes on from validation, this is used when running validation from the `__init__` method of a model. - by_alias: Whether to use the field's alias to match the input data to an attribute. - by_name: Whether to use the field's name to match the input data to an attribute. """ + pass class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol): @@ -145,24 +136,19 @@ class ValidateJsonHandlerProtocol(BaseValidateHandlerProtocol, Protocol): input: str | bytes | bytearray, *, strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, + context: dict[str, Any] | None = None, self_instance: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, ) -> None: """Callback to be notified of validation start, and create an instance of the event handler. Args: input: The JSON data to be validated. strict: Whether to validate the object in strict mode. - extra: Whether to ignore, allow, or forbid extra data during model validation. context: The context to use for validation, this is passed to functional validators. self_instance: An instance of a model to set attributes on from validation, this is used when running validation from the `__init__` method of a model. - by_alias: Whether to use the field's alias to match the input data to an attribute. - by_name: Whether to use the field's name to match the input data to an attribute. """ + pass StringInput: TypeAlias = 'dict[str, StringInput]' @@ -172,22 +158,13 @@ class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol): """Event handler for `SchemaValidator.validate_strings`.""" def on_enter( - self, - input: StringInput, - *, - strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, + self, input: StringInput, *, strict: bool | None = None, context: dict[str, Any] | None = None ) -> None: """Callback to be notified of validation start, and create an instance of the event handler. Args: input: The string data to be validated. strict: Whether to validate the object in strict mode. - extra: Whether to ignore, allow, or forbid extra data during model validation. context: The context to use for validation, this is passed to functional validators. - by_alias: Whether to use the field's alias to match the input data to an attribute. - by_name: Whether to use the field's name to match the input data to an attribute. """ + pass diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc index 74627129..a6f72cd6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc index d23c45f4..a5cf6d41 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc index 11cb2624..23468fde 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_loader.py b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_loader.py index a789092f..b30143b6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_loader.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_loader.py @@ -1,10 +1,16 @@ from __future__ import annotations -import importlib.metadata as importlib_metadata -import os +import sys import warnings -from collections.abc import Iterable -from typing import TYPE_CHECKING, Final +from typing import TYPE_CHECKING, Iterable + +from typing_extensions import Final + +if sys.version_info >= (3, 8): + import importlib.metadata as importlib_metadata +else: + import importlib_metadata + if TYPE_CHECKING: from . import PydanticPluginProtocol @@ -24,13 +30,10 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]: Inspired by: https://github.com/pytest-dev/pluggy/blob/1.3.0/src/pluggy/_manager.py#L376-L402 """ - disabled_plugins = os.getenv('PYDANTIC_DISABLE_PLUGINS') global _plugins, _loading_plugins if _loading_plugins: # this happens when plugins themselves use pydantic, we return no plugins return () - elif disabled_plugins in ('__all__', '1', 'true'): - return () elif _plugins is None: _plugins = {} # set _loading_plugins so any plugins that use pydantic don't themselves use plugins @@ -42,15 +45,12 @@ def get_plugins() -> Iterable[PydanticPluginProtocol]: continue if entry_point.value in _plugins: continue - if disabled_plugins is not None and entry_point.name in disabled_plugins.split(','): - continue try: _plugins[entry_point.value] = entry_point.load() except (ImportError, AttributeError) as e: warnings.warn( f'{e.__class__.__name__} while loading the `{entry_point.name}` Pydantic plugin, ' - f'this plugin will not be installed.\n\n{e!r}', - stacklevel=2, + f'this plugin will not be installed.\n\n{e!r}' ) finally: _loading_plugins = False diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_schema_validator.py b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_schema_validator.py index 83f2562b..7186ece6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_schema_validator.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/plugin/_schema_validator.py @@ -1,13 +1,11 @@ """Pluggable schema validator for pydantic.""" - from __future__ import annotations import functools -from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar from pydantic_core import CoreConfig, CoreSchema, SchemaValidator, ValidationError -from typing_extensions import ParamSpec +from typing_extensions import Literal, ParamSpec if TYPE_CHECKING: from . import BaseValidateHandlerProtocol, PydanticPluginProtocol, SchemaKind, SchemaTypePath @@ -27,7 +25,7 @@ def create_schema_validator( schema_kind: SchemaKind, config: CoreConfig | None = None, plugin_settings: dict[str, Any] | None = None, -) -> SchemaValidator | PluggableSchemaValidator: +) -> SchemaValidator: """Create a `SchemaValidator` or `PluggableSchemaValidator` if plugins are installed. Returns: @@ -46,7 +44,7 @@ def create_schema_validator( config, plugins, plugin_settings or {}, - ) + ) # type: ignore else: return SchemaValidator(schema, config) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/root_model.py b/Backend/venv/lib/python3.12/site-packages/pydantic/root_model.py index 80a54201..2d856102 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/root_model.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/root_model.py @@ -2,36 +2,31 @@ from __future__ import annotations as _annotations +import typing from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar from pydantic_core import PydanticUndefined -from typing_extensions import Self, dataclass_transform from . import PydanticUserError -from ._internal import _model_construction, _repr +from ._internal import _repr from .main import BaseModel, _object_setattr -if TYPE_CHECKING: - from .fields import Field as PydanticModelField - from .fields import PrivateAttr as PydanticModelPrivateAttr +if typing.TYPE_CHECKING: + from typing import Any + + from typing_extensions import Literal + + Model = typing.TypeVar('Model', bound='BaseModel') - # dataclass_transform could be applied to RootModel directly, but `ModelMetaclass`'s dataclass_transform - # takes priority (at least with pyright). We trick type checkers into thinking we apply dataclass_transform - # on a new metaclass. - @dataclass_transform(kw_only_default=False, field_specifiers=(PydanticModelField, PydanticModelPrivateAttr)) - class _RootModelMetaclass(_model_construction.ModelMetaclass): ... -else: - _RootModelMetaclass = _model_construction.ModelMetaclass __all__ = ('RootModel',) -RootModelRootType = TypeVar('RootModelRootType') + +RootModelRootType = typing.TypeVar('RootModelRootType') -class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetaclass): - """!!! abstract "Usage Documentation" - [`RootModel` and Custom Root Types](../concepts/models.md#rootmodel-and-custom-root-types) +class RootModel(BaseModel, typing.Generic[RootModelRootType]): + """Usage docs: https://docs.pydantic.dev/2.5/concepts/models/#rootmodel-and-custom-root-types A Pydantic `BaseModel` for the root object of the model. @@ -57,7 +52,7 @@ class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetac ) super().__init_subclass__(**kwargs) - def __init__(self, /, root: RootModelRootType = PydanticUndefined, **data) -> None: # type: ignore + def __init__(__pydantic_self__, root: RootModelRootType = PydanticUndefined, **data) -> None: # type: ignore __tracebackhide__ = True if data: if root is not PydanticUndefined: @@ -65,12 +60,12 @@ class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetac '"RootModel.__init__" accepts either a single positional argument or arbitrary keyword arguments' ) root = data # type: ignore - self.__pydantic_validator__.validate_python(root, self_instance=self) + __pydantic_self__.__pydantic_validator__.validate_python(root, self_instance=__pydantic_self__) - __init__.__pydantic_base_init__ = True # pyright: ignore[reportFunctionMemberAccess] + __init__.__pydantic_base_init__ = True @classmethod - def model_construct(cls, root: RootModelRootType, _fields_set: set[str] | None = None) -> Self: # type: ignore + def model_construct(cls: type[Model], root: RootModelRootType, _fields_set: set[str] | None = None) -> Model: """Create a new model using the provided root object and update fields set. Args: @@ -95,7 +90,7 @@ class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetac _object_setattr(self, '__pydantic_fields_set__', state['__pydantic_fields_set__']) _object_setattr(self, '__dict__', state['__dict__']) - def __copy__(self) -> Self: + def __copy__(self: Model) -> Model: """Returns a shallow copy of the model.""" cls = type(self) m = cls.__new__(cls) @@ -103,7 +98,7 @@ class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetac _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) return m - def __deepcopy__(self, memo: dict[int, Any] | None = None) -> Self: + def __deepcopy__(self: Model, memo: dict[int, Any] | None = None) -> Model: """Returns a deep copy of the model.""" cls = type(self) m = cls.__new__(cls) @@ -113,43 +108,32 @@ class RootModel(BaseModel, Generic[RootModelRootType], metaclass=_RootModelMetac _object_setattr(m, '__pydantic_fields_set__', copy(self.__pydantic_fields_set__)) return m - if TYPE_CHECKING: + if typing.TYPE_CHECKING: - def model_dump( # type: ignore + def model_dump( self, *, mode: Literal['json', 'python'] | str = 'python', include: Any = None, exclude: Any = None, - context: dict[str, Any] | None = None, - by_alias: bool | None = None, + by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - serialize_as_any: bool = False, - ) -> Any: + warnings: bool = True, + ) -> RootModelRootType: """This method is included just to get a more accurate return type for type checkers. It is included in this `if TYPE_CHECKING:` block since no override is actually necessary. See the documentation of `BaseModel.model_dump` for more details about the arguments. - - Generally, this method will have a return type of `RootModelRootType`, assuming that `RootModelRootType` is - not a `BaseModel` subclass. If `RootModelRootType` is a `BaseModel` subclass, then the return - type will likely be `dict[str, Any]`, as `model_dump` calls are recursive. The return type could - even be something different, in the case of a custom serializer. - Thus, `Any` is used here to catch all of these cases. """ ... def __eq__(self, other: Any) -> bool: if not isinstance(other, RootModel): return NotImplemented - return self.__pydantic_fields__['root'].annotation == other.__pydantic_fields__[ - 'root' - ].annotation and super().__eq__(other) + return self.model_fields['root'].annotation == other.model_fields['root'].annotation and super().__eq__(other) def __repr_args__(self) -> _repr.ReprArgs: yield 'root', self.root diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/schema.py index a3245a61..e290aed9 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/schema.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/schema.py @@ -1,5 +1,4 @@ """The `schema` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/tools.py b/Backend/venv/lib/python3.12/site-packages/pydantic/tools.py index fdc68c4f..8e317c92 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/tools.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/tools.py @@ -1,5 +1,4 @@ """The `tools` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/type_adapter.py b/Backend/venv/lib/python3.12/site-packages/pydantic/type_adapter.py index 6f1a082e..2262c58f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/type_adapter.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/type_adapter.py @@ -1,31 +1,18 @@ """Type adapter specification.""" - from __future__ import annotations as _annotations import sys -import types -from collections.abc import Callable, Iterable from dataclasses import is_dataclass -from types import FrameType -from typing import ( - Any, - Generic, - Literal, - TypeVar, - cast, - final, - overload, -) +from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Set, TypeVar, Union, cast, overload from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator, Some -from typing_extensions import ParamSpec, is_typeddict +from typing_extensions import Literal, is_typeddict from pydantic.errors import PydanticUserError -from pydantic.main import BaseModel, IncEx +from pydantic.main import BaseModel -from ._internal import _config, _generate_schema, _mock_val_ser, _namespace_utils, _repr, _typing_extra, _utils -from .config import ConfigDict, ExtraValues -from .errors import PydanticUndefinedAnnotation +from ._internal import _config, _generate_schema, _typing_extra +from .config import ConfigDict from .json_schema import ( DEFAULT_REF_TEMPLATE, GenerateJsonSchema, @@ -33,12 +20,67 @@ from .json_schema import ( JsonSchemaMode, JsonSchemaValue, ) -from .plugin._schema_validator import PluggableSchemaValidator, create_schema_validator +from .plugin._schema_validator import create_schema_validator T = TypeVar('T') -R = TypeVar('R') -P = ParamSpec('P') -TypeAdapterT = TypeVar('TypeAdapterT', bound='TypeAdapter') + +if TYPE_CHECKING: + # should be `set[int] | set[str] | dict[int, IncEx] | dict[str, IncEx] | None`, but mypy can't cope + IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] + + +def _get_schema(type_: Any, config_wrapper: _config.ConfigWrapper, parent_depth: int) -> CoreSchema: + """`BaseModel` uses its own `__module__` to find out where it was defined + and then look for symbols to resolve forward references in those globals. + On the other hand this function can be called with arbitrary objects, + including type aliases where `__module__` (always `typing.py`) is not useful. + So instead we look at the globals in our parent stack frame. + + This works for the case where this function is called in a module that + has the target of forward references in its scope, but + does not work for more complex cases. + + For example, take the following: + + a.py + ```python + from typing import Dict, List + + IntList = List[int] + OuterDict = Dict[str, 'IntList'] + ``` + + b.py + ```python test="skip" + from a import OuterDict + + from pydantic import TypeAdapter + + IntList = int # replaces the symbol the forward reference is looking for + v = TypeAdapter(OuterDict) + v({'x': 1}) # should fail but doesn't + ``` + + If OuterDict were a `BaseModel`, this would work because it would resolve + the forward reference within the `a.py` namespace. + But `TypeAdapter(OuterDict)` + can't know what module OuterDict came from. + + In other words, the assumption that _all_ forward references exist in the + module we are being called from is not technically always true. + Although most of the time it is and it works fine for recursive models and such, + `BaseModel`'s behavior isn't perfect either and _can_ break in similar ways, + so there is no right or wrong between the two. + + But at the very least this behavior is _subtly_ different from `BaseModel`'s. + """ + local_ns = _typing_extra.parent_frame_namespace(parent_depth=parent_depth) + global_ns = sys._getframe(max(parent_depth - 1, 1)).f_globals.copy() + global_ns.update(local_ns or {}) + gen = _generate_schema.GenerateSchema(config_wrapper, types_namespace=global_ns, typevars_map={}) + schema = gen.generate_schema(type_) + schema = gen.clean_schema(schema) + return schema def _getattr_no_parents(obj: Any, attribute: str) -> Any: @@ -56,152 +98,85 @@ def _getattr_no_parents(obj: Any, attribute: str) -> Any: raise AttributeError(attribute) -def _type_has_config(type_: Any) -> bool: - """Returns whether the type has config.""" - type_ = _typing_extra.annotated_type(type_) or type_ - try: - return issubclass(type_, BaseModel) or is_dataclass(type_) or is_typeddict(type_) - except TypeError: - # type is not a class - return False - - -@final class TypeAdapter(Generic[T]): - """!!! abstract "Usage Documentation" - [`TypeAdapter`](../concepts/type_adapter.md) - - Type adapters provide a flexible way to perform validation and serialization based on a Python type. + """Type adapters provide a flexible way to perform validation and serialization based on a Python type. A `TypeAdapter` instance exposes some of the functionality from `BaseModel` instance methods for types that do not have such methods (such as dataclasses, primitive types, and more). - **Note:** `TypeAdapter` instances are not types, and cannot be used as type annotations for fields. - - Args: - type: The type associated with the `TypeAdapter`. - config: Configuration for the `TypeAdapter`, should be a dictionary conforming to - [`ConfigDict`][pydantic.config.ConfigDict]. - - !!! note - You cannot provide a configuration when instantiating a `TypeAdapter` if the type you're using - has its own config that cannot be overridden (ex: `BaseModel`, `TypedDict`, and `dataclass`). A - [`type-adapter-config-unused`](../errors/usage_errors.md#type-adapter-config-unused) error will - be raised in this case. - _parent_depth: Depth at which to search for the [parent frame][frame-objects]. This frame is used when - resolving forward annotations during schema building, by looking for the globals and locals of this - frame. Defaults to 2, which will result in the frame where the `TypeAdapter` was instantiated. - - !!! note - This parameter is named with an underscore to suggest its private nature and discourage use. - It may be deprecated in a minor version, so we only recommend using it if you're comfortable - with potential change in behavior/support. It's default value is 2 because internally, - the `TypeAdapter` class makes another call to fetch the frame. - module: The module that passes to plugin if provided. + Note that `TypeAdapter` is not an actual type, so you cannot use it in type annotations. Attributes: core_schema: The core schema for the type. - validator: The schema validator for the type. + validator (SchemaValidator): The schema validator for the type. serializer: The schema serializer for the type. - pydantic_complete: Whether the core schema for the type is successfully built. - - ??? tip "Compatibility with `mypy`" - Depending on the type used, `mypy` might raise an error when instantiating a `TypeAdapter`. As a workaround, you can explicitly - annotate your variable: - - ```py - from typing import Union - - from pydantic import TypeAdapter - - ta: TypeAdapter[Union[str, int]] = TypeAdapter(Union[str, int]) # type: ignore[arg-type] - ``` - - ??? info "Namespace management nuances and implementation details" - - Here, we collect some notes on namespace management, and subtle differences from `BaseModel`: - - `BaseModel` uses its own `__module__` to find out where it was defined - and then looks for symbols to resolve forward references in those globals. - On the other hand, `TypeAdapter` can be initialized with arbitrary objects, - which may not be types and thus do not have a `__module__` available. - So instead we look at the globals in our parent stack frame. - - It is expected that the `ns_resolver` passed to this function will have the correct - namespace for the type we're adapting. See the source code for `TypeAdapter.__init__` - and `TypeAdapter.rebuild` for various ways to construct this namespace. - - This works for the case where this function is called in a module that - has the target of forward references in its scope, but - does not always work for more complex cases. - - For example, take the following: - - ```python {title="a.py"} - IntList = list[int] - OuterDict = dict[str, 'IntList'] - ``` - - ```python {test="skip" title="b.py"} - from a import OuterDict - - from pydantic import TypeAdapter - - IntList = int # replaces the symbol the forward reference is looking for - v = TypeAdapter(OuterDict) - v({'x': 1}) # should fail but doesn't - ``` - - If `OuterDict` were a `BaseModel`, this would work because it would resolve - the forward reference within the `a.py` namespace. - But `TypeAdapter(OuterDict)` can't determine what module `OuterDict` came from. - - In other words, the assumption that _all_ forward references exist in the - module we are being called from is not technically always true. - Although most of the time it is and it works fine for recursive models and such, - `BaseModel`'s behavior isn't perfect either and _can_ break in similar ways, - so there is no right or wrong between the two. - - But at the very least this behavior is _subtly_ different from `BaseModel`'s. """ - core_schema: CoreSchema - validator: SchemaValidator | PluggableSchemaValidator - serializer: SchemaSerializer - pydantic_complete: bool + if TYPE_CHECKING: - @overload - def __init__( - self, - type: type[T], - *, - config: ConfigDict | None = ..., - _parent_depth: int = ..., - module: str | None = ..., - ) -> None: ... + @overload + def __new__(cls, __type: type[T], *, config: ConfigDict | None = ...) -> TypeAdapter[T]: + ... - # This second overload is for unsupported special forms (such as Annotated, Union, etc.) - # Currently there is no way to type this correctly - # See https://github.com/python/typing/pull/1618 - @overload - def __init__( - self, - type: Any, - *, - config: ConfigDict | None = ..., - _parent_depth: int = ..., - module: str | None = ..., - ) -> None: ... + # this overload is for non-type things like Union[int, str] + # Pyright currently handles this "correctly", but MyPy understands this as TypeAdapter[object] + # so an explicit type cast is needed + @overload + def __new__(cls, __type: T, *, config: ConfigDict | None = ...) -> TypeAdapter[T]: + ... + + def __new__(cls, __type: Any, *, config: ConfigDict | None = None) -> TypeAdapter[T]: + """A class representing the type adapter.""" + raise NotImplementedError + + @overload + def __init__( + self, type: type[T], *, config: ConfigDict | None = None, _parent_depth: int = 2, module: str | None = None + ) -> None: + ... + + # this overload is for non-type things like Union[int, str] + # Pyright currently handles this "correctly", but MyPy understands this as TypeAdapter[object] + # so an explicit type cast is needed + @overload + def __init__( + self, type: T, *, config: ConfigDict | None = None, _parent_depth: int = 2, module: str | None = None + ) -> None: + ... def __init__( - self, - type: Any, - *, - config: ConfigDict | None = None, - _parent_depth: int = 2, - module: str | None = None, + self, type: Any, *, config: ConfigDict | None = None, _parent_depth: int = 2, module: str | None = None ) -> None: - if _type_has_config(type) and config is not None: + """Initializes the TypeAdapter object. + + Args: + type: The type associated with the `TypeAdapter`. + config: Configuration for the `TypeAdapter`, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. + _parent_depth: depth at which to search the parent namespace to construct the local namespace. + module: The module that passes to plugin if provided. + + !!! note + You cannot use the `config` argument when instantiating a `TypeAdapter` if the type you're using has its own + config that cannot be overridden (ex: `BaseModel`, `TypedDict`, and `dataclass`). A + [`type-adapter-config-unused`](../errors/usage_errors.md#type-adapter-config-unused) error will be raised in this case. + + !!! note + The `_parent_depth` argument is named with an underscore to suggest its private nature and discourage use. + It may be deprecated in a minor version, so we only recommend using it if you're + comfortable with potential change in behavior / support. + + Returns: + A type adapter configured for the specified `type`. + """ + config_wrapper = _config.ConfigWrapper(config) + + try: + type_has_config = issubclass(type, BaseModel) or is_dataclass(type) or is_typeddict(type) + except TypeError: + # type is not a class + type_has_config = False + + if type_has_config and config is not None: raise PydanticUserError( 'Cannot use `config` when the type is a BaseModel, dataclass or TypedDict.' ' These types can have their own config and setting the config via the `config`' @@ -210,220 +185,49 @@ class TypeAdapter(Generic[T]): code='type-adapter-config-unused', ) - self._type = type - self._config = config - self._parent_depth = _parent_depth - self.pydantic_complete = False - - parent_frame = self._fetch_parent_frame() - if isinstance(type, types.FunctionType): - # Special case functions, which are *not* pushed to the `NsResolver` stack and without this special case - # would only have access to the parent namespace where the `TypeAdapter` was instantiated (if the function is defined - # in another module, we need to look at that module's globals). - if parent_frame is not None: - # `f_locals` is the namespace where the type adapter was instantiated (~ to `f_globals` if at the module level): - parent_ns = parent_frame.f_locals - else: # pragma: no cover - parent_ns = None - globalns, localns = _namespace_utils.ns_for_function( - type, - parent_namespace=parent_ns, - ) - parent_namespace = None - else: - if parent_frame is not None: - globalns = parent_frame.f_globals - # Do not provide a local ns if the type adapter happens to be instantiated at the module level: - localns = parent_frame.f_locals if parent_frame.f_locals is not globalns else {} - else: # pragma: no cover - globalns = {} - localns = {} - parent_namespace = localns - - self._module_name = module or cast(str, globalns.get('__name__', '')) - self._init_core_attrs( - ns_resolver=_namespace_utils.NsResolver( - namespaces_tuple=_namespace_utils.NamespacesTuple(locals=localns, globals=globalns), - parent_namespace=parent_namespace, - ), - force=False, - ) - - def _fetch_parent_frame(self) -> FrameType | None: - frame = sys._getframe(self._parent_depth) - if frame.f_globals.get('__name__') == 'typing': - # Because `TypeAdapter` is generic, explicitly parametrizing the class results - # in a `typing._GenericAlias` instance, which proxies instantiation calls to the - # "real" `TypeAdapter` class and thus adding an extra frame to the call. To avoid - # pulling anything from the `typing` module, use the correct frame (the one before): - return frame.f_back - - return frame - - def _init_core_attrs( - self, ns_resolver: _namespace_utils.NsResolver, force: bool, raise_errors: bool = False - ) -> bool: - """Initialize the core schema, validator, and serializer for the type. - - Args: - ns_resolver: The namespace resolver to use when building the core schema for the adapted type. - force: Whether to force the construction of the core schema, validator, and serializer. - If `force` is set to `False` and `_defer_build` is `True`, the core schema, validator, and serializer will be set to mocks. - raise_errors: Whether to raise errors if initializing any of the core attrs fails. - - Returns: - `True` if the core schema, validator, and serializer were successfully initialized, otherwise `False`. - - Raises: - PydanticUndefinedAnnotation: If `PydanticUndefinedAnnotation` occurs in`__get_pydantic_core_schema__` - and `raise_errors=True`. - """ - if not force and self._defer_build: - _mock_val_ser.set_type_adapter_mocks(self) - self.pydantic_complete = False - return False - + core_schema: CoreSchema try: - self.core_schema = _getattr_no_parents(self._type, '__pydantic_core_schema__') - self.validator = _getattr_no_parents(self._type, '__pydantic_validator__') - self.serializer = _getattr_no_parents(self._type, '__pydantic_serializer__') - - # TODO: we don't go through the rebuild logic here directly because we don't want - # to repeat all of the namespace fetching logic that we've already done - # so we simply skip to the block below that does the actual schema generation - if ( - isinstance(self.core_schema, _mock_val_ser.MockCoreSchema) - or isinstance(self.validator, _mock_val_ser.MockValSer) - or isinstance(self.serializer, _mock_val_ser.MockValSer) - ): - raise AttributeError() + core_schema = _getattr_no_parents(type, '__pydantic_core_schema__') except AttributeError: - config_wrapper = _config.ConfigWrapper(self._config) + core_schema = _get_schema(type, config_wrapper, parent_depth=_parent_depth + 1) - schema_generator = _generate_schema.GenerateSchema(config_wrapper, ns_resolver=ns_resolver) + core_config = config_wrapper.core_config(None) + validator: SchemaValidator + try: + validator = _getattr_no_parents(type, '__pydantic_validator__') + except AttributeError: + if module is None: + f = sys._getframe(1) + module = cast(str, f.f_globals['__name__']) + validator = create_schema_validator( + core_schema, type, module, str(type), 'TypeAdapter', core_config, config_wrapper.plugin_settings + ) # type: ignore - try: - core_schema = schema_generator.generate_schema(self._type) - except PydanticUndefinedAnnotation: - if raise_errors: - raise - _mock_val_ser.set_type_adapter_mocks(self) - return False + serializer: SchemaSerializer + try: + serializer = _getattr_no_parents(type, '__pydantic_serializer__') + except AttributeError: + serializer = SchemaSerializer(core_schema, core_config) - try: - self.core_schema = schema_generator.clean_schema(core_schema) - except _generate_schema.InvalidSchemaError: - _mock_val_ser.set_type_adapter_mocks(self) - return False - - core_config = config_wrapper.core_config(None) - - self.validator = create_schema_validator( - schema=self.core_schema, - schema_type=self._type, - schema_type_module=self._module_name, - schema_type_name=str(self._type), - schema_kind='TypeAdapter', - config=core_config, - plugin_settings=config_wrapper.plugin_settings, - ) - self.serializer = SchemaSerializer(self.core_schema, core_config) - - self.pydantic_complete = True - return True - - @property - def _defer_build(self) -> bool: - config = self._config if self._config is not None else self._model_config - if config: - return config.get('defer_build') is True - return False - - @property - def _model_config(self) -> ConfigDict | None: - type_: Any = _typing_extra.annotated_type(self._type) or self._type # Eg FastAPI heavily uses Annotated - if _utils.lenient_issubclass(type_, BaseModel): - return type_.model_config - return getattr(type_, '__pydantic_config__', None) - - def __repr__(self) -> str: - return f'TypeAdapter({_repr.display_as_type(self._type)})' - - def rebuild( - self, - *, - force: bool = False, - raise_errors: bool = True, - _parent_namespace_depth: int = 2, - _types_namespace: _namespace_utils.MappingNamespace | None = None, - ) -> bool | None: - """Try to rebuild the pydantic-core schema for the adapter's type. - - This may be necessary when one of the annotations is a ForwardRef which could not be resolved during - the initial attempt to build the schema, and automatic rebuilding fails. - - Args: - force: Whether to force the rebuilding of the type adapter's schema, defaults to `False`. - raise_errors: Whether to raise errors, defaults to `True`. - _parent_namespace_depth: Depth at which to search for the [parent frame][frame-objects]. This - frame is used when resolving forward annotations during schema rebuilding, by looking for - the locals of this frame. Defaults to 2, which will result in the frame where the method - was called. - _types_namespace: An explicit types namespace to use, instead of using the local namespace - from the parent frame. Defaults to `None`. - - Returns: - Returns `None` if the schema is already "complete" and rebuilding was not required. - If rebuilding _was_ required, returns `True` if rebuilding was successful, otherwise `False`. - """ - if not force and self.pydantic_complete: - return None - - if _types_namespace is not None: - rebuild_ns = _types_namespace - elif _parent_namespace_depth > 0: - rebuild_ns = _typing_extra.parent_frame_namespace(parent_depth=_parent_namespace_depth, force=True) or {} - else: - rebuild_ns = {} - - # we have to manually fetch globals here because there's no type on the stack of the NsResolver - # and so we skip the globalns = get_module_ns_of(typ) call that would normally happen - globalns = sys._getframe(max(_parent_namespace_depth - 1, 1)).f_globals - ns_resolver = _namespace_utils.NsResolver( - namespaces_tuple=_namespace_utils.NamespacesTuple(locals=rebuild_ns, globals=globalns), - parent_namespace=rebuild_ns, - ) - return self._init_core_attrs(ns_resolver=ns_resolver, force=True, raise_errors=raise_errors) + self.core_schema = core_schema + self.validator = validator + self.serializer = serializer def validate_python( self, - object: Any, - /, + __object: Any, *, strict: bool | None = None, - extra: ExtraValues | None = None, from_attributes: bool | None = None, - context: Any | None = None, - experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, + context: dict[str, Any] | None = None, ) -> T: """Validate a Python object against the model. Args: - object: The Python object to validate against the model. + __object: The Python object to validate against the model. strict: Whether to strictly check types. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. from_attributes: Whether to extract data from object attributes. context: Additional context to pass to the validator. - experimental_allow_partial: **Experimental** whether to enable - [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. - * False / 'off': Default behavior, no partial validation. - * True / 'on': Enable partial validation. - * 'trailing-strings': Enable partial validation and allow trailing strings in the input. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. !!! note When using `TypeAdapter` with a Pydantic `dataclass`, the use of the `from_attributes` @@ -432,121 +236,39 @@ class TypeAdapter(Generic[T]): Returns: The validated object. """ - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) - - return self.validator.validate_python( - object, - strict=strict, - extra=extra, - from_attributes=from_attributes, - context=context, - allow_partial=experimental_allow_partial, - by_alias=by_alias, - by_name=by_name, - ) + return self.validator.validate_python(__object, strict=strict, from_attributes=from_attributes, context=context) def validate_json( - self, - data: str | bytes | bytearray, - /, - *, - strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, - experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, + self, __data: str | bytes, *, strict: bool | None = None, context: dict[str, Any] | None = None ) -> T: - """!!! abstract "Usage Documentation" - [JSON Parsing](../concepts/json.md#json-parsing) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/json/#json-parsing Validate a JSON string or bytes against the model. Args: - data: The JSON data to validate against the model. + __data: The JSON data to validate against the model. strict: Whether to strictly check types. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. context: Additional context to use during validation. - experimental_allow_partial: **Experimental** whether to enable - [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. - * False / 'off': Default behavior, no partial validation. - * True / 'on': Enable partial validation. - * 'trailing-strings': Enable partial validation and allow trailing strings in the input. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Returns: The validated object. """ - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) + return self.validator.validate_json(__data, strict=strict, context=context) - return self.validator.validate_json( - data, - strict=strict, - extra=extra, - context=context, - allow_partial=experimental_allow_partial, - by_alias=by_alias, - by_name=by_name, - ) - - def validate_strings( - self, - obj: Any, - /, - *, - strict: bool | None = None, - extra: ExtraValues | None = None, - context: Any | None = None, - experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, - ) -> T: + def validate_strings(self, __obj: Any, *, strict: bool | None = None, context: dict[str, Any] | None = None) -> T: """Validate object contains string data against the model. Args: - obj: The object contains string data to validate. + __obj: The object contains string data to validate. strict: Whether to strictly check types. - extra: Whether to ignore, allow, or forbid extra data during model validation. - See the [`extra` configuration value][pydantic.ConfigDict.extra] for details. context: Additional context to use during validation. - experimental_allow_partial: **Experimental** whether to enable - [partial validation](../concepts/experimental.md#partial-validation), e.g. to process streams. - * False / 'off': Default behavior, no partial validation. - * True / 'on': Enable partial validation. - * 'trailing-strings': Enable partial validation and allow trailing strings in the input. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Returns: The validated object. """ - if by_alias is False and by_name is not True: - raise PydanticUserError( - 'At least one of `by_alias` or `by_name` must be set to True.', - code='validate-by-alias-and-name-false', - ) + return self.validator.validate_strings(__obj, strict=strict, context=context) - return self.validator.validate_strings( - obj, - strict=strict, - extra=extra, - context=context, - allow_partial=experimental_allow_partial, - by_alias=by_alias, - by_name=by_name, - ) - - def get_default_value(self, *, strict: bool | None = None, context: Any | None = None) -> Some[T] | None: + def get_default_value(self, *, strict: bool | None = None, context: dict[str, Any] | None = None) -> Some[T] | None: """Get the default value for the wrapped type. Args: @@ -560,27 +282,22 @@ class TypeAdapter(Generic[T]): def dump_python( self, - instance: T, - /, + __instance: T, *, mode: Literal['json', 'python'] = 'python', include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool | None = None, + by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, + warnings: bool = True, ) -> Any: """Dump an instance of the adapted type to a Python object. Args: - instance: The Python object to serialize. + __instance: The Python object to serialize. mode: The output format. include: Fields to include in the output. exclude: Fields to exclude from the output. @@ -588,22 +305,14 @@ class TypeAdapter(Generic[T]): exclude_unset: Whether to exclude unset fields. exclude_defaults: Whether to exclude fields with default values. exclude_none: Whether to exclude fields with None values. - exclude_computed_fields: Whether to exclude computed fields. - While this can be useful for round-tripping, it is usually recommended to use the dedicated - `round_trip` parameter instead. round_trip: Whether to output the serialized data in a way that is compatible with deserialization. - warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. - fallback: A function to call when an unknown value is encountered. If not provided, - a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: Additional context to pass to the serializer. + warnings: Whether to display serialization warnings. Returns: The serialized object. """ return self.serializer.to_python( - instance, + __instance, mode=mode, by_alias=by_alias, include=include, @@ -611,80 +320,54 @@ class TypeAdapter(Generic[T]): exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, - exclude_computed_fields=exclude_computed_fields, round_trip=round_trip, warnings=warnings, - fallback=fallback, - serialize_as_any=serialize_as_any, - context=context, ) def dump_json( self, - instance: T, - /, + __instance: T, *, indent: int | None = None, - ensure_ascii: bool = False, include: IncEx | None = None, exclude: IncEx | None = None, - by_alias: bool | None = None, + by_alias: bool = False, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, - fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, + warnings: bool = True, ) -> bytes: - """!!! abstract "Usage Documentation" - [JSON Serialization](../concepts/json.md#json-serialization) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/json/#json-serialization Serialize an instance of the adapted type to JSON. Args: - instance: The instance to be serialized. + __instance: The instance to be serialized. indent: Number of spaces for JSON indentation. - ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. - If `False` (the default), these characters will be output as-is. include: Fields to include. exclude: Fields to exclude. by_alias: Whether to use alias names for field names. exclude_unset: Whether to exclude unset fields. exclude_defaults: Whether to exclude fields with default values. exclude_none: Whether to exclude fields with a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. - While this can be useful for round-tripping, it is usually recommended to use the dedicated - `round_trip` parameter instead. round_trip: Whether to serialize and deserialize the instance to ensure round-tripping. - warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. - fallback: A function to call when an unknown value is encountered. If not provided, - a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: Additional context to pass to the serializer. + warnings: Whether to emit serialization warnings. Returns: The JSON representation of the given instance as bytes. """ return self.serializer.to_json( - instance, + __instance, indent=indent, - ensure_ascii=ensure_ascii, include=include, exclude=exclude, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, - exclude_computed_fields=exclude_computed_fields, round_trip=round_trip, warnings=warnings, - fallback=fallback, - serialize_as_any=serialize_as_any, - context=context, ) def json_schema( @@ -692,7 +375,6 @@ class TypeAdapter(Generic[T]): *, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, mode: JsonSchemaMode = 'validation', ) -> dict[str, Any]: @@ -701,61 +383,35 @@ class TypeAdapter(Generic[T]): Args: by_alias: Whether to use alias names for field names. ref_template: The format string used for generating $ref strings. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. - schema_generator: To override the logic used to generate the JSON schema, as a subclass of - `GenerateJsonSchema` with your desired modifications - mode: The mode in which to generate the schema. schema_generator: The generator class used for creating the schema. mode: The mode to use for schema generation. Returns: The JSON schema for the model as a dictionary. """ - schema_generator_instance = schema_generator( - by_alias=by_alias, ref_template=ref_template, union_format=union_format - ) - if isinstance(self.core_schema, _mock_val_ser.MockCoreSchema): - self.core_schema.rebuild() - assert not isinstance(self.core_schema, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it' + schema_generator_instance = schema_generator(by_alias=by_alias, ref_template=ref_template) return schema_generator_instance.generate(self.core_schema, mode=mode) @staticmethod def json_schemas( - inputs: Iterable[tuple[JsonSchemaKeyT, JsonSchemaMode, TypeAdapter[Any]]], - /, + __inputs: Iterable[tuple[JsonSchemaKeyT, JsonSchemaMode, TypeAdapter[Any]]], *, by_alias: bool = True, title: str | None = None, description: str | None = None, ref_template: str = DEFAULT_REF_TEMPLATE, - union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]: """Generate a JSON schema including definitions from multiple type adapters. Args: - inputs: Inputs to schema generation. The first two items will form the keys of the (first) + __inputs: Inputs to schema generation. The first two items will form the keys of the (first) output mapping; the type adapters will provide the core schemas that get converted into definitions in the output JSON schema. by_alias: Whether to use alias names. title: The title for the schema. description: The description for the schema. ref_template: The format string used for generating $ref strings. - union_format: The format to use when combining schemas from unions together. Can be one of: - - - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) - keyword to combine schemas (the default). - - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) - keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive - type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to - `any_of`. schema_generator: The generator class used for creating the schema. Returns: @@ -768,21 +424,11 @@ class TypeAdapter(Generic[T]): element, along with the optional title and description keys. """ - schema_generator_instance = schema_generator( - by_alias=by_alias, ref_template=ref_template, union_format=union_format - ) + schema_generator_instance = schema_generator(by_alias=by_alias, ref_template=ref_template) - inputs_ = [] - for key, mode, adapter in inputs: - # This is the same pattern we follow for model json schemas - we attempt a core schema rebuild if we detect a mock - if isinstance(adapter.core_schema, _mock_val_ser.MockCoreSchema): - adapter.core_schema.rebuild() - assert not isinstance(adapter.core_schema, _mock_val_ser.MockCoreSchema), ( - 'this is a bug! please report it' - ) - inputs_.append((key, mode, adapter.core_schema)) + inputs = [(key, mode, adapter.core_schema) for key, mode, adapter in __inputs] - json_schemas_map, definitions = schema_generator_instance.generate_definitions(inputs_) + json_schemas_map, definitions = schema_generator_instance.generate_definitions(inputs) json_schema: dict[str, Any] = {} if definitions: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/types.py b/Backend/venv/lib/python3.12/site-packages/pydantic/types.py index 59160ab7..095d55b4 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/types.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/types.py @@ -1,25 +1,26 @@ """The types module contains custom types used by pydantic.""" - from __future__ import annotations as _annotations import base64 import dataclasses as _dataclasses import re -from collections.abc import Hashable, Iterator from datetime import date, datetime from decimal import Decimal from enum import Enum from pathlib import Path -from re import Pattern from types import ModuleType from typing import ( TYPE_CHECKING, - Annotated, Any, Callable, ClassVar, + Dict, + FrozenSet, Generic, - Literal, + Hashable, + Iterator, + List, + Set, TypeVar, Union, cast, @@ -28,24 +29,26 @@ from uuid import UUID import annotated_types from annotated_types import BaseMetadata, MaxLen, MinLen -from pydantic_core import CoreSchema, PydanticCustomError, SchemaSerializer, core_schema -from typing_extensions import Protocol, TypeAlias, TypeAliasType, deprecated, get_args, get_origin -from typing_inspection.introspection import is_union_origin +from pydantic_core import CoreSchema, PydanticCustomError, core_schema +from typing_extensions import Annotated, Literal, Protocol, TypeAlias, TypeAliasType, deprecated -from ._internal import _fields, _internal_dataclass, _utils, _validators +from ._internal import ( + _core_utils, + _fields, + _internal_dataclass, + _typing_extra, + _utils, + _validators, +) from ._migration import getattr_migration from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler from .errors import PydanticUserError from .json_schema import JsonSchemaValue from .warnings import PydanticDeprecatedSince20 -if TYPE_CHECKING: - from ._internal._core_metadata import CoreMetadata - __all__ = ( 'Strict', 'StrictStr', - 'SocketPath', 'conbytes', 'conlist', 'conset', @@ -68,14 +71,10 @@ __all__ = ( 'UUID3', 'UUID4', 'UUID5', - 'UUID6', - 'UUID7', - 'UUID8', 'FilePath', 'DirectoryPath', 'NewPath', 'Json', - 'Secret', 'SecretStr', 'SecretBytes', 'StrictBool', @@ -105,28 +104,21 @@ __all__ = ( 'Tag', 'Discriminator', 'JsonValue', - 'OnErrorOmit', - 'FailFast', ) -T = TypeVar('T') - - @_dataclasses.dataclass class Strict(_fields.PydanticMetadata, BaseMetadata): - """!!! abstract "Usage Documentation" - [Strict Mode with `Annotated` `Strict`](../concepts/strict_mode.md#strict-mode-with-annotated-strict) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/strict_mode/#strict-mode-with-annotated-strict A field metadata class to indicate that a field should be validated in strict mode. - Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. Attributes: strict: Whether to validate the field in strict mode. Example: ```python - from typing import Annotated + from typing_extensions import Annotated from pydantic.types import Strict @@ -168,7 +160,7 @@ def conint( The reason is that `conint` returns a type, which doesn't play well with static analysis tools. === ":x: Don't do this" - ```python + ```py from pydantic import BaseModel, conint class Foo(BaseModel): @@ -176,8 +168,8 @@ def conint( ``` === ":white_check_mark: Do this" - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, Field @@ -198,7 +190,7 @@ def conint( Returns: The wrapped integer type. - ```python + ```py from pydantic import BaseModel, ValidationError, conint class ConstrainedExample(BaseModel): @@ -227,7 +219,7 @@ def conint( ``` """ # noqa: D212 - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ int, Strict(strict) if strict is not None else None, annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), @@ -238,7 +230,7 @@ def conint( PositiveInt = Annotated[int, annotated_types.Gt(0)] """An integer that must be greater than zero. -```python +```py from pydantic import BaseModel, PositiveInt, ValidationError class Model(BaseModel): @@ -269,7 +261,7 @@ except ValidationError as e: NegativeInt = Annotated[int, annotated_types.Lt(0)] """An integer that must be less than zero. -```python +```py from pydantic import BaseModel, NegativeInt, ValidationError class Model(BaseModel): @@ -300,7 +292,7 @@ except ValidationError as e: NonPositiveInt = Annotated[int, annotated_types.Le(0)] """An integer that must be less than or equal to zero. -```python +```py from pydantic import BaseModel, NonPositiveInt, ValidationError class Model(BaseModel): @@ -331,7 +323,7 @@ except ValidationError as e: NonNegativeInt = Annotated[int, annotated_types.Ge(0)] """An integer that must be greater than or equal to zero. -```python +```py from pydantic import BaseModel, NonNegativeInt, ValidationError class Model(BaseModel): @@ -362,7 +354,7 @@ except ValidationError as e: StrictInt = Annotated[int, Strict()] """An integer that must be validated in strict mode. -```python +```py from pydantic import BaseModel, StrictInt, ValidationError class StrictIntModel(BaseModel): @@ -385,22 +377,7 @@ except ValidationError as e: @_dataclasses.dataclass class AllowInfNan(_fields.PydanticMetadata): - """A field metadata class to indicate that a field should allow `-inf`, `inf`, and `nan`. - - Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. - - Attributes: - allow_inf_nan: Whether to allow `-inf`, `inf`, and `nan`. Defaults to `True`. - - Example: - ```python - from typing import Annotated - - from pydantic.types import AllowInfNan - - LaxFloat = Annotated[float, AllowInfNan()] - ``` - """ + """A field metadata class to indicate that a field should allow ``-inf``, ``inf``, and ``nan``.""" allow_inf_nan: bool = True @@ -429,7 +406,7 @@ def confloat( The reason is that `confloat` returns a type, which doesn't play well with static analysis tools. === ":x: Don't do this" - ```python + ```py from pydantic import BaseModel, confloat class Foo(BaseModel): @@ -437,8 +414,8 @@ def confloat( ``` === ":white_check_mark: Do this" - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, Field @@ -460,7 +437,7 @@ def confloat( Returns: The wrapped float type. - ```python + ```py from pydantic import BaseModel, ValidationError, confloat class ConstrainedExample(BaseModel): @@ -488,7 +465,7 @@ def confloat( ''' ``` """ # noqa: D212 - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ float, Strict(strict) if strict is not None else None, annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), @@ -500,7 +477,7 @@ def confloat( PositiveFloat = Annotated[float, annotated_types.Gt(0)] """A float that must be greater than zero. -```python +```py from pydantic import BaseModel, PositiveFloat, ValidationError class Model(BaseModel): @@ -531,7 +508,7 @@ except ValidationError as e: NegativeFloat = Annotated[float, annotated_types.Lt(0)] """A float that must be less than zero. -```python +```py from pydantic import BaseModel, NegativeFloat, ValidationError class Model(BaseModel): @@ -562,7 +539,7 @@ except ValidationError as e: NonPositiveFloat = Annotated[float, annotated_types.Le(0)] """A float that must be less than or equal to zero. -```python +```py from pydantic import BaseModel, NonPositiveFloat, ValidationError class Model(BaseModel): @@ -593,7 +570,7 @@ except ValidationError as e: NonNegativeFloat = Annotated[float, annotated_types.Ge(0)] """A float that must be greater than or equal to zero. -```python +```py from pydantic import BaseModel, NonNegativeFloat, ValidationError class Model(BaseModel): @@ -624,7 +601,7 @@ except ValidationError as e: StrictFloat = Annotated[float, Strict(True)] """A float that must be validated in strict mode. -```python +```py from pydantic import BaseModel, StrictFloat, ValidationError class StrictFloatModel(BaseModel): @@ -644,7 +621,7 @@ except ValidationError as e: FiniteFloat = Annotated[float, AllowInfNan(False)] """A float that must be finite (not ``-inf``, ``inf``, or ``nan``). -```python +```py from pydantic import BaseModel, FiniteFloat class Model(BaseModel): @@ -676,7 +653,7 @@ def conbytes( Returns: The wrapped bytes type. """ - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ bytes, Strict(strict) if strict is not None else None, annotated_types.Len(min_length or 0, max_length), @@ -692,29 +669,18 @@ StrictBytes = Annotated[bytes, Strict()] @_dataclasses.dataclass(frozen=True) class StringConstraints(annotated_types.GroupedMetadata): - """!!! abstract "Usage Documentation" - [String types](./standard_library_types.md#strings) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/fields/#string-constraints - A field metadata class to apply constraints to `str` types. - Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. + Apply constraints to `str` types. Attributes: - strip_whitespace: Whether to remove leading and trailing whitespace. + strip_whitespace: Whether to strip whitespace from the string. to_upper: Whether to convert the string to uppercase. to_lower: Whether to convert the string to lowercase. strict: Whether to validate the string in strict mode. min_length: The minimum length of the string. max_length: The maximum length of the string. pattern: A regex pattern that the string must match. - - Example: - ```python - from typing import Annotated - - from pydantic.types import StringConstraints - - ConstrainedStr = Annotated[str, StringConstraints(min_length=1, max_length=10)] - ``` """ strip_whitespace: bool | None = None @@ -723,7 +689,7 @@ class StringConstraints(annotated_types.GroupedMetadata): strict: bool | None = None min_length: int | None = None max_length: int | None = None - pattern: str | Pattern[str] | None = None + pattern: str | None = None def __iter__(self) -> Iterator[BaseMetadata]: if self.min_length is not None: @@ -731,7 +697,7 @@ class StringConstraints(annotated_types.GroupedMetadata): if self.max_length is not None: yield MaxLen(self.max_length) if self.strict is not None: - yield Strict(self.strict) + yield Strict() if ( self.strip_whitespace is not None or self.pattern is not None @@ -754,7 +720,7 @@ def constr( strict: bool | None = None, min_length: int | None = None, max_length: int | None = None, - pattern: str | Pattern[str] | None = None, + pattern: str | None = None, ) -> type[str]: """ !!! warning "Discouraged" @@ -767,7 +733,7 @@ def constr( The reason is that `constr` returns a type, which doesn't play well with static analysis tools. === ":x: Don't do this" - ```python + ```py from pydantic import BaseModel, constr class Foo(BaseModel): @@ -775,27 +741,23 @@ def constr( ``` === ":white_check_mark: Do this" - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, StringConstraints class Foo(BaseModel): - bar: Annotated[ - str, - StringConstraints( - strip_whitespace=True, to_upper=True, pattern=r'^[A-Z]+$' - ), - ] + bar: Annotated[str, StringConstraints(strip_whitespace=True, to_upper=True, pattern=r'^[A-Z]+$')] ``` A wrapper around `str` that allows for additional constraints. - ```python + ```py from pydantic import BaseModel, constr class Foo(BaseModel): - bar: constr(strip_whitespace=True, to_upper=True) + bar: constr(strip_whitespace=True, to_upper=True, pattern=r'^[A-Z]+$') + foo = Foo(bar=' hello ') print(foo) @@ -814,7 +776,7 @@ def constr( Returns: The wrapped string type. """ # noqa: D212 - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ str, StringConstraints( strip_whitespace=strip_whitespace, @@ -849,7 +811,7 @@ def conset( Returns: The wrapped set type. """ - return Annotated[set[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + return Annotated[Set[item_type], annotated_types.Len(min_length or 0, max_length)] def confrozenset( @@ -865,7 +827,7 @@ def confrozenset( Returns: The wrapped frozenset type. """ - return Annotated[frozenset[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + return Annotated[FrozenSet[item_type], annotated_types.Len(min_length or 0, max_length)] AnyItemType = TypeVar('AnyItemType') @@ -878,7 +840,7 @@ def conlist( max_length: int | None = None, unique_items: bool | None = None, ) -> type[list[AnyItemType]]: - """A wrapper around [`list`][] that adds validation. + """A wrapper around typing.List that adds validation. Args: item_type: The type of the items in the list. @@ -900,7 +862,7 @@ def conlist( ), code='removed-kwargs', ) - return Annotated[list[item_type], annotated_types.Len(min_length or 0, max_length)] # pyright: ignore[reportReturnType] + return Annotated[List[item_type], annotated_types.Len(min_length or 0, max_length)] # ~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT STRING TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -911,24 +873,31 @@ if TYPE_CHECKING: else: class ImportString: - """A type that can be used to import a Python object from a string. + """A type that can be used to import a type from a string. `ImportString` expects a string and loads the Python object importable at that dotted path. - Attributes of modules may be separated from the module by `:` or `.`, e.g. if `'math:cos'` is provided, - the resulting field value would be the function `cos`. If a `.` is used and both an attribute and submodule + Attributes of modules may be separated from the module by `:` or `.`, e.g. if `'math:cos'` was provided, + the resulting field value would be the function`cos`. If a `.` is used and both an attribute and submodule are present at the same path, the module will be preferred. On model instantiation, pointers will be evaluated and imported. There is some nuance to this behavior, demonstrated in the examples below. - ```python - import math + > A known limitation: setting a default value to a string + > won't result in validation (thus evaluation). This is actively + > being worked on. + + **Good behavior:** + ```py + from math import cos + + from pydantic import BaseModel, ImportString, ValidationError - from pydantic import BaseModel, Field, ImportString, ValidationError class ImportThings(BaseModel): obj: ImportString + # A string value will cause an automatic import my_cos = ImportThings(obj='math.cos') @@ -936,6 +905,7 @@ else: cos_of_0 = my_cos.obj(0) assert cos_of_0 == 1 + # A string whose value cannot be imported will raise an error try: ImportThings(obj='foo.bar') @@ -944,45 +914,28 @@ else: ''' 1 validation error for ImportThings obj - Invalid python path: No module named 'foo.bar' [type=import_error, input_value='foo.bar', input_type=str] + Invalid python path: No module named 'foo.bar' [type=import_error, input_value='foo.bar', input_type=str] ''' + # Actual python objects can be assigned as well - my_cos = ImportThings(obj=math.cos) + my_cos = ImportThings(obj=cos) my_cos_2 = ImportThings(obj='math.cos') - my_cos_3 = ImportThings(obj='math:cos') - assert my_cos == my_cos_2 == my_cos_3 - - # You can set default field value either as Python object: - class ImportThingsDefaultPyObj(BaseModel): - obj: ImportString = math.cos - - # or as a string value (but only if used with `validate_default=True`) - class ImportThingsDefaultString(BaseModel): - obj: ImportString = Field(default='math.cos', validate_default=True) - - my_cos_default1 = ImportThingsDefaultPyObj() - my_cos_default2 = ImportThingsDefaultString() - assert my_cos_default1.obj == my_cos_default2.obj == math.cos - - # note: this will not work! - class ImportThingsMissingValidateDefault(BaseModel): - obj: ImportString = 'math.cos' - - my_cos_default3 = ImportThingsMissingValidateDefault() - assert my_cos_default3.obj == 'math.cos' # just string, not evaluated + assert my_cos == my_cos_2 ``` Serializing an `ImportString` type to json is also possible. - ```python + ```py from pydantic import BaseModel, ImportString + class ImportThings(BaseModel): obj: ImportString + # Create an instance - m = ImportThings(obj='math.cos') + m = ImportThings(obj='math:cos') print(m) #> obj= print(m.model_dump_json()) @@ -1009,26 +962,14 @@ else: function=_validators.import_string, schema=handler(source), serialization=serializer ) - @classmethod - def __get_pydantic_json_schema__(cls, cs: CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: - return handler(core_schema.str_schema()) - @staticmethod def _serialize(v: Any) -> str: if isinstance(v, ModuleType): return v.__name__ elif hasattr(v, '__module__') and hasattr(v, '__name__'): return f'{v.__module__}.{v.__name__}' - # Handle special cases for sys.XXX streams - # if we see more of these, we should consider a more general solution - elif hasattr(v, 'name'): - if v.name == '': - return 'sys.stdout' - elif v.name == '': - return 'sys.stdin' - elif v.name == '': - return 'sys.stderr' - return v + else: + return v def __repr__(self) -> str: return 'ImportString' @@ -1060,7 +1001,7 @@ def condecimal( The reason is that `condecimal` returns a type, which doesn't play well with static analysis tools. === ":x: Don't do this" - ```python + ```py from pydantic import BaseModel, condecimal class Foo(BaseModel): @@ -1068,9 +1009,10 @@ def condecimal( ``` === ":white_check_mark: Do this" - ```python + ```py from decimal import Decimal - from typing import Annotated + + from typing_extensions import Annotated from pydantic import BaseModel, Field @@ -1091,7 +1033,7 @@ def condecimal( decimal_places: The number of decimal places. Defaults to `None`. allow_inf_nan: Whether to allow infinity and NaN. Defaults to `None`. - ```python + ```py from decimal import Decimal from pydantic import BaseModel, ValidationError, condecimal @@ -1121,7 +1063,7 @@ def condecimal( ''' ``` """ # noqa: D212 - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ Decimal, Strict(strict) if strict is not None else None, annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), @@ -1136,25 +1078,9 @@ def condecimal( @_dataclasses.dataclass(**_internal_dataclass.slots_true) class UuidVersion: - """A field metadata class to indicate a [UUID](https://docs.python.org/3/library/uuid.html) version. + """A field metadata class to indicate a [UUID](https://docs.python.org/3/library/uuid.html) version.""" - Use this class as an annotation via [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated), as seen below. - - Attributes: - uuid_version: The version of the UUID. Must be one of 1, 3, 4, 5, 6, 7 or 8. - - Example: - ```python - from typing import Annotated - from uuid import UUID - - from pydantic.types import UuidVersion - - UUID1 = Annotated[UUID, UuidVersion(1)] - ``` - """ - - uuid_version: Literal[1, 3, 4, 5, 6, 7, 8] + uuid_version: Literal[1, 3, 4, 5] def __get_pydantic_json_schema__( self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler @@ -1165,10 +1091,15 @@ class UuidVersion: return field_schema def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - schema = handler(source) - _check_annotated_type(schema['type'], 'uuid', self.__class__.__name__) - schema['version'] = self.uuid_version # type: ignore - return schema + if isinstance(self, source): + # used directly as a type + return core_schema.uuid_schema(version=self.uuid_version) + else: + # update existing schema with self.uuid_version + schema = handler(source) + _check_annotated_type(schema['type'], 'uuid', self.__class__.__name__) + schema['version'] = self.uuid_version # type: ignore + return schema def __hash__(self) -> int: return hash(type(self.uuid_version)) @@ -1177,7 +1108,7 @@ class UuidVersion: UUID1 = Annotated[UUID, UuidVersion(1)] """A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 1. -```python +```py import uuid from pydantic import UUID1, BaseModel @@ -1191,7 +1122,7 @@ Model(uuid1=uuid.uuid1()) UUID3 = Annotated[UUID, UuidVersion(3)] """A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 3. -```python +```py import uuid from pydantic import UUID3, BaseModel @@ -1205,7 +1136,7 @@ Model(uuid3=uuid.uuid3(uuid.NAMESPACE_DNS, 'pydantic.org')) UUID4 = Annotated[UUID, UuidVersion(4)] """A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 4. -```python +```py import uuid from pydantic import UUID4, BaseModel @@ -1219,7 +1150,7 @@ Model(uuid4=uuid.uuid4()) UUID5 = Annotated[UUID, UuidVersion(5)] """A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 5. -```python +```py import uuid from pydantic import UUID5, BaseModel @@ -1230,55 +1161,14 @@ class Model(BaseModel): Model(uuid5=uuid.uuid5(uuid.NAMESPACE_DNS, 'pydantic.org')) ``` """ -UUID6 = Annotated[UUID, UuidVersion(6)] -"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 6. -```python -import uuid - -from pydantic import UUID6, BaseModel - -class Model(BaseModel): - uuid6: UUID6 - -Model(uuid6=uuid.UUID('1efea953-c2d6-6790-aa0a-69db8c87df97')) -``` -""" -UUID7 = Annotated[UUID, UuidVersion(7)] -"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 7. - -```python -import uuid - -from pydantic import UUID7, BaseModel - -class Model(BaseModel): - uuid7: UUID7 - -Model(uuid7=uuid.UUID('0194fdcb-1c47-7a09-b52c-561154de0b4a')) -``` -""" -UUID8 = Annotated[UUID, UuidVersion(8)] -"""A [UUID](https://docs.python.org/3/library/uuid.html) that must be version 8. - -```python -import uuid - -from pydantic import UUID8, BaseModel - -class Model(BaseModel): - uuid8: UUID8 - -Model(uuid8=uuid.UUID('81a0b92e-6078-8551-9c81-8ccb666bdab8')) -``` -""" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PATH TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @_dataclasses.dataclass class PathType: - path_type: Literal['file', 'dir', 'new', 'socket'] + path_type: Literal['file', 'dir', 'new'] def __get_pydantic_json_schema__( self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler @@ -1293,7 +1183,6 @@ class PathType: 'file': cast(core_schema.WithInfoValidatorFunction, self.validate_file), 'dir': cast(core_schema.WithInfoValidatorFunction, self.validate_directory), 'new': cast(core_schema.WithInfoValidatorFunction, self.validate_new), - 'socket': cast(core_schema.WithInfoValidatorFunction, self.validate_socket), } return core_schema.with_info_after_validator_function( @@ -1308,13 +1197,6 @@ class PathType: else: raise PydanticCustomError('path_not_file', 'Path does not point to a file') - @staticmethod - def validate_socket(path: Path, _: core_schema.ValidationInfo) -> Path: - if path.is_socket(): - return path - else: - raise PydanticCustomError('path_not_socket', 'Path does not point to a socket') - @staticmethod def validate_directory(path: Path, _: core_schema.ValidationInfo) -> Path: if path.is_dir(): @@ -1338,7 +1220,7 @@ class PathType: FilePath = Annotated[Path, PathType('file')] """A path that must point to a file. -```python +```py from pathlib import Path from pydantic import BaseModel, FilePath, ValidationError @@ -1380,7 +1262,7 @@ except ValidationError as e: DirectoryPath = Annotated[Path, PathType('dir')] """A path that must point to a directory. -```python +```py from pathlib import Path from pydantic import BaseModel, DirectoryPath, ValidationError @@ -1420,16 +1302,13 @@ except ValidationError as e: ``` """ NewPath = Annotated[Path, PathType('new')] -"""A path for a new file or directory that must not already exist. The parent directory must already exist.""" +"""A path for a new file or directory that must not already exist.""" -SocketPath = Annotated[Path, PathType('socket')] -"""A path to an existing socket file""" # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if TYPE_CHECKING: - # Json[list[str]] will be recognized by type checkers as list[str] - Json = Annotated[AnyType, ...] + Json = Annotated[AnyType, ...] # Json[list[str]] will be recognized by type checkers as list[str] else: @@ -1439,16 +1318,19 @@ else: You can use the `Json` data type to make Pydantic first load a raw JSON string before validating the loaded data into the parametrized type: - ```python - from typing import Any + ```py + from typing import Any, List from pydantic import BaseModel, Json, ValidationError + class AnyJsonModel(BaseModel): json_obj: Json[Any] + class ConstrainedJsonModel(BaseModel): - json_obj: Json[list[int]] + json_obj: Json[List[int]] + print(AnyJsonModel(json_obj='{"b": 1}')) #> json_obj={'b': 1} @@ -1462,7 +1344,7 @@ else: ''' 1 validation error for ConstrainedJsonModel json_obj - JSON input should be string, bytes or bytearray [type=json_type, input_value=12, input_type=int] + JSON input should be string, bytes or bytearray [type=json_type, input_value=12, input_type=int] ''' try: @@ -1472,7 +1354,7 @@ else: ''' 1 validation error for ConstrainedJsonModel json_obj - Invalid JSON: expected value at line 1 column 2 [type=json_invalid, input_value='[a, b]', input_type=str] + Invalid JSON: expected value at line 1 column 2 [type=json_invalid, input_value='[a, b]', input_type=str] ''' try: @@ -1482,20 +1364,24 @@ else: ''' 2 validation errors for ConstrainedJsonModel json_obj.0 - Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] json_obj.1 - Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='b', input_type=str] + Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='b', input_type=str] ''' ``` When you dump the model using `model_dump` or `model_dump_json`, the dumped value will be the result of validation, not the original JSON string. However, you can use the argument `round_trip=True` to get the original JSON string back: - ```python + ```py + from typing import List + from pydantic import BaseModel, Json + class ConstrainedJsonModel(BaseModel): - json_obj: Json[list[int]] + json_obj: Json[List[int]] + print(ConstrainedJsonModel(json_obj='[1, 2, 3]').model_dump_json()) #> {"json_obj":[1,2,3]} @@ -1524,16 +1410,15 @@ else: return hash(type(self)) def __eq__(self, other: Any) -> bool: - return type(other) is type(self) + return type(other) == type(self) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SECRET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# The `Secret` class being conceptually immutable, make the type variable covariant: -SecretType = TypeVar('SecretType', covariant=True) +SecretType = TypeVar('SecretType', str, bytes) -class _SecretBase(Generic[SecretType]): +class _SecretField(Generic[SecretType]): def __init__(self, secret_value: SecretType) -> None: self._secret_value: SecretType = secret_value @@ -1551,206 +1436,41 @@ class _SecretBase(Generic[SecretType]): def __hash__(self) -> int: return hash(self.get_secret_value()) + def __len__(self) -> int: + return len(self._secret_value) + def __str__(self) -> str: return str(self._display()) def __repr__(self) -> str: return f'{self.__class__.__name__}({self._display()!r})' - def _display(self) -> str | bytes: + def _display(self) -> SecretType: raise NotImplementedError - -def _serialize_secret(value: Secret[SecretType], info: core_schema.SerializationInfo) -> str | Secret[SecretType]: - if info.mode == 'json': - return str(value) - else: - return value - - -class Secret(_SecretBase[SecretType]): - """A generic base class used for defining a field with sensitive information that you do not want to be visible in logging or tracebacks. - - You may either directly parametrize `Secret` with a type, or subclass from `Secret` with a parametrized type. The benefit of subclassing - is that you can define a custom `_display` method, which will be used for `repr()` and `str()` methods. The examples below demonstrate both - ways of using `Secret` to create a new secret type. - - 1. Directly parametrizing `Secret` with a type: - - ```python - from pydantic import BaseModel, Secret - - SecretBool = Secret[bool] - - class Model(BaseModel): - secret_bool: SecretBool - - m = Model(secret_bool=True) - print(m.model_dump()) - #> {'secret_bool': Secret('**********')} - - print(m.model_dump_json()) - #> {"secret_bool":"**********"} - - print(m.secret_bool.get_secret_value()) - #> True - ``` - - 2. Subclassing from parametrized `Secret`: - - ```python - from datetime import date - - from pydantic import BaseModel, Secret - - class SecretDate(Secret[date]): - def _display(self) -> str: - return '****/**/**' - - class Model(BaseModel): - secret_date: SecretDate - - m = Model(secret_date=date(2022, 1, 1)) - print(m.model_dump()) - #> {'secret_date': SecretDate('****/**/**')} - - print(m.model_dump_json()) - #> {"secret_date":"****/**/**"} - - print(m.secret_date.get_secret_value()) - #> 2022-01-01 - ``` - - The value returned by the `_display` method will be used for `repr()` and `str()`. - - You can enforce constraints on the underlying type through annotations: - For example: - - ```python - from typing import Annotated - - from pydantic import BaseModel, Field, Secret, ValidationError - - SecretPosInt = Secret[Annotated[int, Field(gt=0, strict=True)]] - - class Model(BaseModel): - sensitive_int: SecretPosInt - - m = Model(sensitive_int=42) - print(m.model_dump()) - #> {'sensitive_int': Secret('**********')} - - try: - m = Model(sensitive_int=-42) # (1)! - except ValidationError as exc_info: - print(exc_info.errors(include_url=False, include_input=False)) - ''' - [ - { - 'type': 'greater_than', - 'loc': ('sensitive_int',), - 'msg': 'Input should be greater than 0', - 'ctx': {'gt': 0}, - } - ] - ''' - - try: - m = Model(sensitive_int='42') # (2)! - except ValidationError as exc_info: - print(exc_info.errors(include_url=False, include_input=False)) - ''' - [ - { - 'type': 'int_type', - 'loc': ('sensitive_int',), - 'msg': 'Input should be a valid integer', - } - ] - ''' - ``` - - 1. The input value is not greater than 0, so it raises a validation error. - 2. The input value is not an integer, so it raises a validation error because the `SecretPosInt` type has strict mode enabled. - """ - - def _display(self) -> str | bytes: - return '**********' if self.get_secret_value() else '' - @classmethod def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - inner_type = None - # if origin_type is Secret, then cls is a GenericAlias, and we can extract the inner type directly - origin_type = get_origin(source) - if origin_type is not None: - inner_type = get_args(source)[0] - # otherwise, we need to get the inner type from the base class + if issubclass(source, SecretStr): + field_type = str + inner_schema = core_schema.str_schema() else: - bases = getattr(cls, '__orig_bases__', getattr(cls, '__bases__', [])) - for base in bases: - if get_origin(base) is Secret: - inner_type = get_args(base)[0] - if bases == [] or inner_type is None: - raise TypeError( - f"Can't get secret type from {cls.__name__}. " - 'Please use Secret[], or subclass from Secret[] instead.' - ) + assert issubclass(source, SecretBytes) + field_type = bytes + inner_schema = core_schema.bytes_schema() + error_kind = 'string_type' if field_type is str else 'bytes_type' - inner_schema = handler.generate_schema(inner_type) # type: ignore + def serialize( + value: _SecretField[SecretType], info: core_schema.SerializationInfo + ) -> str | _SecretField[SecretType]: + if info.mode == 'json': + # we want the output to always be string without the `b'` prefix for bytes, + # hence we just use `secret_display` + return _secret_display(value.get_secret_value()) + else: + return value - def validate_secret_value(value, handler) -> Secret[SecretType]: - if isinstance(value, Secret): - value = value.get_secret_value() - validated_inner = handler(value) - return cls(validated_inner) - - return core_schema.json_or_python_schema( - python_schema=core_schema.no_info_wrap_validator_function( - validate_secret_value, - inner_schema, - ), - json_schema=core_schema.no_info_after_validator_function(lambda x: cls(x), inner_schema), - serialization=core_schema.plain_serializer_function_ser_schema( - _serialize_secret, - info_arg=True, - when_used='always', - ), - ) - - __pydantic_serializer__ = SchemaSerializer( - core_schema.any_schema( - serialization=core_schema.plain_serializer_function_ser_schema( - _serialize_secret, - info_arg=True, - when_used='always', - ) - ) - ) - - -def _secret_display(value: SecretType) -> str: # type: ignore - return '**********' if value else '' - - -def _serialize_secret_field( - value: _SecretField[SecretType], info: core_schema.SerializationInfo -) -> str | _SecretField[SecretType]: - if info.mode == 'json': - # we want the output to always be string without the `b'` prefix for bytes, - # hence we just use `secret_display` - return _secret_display(value.get_secret_value()) - else: - return value - - -class _SecretField(_SecretBase[SecretType]): - _inner_schema: ClassVar[CoreSchema] - _error_kind: ClassVar[str] - - @classmethod - def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: def get_json_schema(_core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler) -> JsonSchemaValue: - json_schema = handler(cls._inner_schema) + json_schema = handler(inner_schema) _utils.update_not_none( json_schema, type='string', @@ -1759,52 +1479,41 @@ class _SecretField(_SecretBase[SecretType]): ) return json_schema - def get_secret_schema(strict: bool) -> CoreSchema: - inner_schema = {**cls._inner_schema, 'strict': strict} - json_schema = core_schema.no_info_after_validator_function( - source, # construct the type - inner_schema, # pyright: ignore[reportArgumentType] - ) - return core_schema.json_or_python_schema( - python_schema=core_schema.union_schema( - [ - core_schema.is_instance_schema(source), - json_schema, - ], - custom_error_type=cls._error_kind, - ), - json_schema=json_schema, - serialization=core_schema.plain_serializer_function_ser_schema( - _serialize_secret_field, - info_arg=True, - when_used='always', - ), - ) - - return core_schema.lax_or_strict_schema( - lax_schema=get_secret_schema(strict=False), - strict_schema=get_secret_schema(strict=True), - metadata={'pydantic_js_functions': [get_json_schema]}, + json_schema = core_schema.no_info_after_validator_function( + source, # construct the type + inner_schema, ) - - __pydantic_serializer__ = SchemaSerializer( - core_schema.any_schema( + s = core_schema.json_or_python_schema( + python_schema=core_schema.union_schema( + [ + core_schema.is_instance_schema(source), + json_schema, + ], + strict=True, + custom_error_type=error_kind, + ), + json_schema=json_schema, serialization=core_schema.plain_serializer_function_ser_schema( - _serialize_secret_field, + serialize, info_arg=True, - when_used='always', - ) + return_schema=core_schema.str_schema(), + when_used='json', + ), ) - ) + s.setdefault('metadata', {}).setdefault('pydantic_js_functions', []).append(get_json_schema) + return s + + +def _secret_display(value: str | bytes) -> str: + return '**********' if value else '' class SecretStr(_SecretField[str]): """A string used for storing sensitive information that you do not want to be visible in logging or tracebacks. - When the secret value is nonempty, it is displayed as `'**********'` instead of the underlying value in - calls to `repr()` and `str()`. If the value _is_ empty, it is displayed as `''`. + It displays `'**********'` instead of the string value on `repr()` and `str()` calls. - ```python + ```py from pydantic import BaseModel, SecretStr class User(BaseModel): @@ -1817,62 +1526,19 @@ class SecretStr(_SecretField[str]): #> username='scolvin' password=SecretStr('**********') print(user.password.get_secret_value()) #> password1 - print((SecretStr('password'), SecretStr(''))) - #> (SecretStr('**********'), SecretStr('')) - ``` - - As seen above, by default, [`SecretStr`][pydantic.types.SecretStr] (and [`SecretBytes`][pydantic.types.SecretBytes]) - will be serialized as `**********` when serializing to json. - - You can use the [`field_serializer`][pydantic.functional_serializers.field_serializer] to dump the - secret as plain-text when serializing to json. - - ```python - from pydantic import BaseModel, SecretBytes, SecretStr, field_serializer - - class Model(BaseModel): - password: SecretStr - password_bytes: SecretBytes - - @field_serializer('password', 'password_bytes', when_used='json') - def dump_secret(self, v): - return v.get_secret_value() - - model = Model(password='IAmSensitive', password_bytes=b'IAmSensitiveBytes') - print(model) - #> password=SecretStr('**********') password_bytes=SecretBytes(b'**********') - print(model.password) - #> ********** - print(model.model_dump()) - ''' - { - 'password': SecretStr('**********'), - 'password_bytes': SecretBytes(b'**********'), - } - ''' - print(model.model_dump_json()) - #> {"password":"IAmSensitive","password_bytes":"IAmSensitiveBytes"} ``` """ - _inner_schema: ClassVar[CoreSchema] = core_schema.str_schema() - _error_kind: ClassVar[str] = 'string_type' - - def __len__(self) -> int: - return len(self._secret_value) - def _display(self) -> str: - return _secret_display(self._secret_value) + return _secret_display(self.get_secret_value()) class SecretBytes(_SecretField[bytes]): """A bytes used for storing sensitive information that you do not want to be visible in logging or tracebacks. It displays `b'**********'` instead of the string value on `repr()` and `str()` calls. - When the secret value is nonempty, it is displayed as `b'**********'` instead of the underlying value in - calls to `repr()` and `str()`. If the value _is_ empty, it is displayed as `b''`. - ```python + ```py from pydantic import BaseModel, SecretBytes class User(BaseModel): @@ -1883,19 +1549,11 @@ class SecretBytes(_SecretField[bytes]): #> username='scolvin' password=SecretBytes(b'**********') print(user.password.get_secret_value()) #> b'password1' - print((SecretBytes(b'password'), SecretBytes(b''))) - #> (SecretBytes(b'**********'), SecretBytes(b'')) ``` """ - _inner_schema: ClassVar[CoreSchema] = core_schema.bytes_schema() - _error_kind: ClassVar[str] = 'bytes_type' - - def __len__(self) -> int: - return len(self._secret_value) - def _display(self) -> bytes: - return _secret_display(self._secret_value).encode() + return _secret_display(self.get_secret_value()).encode() # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PAYMENT CARD TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1945,9 +1603,9 @@ class PaymentCardNumber(str): ) @classmethod - def validate(cls, input_value: str, /, _: core_schema.ValidationInfo) -> PaymentCardNumber: + def validate(cls, __input_value: str, _: core_schema.ValidationInfo) -> PaymentCardNumber: """Validate the card number and return a `PaymentCardNumber` instance.""" - return cls(input_value) + return cls(__input_value) @property def masked(self) -> str: @@ -2021,6 +1679,24 @@ class PaymentCardNumber(str): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ BYTE SIZE TYPE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BYTE_SIZES = { + 'b': 1, + 'kb': 10**3, + 'mb': 10**6, + 'gb': 10**9, + 'tb': 10**12, + 'pb': 10**15, + 'eb': 10**18, + 'kib': 2**10, + 'mib': 2**20, + 'gib': 2**30, + 'tib': 2**40, + 'pib': 2**50, + 'eib': 2**60, +} +BYTE_SIZES.update({k.lower()[0]: v for k, v in BYTE_SIZES.items() if 'i' not in k}) +byte_string_re = re.compile(r'^\s*(\d*\.?\d+)\s*(\w+)?', re.IGNORECASE) + class ByteSize(int): """Converts a string representing a number of bytes with units (such as `'1KB'` or `'11.5MiB'`) into an integer. @@ -2035,7 +1711,7 @@ class ByteSize(int): !!! info Note that `1b` will be parsed as "1 byte" and not "1 bit". - ```python + ```py from pydantic import BaseModel, ByteSize class MyModel(BaseModel): @@ -2051,72 +1727,24 @@ class ByteSize(int): #> 44.4PiB print(m.size.human_readable(decimal=True)) #> 50.0PB - print(m.size.human_readable(separator=' ')) - #> 44.4 PiB print(m.size.to('TiB')) #> 45474.73508864641 ``` """ - byte_sizes = { - 'b': 1, - 'kb': 10**3, - 'mb': 10**6, - 'gb': 10**9, - 'tb': 10**12, - 'pb': 10**15, - 'eb': 10**18, - 'kib': 2**10, - 'mib': 2**20, - 'gib': 2**30, - 'tib': 2**40, - 'pib': 2**50, - 'eib': 2**60, - 'bit': 1 / 8, - 'kbit': 10**3 / 8, - 'mbit': 10**6 / 8, - 'gbit': 10**9 / 8, - 'tbit': 10**12 / 8, - 'pbit': 10**15 / 8, - 'ebit': 10**18 / 8, - 'kibit': 2**10 / 8, - 'mibit': 2**20 / 8, - 'gibit': 2**30 / 8, - 'tibit': 2**40 / 8, - 'pibit': 2**50 / 8, - 'eibit': 2**60 / 8, - } - byte_sizes.update({k.lower()[0]: v for k, v in byte_sizes.items() if 'i' not in k}) - - byte_string_pattern = r'^\s*(\d*\.?\d+)\s*(\w+)?' - byte_string_re = re.compile(byte_string_pattern, re.IGNORECASE) - @classmethod def __get_pydantic_core_schema__(cls, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - return core_schema.with_info_after_validator_function( - function=cls._validate, - schema=core_schema.union_schema( - [ - core_schema.str_schema(pattern=cls.byte_string_pattern), - core_schema.int_schema(ge=0), - ], - custom_error_type='byte_size', - custom_error_message='could not parse value and unit from byte string', - ), - serialization=core_schema.plain_serializer_function_ser_schema( - int, return_schema=core_schema.int_schema(ge=0) - ), - ) + return core_schema.with_info_plain_validator_function(cls._validate) @classmethod - def _validate(cls, input_value: Any, /, _: core_schema.ValidationInfo) -> ByteSize: + def _validate(cls, __input_value: Any, _: core_schema.ValidationInfo) -> ByteSize: try: - return cls(int(input_value)) + return cls(int(__input_value)) except ValueError: pass - str_match = cls.byte_string_re.match(str(input_value)) + str_match = byte_string_re.match(str(__input_value)) if str_match is None: raise PydanticCustomError('byte_size', 'could not parse value and unit from byte string') @@ -2125,19 +1753,18 @@ class ByteSize(int): unit = 'b' try: - unit_mult = cls.byte_sizes[unit.lower()] + unit_mult = BYTE_SIZES[unit.lower()] except KeyError: raise PydanticCustomError('byte_size_unit', 'could not interpret byte unit: {unit}', {'unit': unit}) return cls(int(float(scalar) * unit_mult)) - def human_readable(self, decimal: bool = False, separator: str = '') -> str: + def human_readable(self, decimal: bool = False) -> str: """Converts a byte size to a human readable string. Args: decimal: If True, use decimal units (e.g. 1000 bytes per KB). If False, use binary units (e.g. 1024 bytes per KiB). - separator: A string used to split the value and unit. Defaults to an empty string (''). Returns: A human readable string representation of the byte size. @@ -2155,27 +1782,25 @@ class ByteSize(int): for unit in units: if abs(num) < divisor: if unit == 'B': - return f'{num:0.0f}{separator}{unit}' + return f'{num:0.0f}{unit}' else: - return f'{num:0.1f}{separator}{unit}' + return f'{num:0.1f}{unit}' num /= divisor - return f'{num:0.1f}{separator}{final_unit}' + return f'{num:0.1f}{final_unit}' def to(self, unit: str) -> float: - """Converts a byte size to another unit, including both byte and bit units. + """Converts a byte size to another unit. Args: - unit: The unit to convert to. Must be one of the following: B, KB, MB, GB, TB, PB, EB, - KiB, MiB, GiB, TiB, PiB, EiB (byte units) and - bit, kbit, mbit, gbit, tbit, pbit, ebit, - kibit, mibit, gibit, tibit, pibit, eibit (bit units). + unit: The unit to convert to. Must be one of the following: B, KB, MB, GB, TB, PB, EiB, + KiB, MiB, GiB, TiB, PiB, EiB. Returns: The byte size in the new unit. """ try: - unit_div = self.byte_sizes[unit.lower()] + unit_div = BYTE_SIZES[unit.lower()] except KeyError: raise PydanticCustomError('byte_size_unit', 'Could not interpret byte unit: {unit}', {'unit': unit}) @@ -2187,7 +1812,7 @@ class ByteSize(int): def _check_annotated_type(annotated_type: str, expected_type: str, annotation: str) -> None: if annotated_type != expected_type: - raise PydanticUserError(f"'{annotation}' cannot annotate '{annotated_type}'.", code='invalid-annotated-type') + raise PydanticUserError(f"'{annotation}' cannot annotate '{annotated_type}'.", code='invalid_annotated_type') if TYPE_CHECKING: @@ -2254,7 +1879,7 @@ def condate( Returns: A date type with the specified constraints. """ - return Annotated[ # pyright: ignore[reportReturnType] + return Annotated[ date, Strict(strict) if strict is not None else None, annotated_types.Interval(gt=gt, ge=ge, lt=lt, le=le), @@ -2402,7 +2027,7 @@ class Base64Encoder(EncoderProtocol): The decoded data. """ try: - return base64.b64decode(data) + return base64.decodebytes(data) except ValueError as e: raise PydanticCustomError('base64_decode', "Base64 decoding error: '{error}'", {'error': str(e)}) @@ -2416,7 +2041,7 @@ class Base64Encoder(EncoderProtocol): Returns: The encoded data. """ - return base64.b64encode(value) + return base64.encodebytes(value) @classmethod def get_json_format(cls) -> Literal['base64']: @@ -2474,8 +2099,8 @@ class EncodedBytes: `EncodedBytes` needs an encoder that implements `EncoderProtocol` to operate. - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, EncodedBytes, EncoderProtocol, ValidationError @@ -2533,11 +2158,9 @@ class EncodedBytes: return field_schema def __get_pydantic_core_schema__(self, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - schema = handler(source) - _check_annotated_type(schema['type'], 'bytes', self.__class__.__name__) return core_schema.with_info_after_validator_function( function=self.decode, - schema=schema, + schema=core_schema.bytes_schema(), serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode), ) @@ -2568,13 +2191,13 @@ class EncodedBytes: @_dataclasses.dataclass(**_internal_dataclass.slots_true) -class EncodedStr: +class EncodedStr(EncodedBytes): """A str type that is encoded and decoded using the specified encoder. `EncodedStr` needs an encoder that implements `EncoderProtocol` to operate. - ```python - from typing import Annotated + ```py + from typing_extensions import Annotated from pydantic import BaseModel, EncodedStr, EncoderProtocol, ValidationError @@ -2622,25 +2245,14 @@ class EncodedStr: ``` """ - encoder: type[EncoderProtocol] - - def __get_pydantic_json_schema__( - self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler - ) -> JsonSchemaValue: - field_schema = handler(core_schema) - field_schema.update(type='string', format=self.encoder.get_json_format()) - return field_schema - def __get_pydantic_core_schema__(self, source: type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: - schema = handler(source) - _check_annotated_type(schema['type'], 'str', self.__class__.__name__) return core_schema.with_info_after_validator_function( function=self.decode_str, - schema=schema, + schema=super(EncodedStr, self).__get_pydantic_core_schema__(source=source, handler=handler), # noqa: UP008 serialization=core_schema.plain_serializer_function_ser_schema(function=self.encode_str), ) - def decode_str(self, data: str, _: core_schema.ValidationInfo) -> str: + def decode_str(self, data: bytes, _: core_schema.ValidationInfo) -> str: """Decode the data using the specified encoder. Args: @@ -2649,7 +2261,7 @@ class EncodedStr: Returns: The decoded data. """ - return self.encoder.decode(data.encode()).decode() + return data.decode() def encode_str(self, value: str) -> str: """Encode the data using the specified encoder. @@ -2660,7 +2272,7 @@ class EncodedStr: Returns: The encoded data. """ - return self.encoder.encode(value.encode()).decode() # noqa: UP008 + return super(EncodedStr, self).encode(value=value.encode()).decode() # noqa: UP008 def __hash__(self) -> int: return hash(self.encoder) @@ -2670,52 +2282,12 @@ Base64Bytes = Annotated[bytes, EncodedBytes(encoder=Base64Encoder)] """A bytes type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. Note: - Under the hood, `Base64Bytes` uses the standard library `base64.b64encode` and `base64.b64decode` functions. + Under the hood, `Base64Bytes` use standard library `base64.encodebytes` and `base64.decodebytes` functions. As a result, attempting to decode url-safe base64 data using the `Base64Bytes` type may fail or produce an incorrect decoding. -Warning: - In versions of Pydantic prior to v2.10, `Base64Bytes` used [`base64.encodebytes`][base64.encodebytes] - and [`base64.decodebytes`][base64.decodebytes] functions. According to the [base64 documentation](https://docs.python.org/3/library/base64.html), - these methods are considered legacy implementation, and thus, Pydantic v2.10+ now uses the modern - [`base64.b64encode`][base64.b64encode] and [`base64.b64decode`][base64.b64decode] functions. - - If you'd still like to use these legacy encoders / decoders, you can achieve this by creating a custom annotated type, - like follows: - - ```python - import base64 - from typing import Annotated, Literal - - from pydantic_core import PydanticCustomError - - from pydantic import EncodedBytes, EncoderProtocol - - class LegacyBase64Encoder(EncoderProtocol): - @classmethod - def decode(cls, data: bytes) -> bytes: - try: - return base64.decodebytes(data) - except ValueError as e: - raise PydanticCustomError( - 'base64_decode', - "Base64 decoding error: '{error}'", - {'error': str(e)}, - ) - - @classmethod - def encode(cls, value: bytes) -> bytes: - return base64.encodebytes(value) - - @classmethod - def get_json_format(cls) -> Literal['base64']: - return 'base64' - - LegacyBase64Bytes = Annotated[bytes, EncodedBytes(encoder=LegacyBase64Encoder)] - ``` - -```python +```py from pydantic import Base64Bytes, BaseModel, ValidationError class Model(BaseModel): @@ -2730,7 +2302,7 @@ print(m.base64_bytes) # Serialize into the base64 form print(m.model_dump()) -#> {'base64_bytes': b'VGhpcyBpcyB0aGUgd2F5'} +#> {'base64_bytes': b'VGhpcyBpcyB0aGUgd2F5\n'} # Validate base64 data try: @@ -2748,21 +2320,12 @@ Base64Str = Annotated[str, EncodedStr(encoder=Base64Encoder)] """A str type that is encoded and decoded using the standard (non-URL-safe) base64 encoder. Note: - Under the hood, `Base64Str` uses the standard library `base64.b64encode` and `base64.b64decode` functions. + Under the hood, `Base64Bytes` use standard library `base64.encodebytes` and `base64.decodebytes` functions. As a result, attempting to decode url-safe base64 data using the `Base64Str` type may fail or produce an incorrect decoding. -Warning: - In versions of Pydantic prior to v2.10, `Base64Str` used [`base64.encodebytes`][base64.encodebytes] - and [`base64.decodebytes`][base64.decodebytes] functions. According to the [base64 documentation](https://docs.python.org/3/library/base64.html), - these methods are considered legacy implementation, and thus, Pydantic v2.10+ now uses the modern - [`base64.b64encode`][base64.b64encode] and [`base64.b64decode`][base64.b64decode] functions. - - See the [`Base64Bytes`][pydantic.types.Base64Bytes] type for more information on how to - replicate the old behavior with the legacy encoders / decoders. - -```python +```py from pydantic import Base64Str, BaseModel, ValidationError class Model(BaseModel): @@ -2777,7 +2340,7 @@ print(m.base64_str) # Serialize into the base64 form print(m.model_dump()) -#> {'base64_str': 'VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y'} +#> {'base64_str': 'VGhlc2UgYXJlbid0IHRoZSBkcm9pZHMgeW91J3JlIGxvb2tpbmcgZm9y\n'} # Validate base64 data try: @@ -2801,7 +2364,7 @@ Note: As a result, the `Base64UrlBytes` type can be used to faithfully decode "vanilla" base64 data (using `'+'` and `'/'`). -```python +```py from pydantic import Base64UrlBytes, BaseModel class Model(BaseModel): @@ -2822,7 +2385,7 @@ Note: As a result, the `Base64UrlStr` type can be used to faithfully decode "vanilla" base64 data (using `'+'` and `'/'`). -```python +```py from pydantic import Base64UrlStr, BaseModel class Model(BaseModel): @@ -2841,8 +2404,7 @@ __getattr__ = getattr_migration(__name__) @_dataclasses.dataclass(**_internal_dataclass.slots_true) class GetPydanticSchema: - """!!! abstract "Usage Documentation" - [Using `GetPydanticSchema` to Reduce Boilerplate](../concepts/types.md#using-getpydanticschema-to-reduce-boilerplate) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/types/#using-getpydanticschema-to-reduce-boilerplate A convenience class for creating an annotation that provides pydantic custom type hooks. @@ -2851,7 +2413,9 @@ class GetPydanticSchema: For example, to have a field treated by type checkers as `int`, but by pydantic as `Any`, you can do: ```python - from typing import Annotated, Any + from typing import Any + + from typing_extensions import Annotated from pydantic import BaseModel, GetPydanticSchema @@ -2900,8 +2464,10 @@ class Tag: The primary role of the `Tag` here is to map the return value from the callable `Discriminator` function to the appropriate member of the `Union` in question. - ```python - from typing import Annotated, Any, Literal, Union + ```py + from typing import Any, Union + + from typing_extensions import Annotated, Literal from pydantic import BaseModel, Discriminator, Tag @@ -2966,15 +2532,15 @@ class Tag: def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: schema = handler(source_type) - metadata = cast('CoreMetadata', schema.setdefault('metadata', {})) - metadata['pydantic_internal_union_tag_key'] = self.tag + metadata = schema.setdefault('metadata', {}) + assert isinstance(metadata, dict) + metadata[_core_utils.TAGGED_UNION_TAG_KEY] = self.tag return schema @_dataclasses.dataclass(**_internal_dataclass.slots_true, frozen=True) class Discriminator: - """!!! abstract "Usage Documentation" - [Discriminated Unions with `Callable` `Discriminator`](../concepts/unions.md#discriminated-unions-with-callable-discriminator) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/unions/#discriminated-unions-with-callable-discriminator Provides a way to use a custom callable as the way to extract the value of a union discriminator. @@ -2987,8 +2553,10 @@ class Discriminator: Consider this example, which is much more performant with the use of `Discriminator` and thus a `TaggedUnion` than it would be as a normal `Union`. - ```python - from typing import Annotated, Any, Literal, Union + ```py + from typing import Any, Union + + from typing_extensions import Annotated, Literal from pydantic import BaseModel, Discriminator, Tag @@ -3051,7 +2619,7 @@ class Discriminator: A `str` discriminator must be the name of a field to discriminate against. """ custom_error_type: str | None = None - """Type to use in [custom errors](../errors/errors.md) replacing the standard discriminated union + """Type to use in [custom errors](../errors/errors.md#custom-errors) replacing the standard discriminated union validation errors. """ custom_error_message: str | None = None @@ -3060,7 +2628,8 @@ class Discriminator: """Context to use in custom errors.""" def __get_pydantic_core_schema__(self, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: - if not is_union_origin(get_origin(source_type)): + origin = _typing_extra.get_origin(source_type) + if not origin or not _typing_extra.origin_is_union(origin): raise TypeError(f'{type(self).__name__} must be used with a Union type, not {source_type}') if isinstance(self.discriminator, str): @@ -3069,11 +2638,9 @@ class Discriminator: return handler(Annotated[source_type, Field(discriminator=self.discriminator)]) else: original_schema = handler(source_type) - return self._convert_schema(original_schema, handler) + return self._convert_schema(original_schema) - def _convert_schema( - self, original_schema: core_schema.CoreSchema, handler: GetCoreSchemaHandler | None = None - ) -> core_schema.TaggedUnionSchema: + def _convert_schema(self, original_schema: core_schema.CoreSchema) -> core_schema.TaggedUnionSchema: if original_schema['type'] != 'union': # This likely indicates that the schema was a single-item union that was simplified. # In this case, we do the same thing we do in @@ -3082,31 +2649,20 @@ class Discriminator: original_schema = core_schema.union_schema([original_schema]) tagged_union_choices = {} - for choice in original_schema['choices']: + for i, choice in enumerate(original_schema['choices']): tag = None if isinstance(choice, tuple): choice, tag = choice - metadata = cast('CoreMetadata | None', choice.get('metadata')) + metadata = choice.get('metadata') if metadata is not None: - tag = metadata.get('pydantic_internal_union_tag_key') or tag + metadata_tag = metadata.get(_core_utils.TAGGED_UNION_TAG_KEY) + if metadata_tag is not None: + tag = metadata_tag if tag is None: - # `handler` is None when this method is called from `apply_discriminator()` (deferred discriminators) - if handler is not None and choice['type'] == 'definition-ref': - # If choice was built from a PEP 695 type alias, try to resolve the def: - try: - choice = handler.resolve_ref_schema(choice) - except LookupError: - pass - else: - metadata = cast('CoreMetadata | None', choice.get('metadata')) - if metadata is not None: - tag = metadata.get('pydantic_internal_union_tag_key') - - if tag is None: - raise PydanticUserError( - f'`Tag` not provided for choice {choice} used with `Discriminator`', - code='callable-discriminator-no-tag', - ) + raise PydanticUserError( + f'`Tag` not provided for choice {choice} used with `Discriminator`', + code='callable-discriminator-no-tag', + ) tagged_union_choices[tag] = choice # Have to do these verbose checks to ensure falsy values ('' and {}) don't get ignored @@ -3144,7 +2700,9 @@ def _get_type_name(x: Any) -> str: if type_ in _JSON_TYPES: return type_.__name__ - # Handle proper subclasses; note we don't need to handle None or bool here + # Handle proper subclasses; note we don't need to handle None here + if isinstance(x, bool): + return 'bool' if isinstance(x, int): return 'int' if isinstance(x, float): @@ -3170,30 +2728,30 @@ class _AllowAnyJson: if TYPE_CHECKING: # This seems to only be necessary for mypy JsonValue: TypeAlias = Union[ - list['JsonValue'], - dict[str, 'JsonValue'], + List['JsonValue'], + Dict[str, 'JsonValue'], str, - bool, int, float, + bool, None, ] """A `JsonValue` is used to represent a value that can be serialized to JSON. It may be one of: - * `list['JsonValue']` - * `dict[str, 'JsonValue']` + * `List['JsonValue']` + * `Dict[str, 'JsonValue']` * `str` - * `bool` * `int` * `float` + * `bool` * `None` The following example demonstrates how to use `JsonValue` to validate JSON data, and what kind of errors to expect when input data is not json serializable. - ```python + ```py import json from pydantic import BaseModel, JsonValue, ValidationError @@ -3226,12 +2784,12 @@ else: 'JsonValue', Annotated[ Union[ - Annotated[list['JsonValue'], Tag('list')], - Annotated[dict[str, 'JsonValue'], Tag('dict')], + Annotated[List['JsonValue'], Tag('list')], + Annotated[Dict[str, 'JsonValue'], Tag('dict')], Annotated[str, Tag('str')], - Annotated[bool, Tag('bool')], Annotated[int, Tag('int')], Annotated[float, Tag('float')], + Annotated[bool, Tag('bool')], Annotated[None, Tag('NoneType')], ], Discriminator( @@ -3242,54 +2800,3 @@ else: _AllowAnyJson, ], ) - - -class _OnErrorOmit: - @classmethod - def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema: - # there is no actual default value here but we use with_default_schema since it already has the on_error - # behavior implemented and it would be no more efficient to implement it on every other validator - # or as a standalone validator - return core_schema.with_default_schema(schema=handler(source_type), on_error='omit') - - -OnErrorOmit = Annotated[T, _OnErrorOmit] -""" -When used as an item in a list, the key type in a dict, optional values of a TypedDict, etc. -this annotation omits the item from the iteration if there is any error validating it. -That is, instead of a [`ValidationError`][pydantic_core.ValidationError] being propagated up and the entire iterable being discarded -any invalid items are discarded and the valid ones are returned. -""" - - -@_dataclasses.dataclass -class FailFast(_fields.PydanticMetadata, BaseMetadata): - """A `FailFast` annotation can be used to specify that validation should stop at the first error. - - This can be useful when you want to validate a large amount of data and you only need to know if it's valid or not. - - You might want to enable this setting if you want to validate your data faster (basically, if you use this, - validation will be more performant with the caveat that you get less information). - - ```python - from typing import Annotated - - from pydantic import BaseModel, FailFast, ValidationError - - class Model(BaseModel): - x: Annotated[list[int], FailFast()] - - # This will raise a single error for the first invalid value and stop validation - try: - obj = Model(x=[1, 2, 'a', 4, 5, 'b', 7, 8, 9, 'c']) - except ValidationError as e: - print(e) - ''' - 1 validation error for Model - x.2 - Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] - ''' - ``` - """ - - fail_fast: bool = True diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/typing.py b/Backend/venv/lib/python3.12/site-packages/pydantic/typing.py index 0bda22d0..f1b32ba2 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/typing.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/typing.py @@ -1,5 +1,4 @@ """`typing` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/utils.py index 8d1e2a81..1619d1db 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/utils.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/utils.py @@ -1,5 +1,4 @@ """The `utils` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__init__.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__init__.py index 4807865c..3bf1418f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__init__.py @@ -1,27 +1,24 @@ # flake8: noqa -import sys -import warnings - -from pydantic.v1 import dataclasses -from pydantic.v1.annotated_types import create_model_from_namedtuple, create_model_from_typeddict -from pydantic.v1.class_validators import root_validator, validator -from pydantic.v1.config import BaseConfig, ConfigDict, Extra -from pydantic.v1.decorator import validate_arguments -from pydantic.v1.env_settings import BaseSettings -from pydantic.v1.error_wrappers import ValidationError -from pydantic.v1.errors import * -from pydantic.v1.fields import Field, PrivateAttr, Required -from pydantic.v1.main import * -from pydantic.v1.networks import * -from pydantic.v1.parse import Protocol -from pydantic.v1.tools import * -from pydantic.v1.types import * -from pydantic.v1.version import VERSION, compiled +from . import dataclasses +from .annotated_types import create_model_from_namedtuple, create_model_from_typeddict +from .class_validators import root_validator, validator +from .config import BaseConfig, ConfigDict, Extra +from .decorator import validate_arguments +from .env_settings import BaseSettings +from .error_wrappers import ValidationError +from .errors import * +from .fields import Field, PrivateAttr, Required +from .main import * +from .networks import * +from .parse import Protocol +from .tools import * +from .types import * +from .version import VERSION, compiled __version__ = VERSION -# WARNING __all__ from pydantic.errors is not included here, it will be removed as an export here in v2 -# please use "from pydantic.v1.errors import ..." instead +# WARNING __all__ from .errors is not included here, it will be removed as an export here in v2 +# please use "from pydantic.errors import ..." instead __all__ = [ # annotated types utils 'create_model_from_namedtuple', @@ -132,11 +129,3 @@ __all__ = [ 'compiled', 'VERSION', ] - - -if sys.version_info >= (3, 14): - warnings.warn( - "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.", - UserWarning, - stacklevel=2, - ) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc index 627f82ce..5de0dd93 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc index 3f33e5c0..0c415a9c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc index 880dfda3..26b2a3b1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc index 6f85ab1a..e8449dc5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc index c3c89c7f..2dab448a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc index b885068f..12be935f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc index 0a00e5e4..a0fa7fd0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc index d3d089c1..f7d2c884 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc index f84dd772..29a12991 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc index 45ea48d6..addd73b5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc index b2640e75..f2cfa02d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc index c0129973..cf37dfa3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc index 313e3e4d..607356b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc index f3657c13..8011b63d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc index acc10381..a2fb00f3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc index 1984a1b3..9e5f1894 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc index 60c07fc7..f27b1b7a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc index 96d2b0b5..a43bb8a9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc index bbcc93de..1bfc7444 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc index a33c0897..5e11ef12 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc index 0da63916..158aeff8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc index 6c94d221..a7b3c9c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc index 39ae05a0..152c5db2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc index 9be57792..8550e64b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc index a3736e4f..54f37c91 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc index 2a4600b8..a98c06ea 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/_hypothesis_plugin.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/_hypothesis_plugin.py index b62234d5..0c529620 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/_hypothesis_plugin.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/_hypothesis_plugin.py @@ -35,7 +35,7 @@ import hypothesis.strategies as st import pydantic import pydantic.color import pydantic.types -from pydantic.v1.utils import lenient_issubclass +from pydantic.utils import lenient_issubclass # FilePath and DirectoryPath are explicitly unsupported, as we'd have to create # them on-disk, and that's unsafe in general without being told *where* to do so. diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/annotated_types.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/annotated_types.py index d9eaaafd..d333457f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/annotated_types.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/annotated_types.py @@ -1,9 +1,9 @@ import sys from typing import TYPE_CHECKING, Any, Dict, FrozenSet, NamedTuple, Type -from pydantic.v1.fields import Required -from pydantic.v1.main import BaseModel, create_model -from pydantic.v1.typing import is_typeddict, is_typeddict_special +from .fields import Required +from .main import BaseModel, create_model +from .typing import is_typeddict, is_typeddict_special if TYPE_CHECKING: from typing_extensions import TypedDict diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/class_validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/class_validators.py index 2f68fc86..71e66509 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/class_validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/class_validators.py @@ -5,12 +5,12 @@ from itertools import chain from types import FunctionType from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Type, Union, overload -from pydantic.v1.errors import ConfigError -from pydantic.v1.typing import AnyCallable -from pydantic.v1.utils import ROOT_KEY, in_ipython +from .errors import ConfigError +from .typing import AnyCallable +from .utils import ROOT_KEY, in_ipython if TYPE_CHECKING: - from pydantic.v1.typing import AnyClassMethod + from .typing import AnyClassMethod class Validator: @@ -36,9 +36,9 @@ class Validator: if TYPE_CHECKING: from inspect import Signature - from pydantic.v1.config import BaseConfig - from pydantic.v1.fields import ModelField - from pydantic.v1.types import ModelOrDc + from .config import BaseConfig + from .fields import ModelField + from .types import ModelOrDc ValidatorCallable = Callable[[Optional[ModelOrDc], Any, Dict[str, Any], ModelField, Type[BaseConfig]], Any] ValidatorsList = List[ValidatorCallable] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/color.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/color.py index b0bbf78f..6fdc9fb1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/color.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/color.py @@ -12,11 +12,11 @@ import re from colorsys import hls_to_rgb, rgb_to_hls from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union, cast -from pydantic.v1.errors import ColorError -from pydantic.v1.utils import Representation, almost_equal_floats +from .errors import ColorError +from .utils import Representation, almost_equal_floats if TYPE_CHECKING: - from pydantic.v1.typing import CallableGenerator, ReprArgs + from .typing import CallableGenerator, ReprArgs ColorTuple = Union[Tuple[int, int, int], Tuple[int, int, int, float]] ColorType = Union[ColorTuple, str] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/config.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/config.py index 18f7c999..a25973af 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/config.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/config.py @@ -4,15 +4,15 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, ForwardRef, Optional, Tup from typing_extensions import Literal, Protocol -from pydantic.v1.typing import AnyArgTCallable, AnyCallable -from pydantic.v1.utils import GetterDict -from pydantic.v1.version import compiled +from .typing import AnyArgTCallable, AnyCallable +from .utils import GetterDict +from .version import compiled if TYPE_CHECKING: from typing import overload - from pydantic.v1.fields import ModelField - from pydantic.v1.main import BaseModel + from .fields import ModelField + from .main import BaseModel ConfigType = Type['BaseConfig'] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/dataclasses.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/dataclasses.py index bd167029..86bad1e6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/dataclasses.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/dataclasses.py @@ -36,28 +36,21 @@ import dataclasses import sys from contextlib import contextmanager from functools import wraps - -try: - from functools import cached_property -except ImportError: - # cached_property available only for python3.8+ - pass - from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, Generator, Optional, Type, TypeVar, Union, overload from typing_extensions import dataclass_transform -from pydantic.v1.class_validators import gather_all_validators -from pydantic.v1.config import BaseConfig, ConfigDict, Extra, get_config -from pydantic.v1.error_wrappers import ValidationError -from pydantic.v1.errors import DataclassTypeError -from pydantic.v1.fields import Field, FieldInfo, Required, Undefined -from pydantic.v1.main import create_model, validate_model -from pydantic.v1.utils import ClassAttribute +from .class_validators import gather_all_validators +from .config import BaseConfig, ConfigDict, Extra, get_config +from .error_wrappers import ValidationError +from .errors import DataclassTypeError +from .fields import Field, FieldInfo, Required, Undefined +from .main import create_model, validate_model +from .utils import ClassAttribute if TYPE_CHECKING: - from pydantic.v1.main import BaseModel - from pydantic.v1.typing import CallableGenerator, NoArgAnyCallable + from .main import BaseModel + from .typing import CallableGenerator, NoArgAnyCallable DataclassT = TypeVar('DataclassT', bound='Dataclass') @@ -416,17 +409,6 @@ def create_pydantic_model_from_dataclass( return model -if sys.version_info >= (3, 8): - - def _is_field_cached_property(obj: 'Dataclass', k: str) -> bool: - return isinstance(getattr(type(obj), k, None), cached_property) - -else: - - def _is_field_cached_property(obj: 'Dataclass', k: str) -> bool: - return False - - def _dataclass_validate_values(self: 'Dataclass') -> None: # validation errors can occur if this function is called twice on an already initialised dataclass. # for example if Extra.forbid is enabled, it would consider __pydantic_initialised__ an invalid extra property @@ -435,13 +417,9 @@ def _dataclass_validate_values(self: 'Dataclass') -> None: if getattr(self, '__pydantic_has_field_info_default__', False): # We need to remove `FieldInfo` values since they are not valid as input # It's ok to do that because they are obviously the default values! - input_data = { - k: v - for k, v in self.__dict__.items() - if not (isinstance(v, FieldInfo) or _is_field_cached_property(self, k)) - } + input_data = {k: v for k, v in self.__dict__.items() if not isinstance(v, FieldInfo)} else: - input_data = {k: v for k, v in self.__dict__.items() if not _is_field_cached_property(self, k)} + input_data = self.__dict__ d, _, validation_error = validate_model(self.__pydantic_model__, input_data, cls=self.__class__) if validation_error: raise validation_error diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/datetime_parse.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/datetime_parse.py index a7598fc6..cfd54593 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/datetime_parse.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/datetime_parse.py @@ -18,7 +18,7 @@ import re from datetime import date, datetime, time, timedelta, timezone from typing import Dict, Optional, Type, Union -from pydantic.v1 import errors +from . import errors date_expr = r'(?P\d{4})-(?P\d{1,2})-(?P\d{1,2})' time_expr = ( diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/decorator.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/decorator.py index 2c7c2c2f..089aab65 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/decorator.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/decorator.py @@ -1,17 +1,17 @@ from functools import wraps from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, Type, TypeVar, Union, overload -from pydantic.v1 import validator -from pydantic.v1.config import Extra -from pydantic.v1.errors import ConfigError -from pydantic.v1.main import BaseModel, create_model -from pydantic.v1.typing import get_all_type_hints -from pydantic.v1.utils import to_camel +from . import validator +from .config import Extra +from .errors import ConfigError +from .main import BaseModel, create_model +from .typing import get_all_type_hints +from .utils import to_camel __all__ = ('validate_arguments',) if TYPE_CHECKING: - from pydantic.v1.typing import AnyCallable + from .typing import AnyCallable AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable) ConfigType = Union[None, Type[Any], Dict[str, Any]] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/env_settings.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/env_settings.py index 5f6f2175..6c446e51 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/env_settings.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/env_settings.py @@ -3,12 +3,12 @@ import warnings from pathlib import Path from typing import AbstractSet, Any, Callable, ClassVar, Dict, List, Mapping, Optional, Tuple, Type, Union -from pydantic.v1.config import BaseConfig, Extra -from pydantic.v1.fields import ModelField -from pydantic.v1.main import BaseModel -from pydantic.v1.types import JsonWrapper -from pydantic.v1.typing import StrPath, display_as_type, get_origin, is_union -from pydantic.v1.utils import deep_update, lenient_issubclass, path_type, sequence_like +from .config import BaseConfig, Extra +from .fields import ModelField +from .main import BaseModel +from .types import JsonWrapper +from .typing import StrPath, display_as_type, get_origin, is_union +from .utils import deep_update, lenient_issubclass, path_type, sequence_like env_file_sentinel = str(object()) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/error_wrappers.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/error_wrappers.py index bc7f2631..5d3204f4 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/error_wrappers.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/error_wrappers.py @@ -1,15 +1,15 @@ import json from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union -from pydantic.v1.json import pydantic_encoder -from pydantic.v1.utils import Representation +from .json import pydantic_encoder +from .utils import Representation if TYPE_CHECKING: from typing_extensions import TypedDict - from pydantic.v1.config import BaseConfig - from pydantic.v1.types import ModelOrDc - from pydantic.v1.typing import ReprArgs + from .config import BaseConfig + from .types import ModelOrDc + from .typing import ReprArgs Loc = Tuple[Union[int, str], ...] @@ -101,6 +101,7 @@ def flatten_errors( ) -> Generator['ErrorDict', None, None]: for error in errors: if isinstance(error, ErrorWrapper): + if loc: error_loc = loc + error.loc_tuple() else: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/errors.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/errors.py index 6e864425..7bdafdd1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/errors.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/errors.py @@ -2,12 +2,12 @@ from decimal import Decimal from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Sequence, Set, Tuple, Type, Union -from pydantic.v1.typing import display_as_type +from .typing import display_as_type if TYPE_CHECKING: - from pydantic.v1.typing import DictStrAny + from .typing import DictStrAny -# explicitly state exports to avoid "from pydantic.v1.errors import *" also importing Decimal, Path etc. +# explicitly state exports to avoid "from .errors import *" also importing Decimal, Path etc. __all__ = ( 'PydanticTypeError', 'PydanticValueError', diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/fields.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/fields.py index 002b60cd..b1856c10 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/fields.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/fields.py @@ -28,12 +28,12 @@ from typing import ( from typing_extensions import Annotated, Final -from pydantic.v1 import errors as errors_ -from pydantic.v1.class_validators import Validator, make_generic_validator, prep_validators -from pydantic.v1.error_wrappers import ErrorWrapper -from pydantic.v1.errors import ConfigError, InvalidDiscriminator, MissingDiscriminator, NoneIsNotAllowedError -from pydantic.v1.types import Json, JsonWrapper -from pydantic.v1.typing import ( +from . import errors as errors_ +from .class_validators import Validator, make_generic_validator, prep_validators +from .error_wrappers import ErrorWrapper +from .errors import ConfigError, InvalidDiscriminator, MissingDiscriminator, NoneIsNotAllowedError +from .types import Json, JsonWrapper +from .typing import ( NoArgAnyCallable, convert_generics, display_as_type, @@ -48,7 +48,7 @@ from pydantic.v1.typing import ( is_union, new_type_supertype, ) -from pydantic.v1.utils import ( +from .utils import ( PyObjectStr, Representation, ValueItems, @@ -59,7 +59,7 @@ from pydantic.v1.utils import ( sequence_like, smart_deepcopy, ) -from pydantic.v1.validators import constant_validator, dict_validator, find_validators, validate_json +from .validators import constant_validator, dict_validator, find_validators, validate_json Required: Any = Ellipsis @@ -83,11 +83,11 @@ class UndefinedType: Undefined = UndefinedType() if TYPE_CHECKING: - from pydantic.v1.class_validators import ValidatorsList - from pydantic.v1.config import BaseConfig - from pydantic.v1.error_wrappers import ErrorList - from pydantic.v1.types import ModelOrDc - from pydantic.v1.typing import AbstractSetIntStr, MappingIntStrAny, ReprArgs + from .class_validators import ValidatorsList + from .config import BaseConfig + from .error_wrappers import ErrorList + from .types import ModelOrDc + from .typing import AbstractSetIntStr, MappingIntStrAny, ReprArgs ValidateReturn = Tuple[Optional[Any], Optional[ErrorList]] LocStr = Union[Tuple[Union[int, str], ...], str] @@ -178,6 +178,7 @@ class FieldInfo(Representation): self.extra = kwargs def __repr_args__(self) -> 'ReprArgs': + field_defaults_to_hide: Dict[str, Any] = { 'repr': True, **self.__field_constraints__, @@ -404,6 +405,7 @@ class ModelField(Representation): alias: Optional[str] = None, field_info: Optional[FieldInfo] = None, ) -> None: + self.name: str = name self.has_alias: bool = alias is not None self.alias: str = alias if alias is not None else name @@ -490,7 +492,7 @@ class ModelField(Representation): class_validators: Optional[Dict[str, Validator]], config: Type['BaseConfig'], ) -> 'ModelField': - from pydantic.v1.schema import get_annotation_from_field_info + from .schema import get_annotation_from_field_info field_info, value = cls._get_field_info(name, annotation, value, config) required: 'BoolUndefined' = Undefined @@ -850,6 +852,7 @@ class ModelField(Representation): def validate( self, v: Any, values: Dict[str, Any], *, loc: 'LocStr', cls: Optional['ModelOrDc'] = None ) -> 'ValidateReturn': + assert self.type_.__class__ is not DeferredType if self.type_.__class__ is ForwardRef: @@ -1160,7 +1163,7 @@ class ModelField(Representation): """ Whether the field is "complex" eg. env variables should be parsed as JSON. """ - from pydantic.v1.main import BaseModel + from .main import BaseModel return ( self.shape != SHAPE_SINGLETON diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/generics.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/generics.py index 9a69f2b3..a75b6b98 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/generics.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/generics.py @@ -22,12 +22,12 @@ from weakref import WeakKeyDictionary, WeakValueDictionary from typing_extensions import Annotated, Literal as ExtLiteral -from pydantic.v1.class_validators import gather_all_validators -from pydantic.v1.fields import DeferredType -from pydantic.v1.main import BaseModel, create_model -from pydantic.v1.types import JsonWrapper -from pydantic.v1.typing import display_as_type, get_all_type_hints, get_args, get_origin, typing_base -from pydantic.v1.utils import all_identical, lenient_issubclass +from .class_validators import gather_all_validators +from .fields import DeferredType +from .main import BaseModel, create_model +from .types import JsonWrapper +from .typing import display_as_type, get_all_type_hints, get_args, get_origin, typing_base +from .utils import all_identical, lenient_issubclass if sys.version_info >= (3, 10): from typing import _UnionGenericAlias diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/json.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/json.py index 41d0d5fc..b358b850 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/json.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/json.py @@ -9,9 +9,9 @@ from types import GeneratorType from typing import Any, Callable, Dict, Type, Union from uuid import UUID -from pydantic.v1.color import Color -from pydantic.v1.networks import NameEmail -from pydantic.v1.types import SecretBytes, SecretStr +from .color import Color +from .networks import NameEmail +from .types import SecretBytes, SecretStr __all__ = 'pydantic_encoder', 'custom_pydantic_encoder', 'timedelta_isoformat' @@ -72,7 +72,7 @@ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { def pydantic_encoder(obj: Any) -> Any: from dataclasses import asdict, is_dataclass - from pydantic.v1.main import BaseModel + from .main import BaseModel if isinstance(obj, BaseModel): return obj.dict() diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/main.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/main.py index 8000967e..683f3f88 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/main.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/main.py @@ -26,11 +26,11 @@ from typing import ( from typing_extensions import dataclass_transform -from pydantic.v1.class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators -from pydantic.v1.config import BaseConfig, Extra, inherit_config, prepare_config -from pydantic.v1.error_wrappers import ErrorWrapper, ValidationError -from pydantic.v1.errors import ConfigError, DictError, ExtraError, MissingError -from pydantic.v1.fields import ( +from .class_validators import ValidatorGroup, extract_root_validators, extract_validators, inherit_validators +from .config import BaseConfig, Extra, inherit_config, prepare_config +from .error_wrappers import ErrorWrapper, ValidationError +from .errors import ConfigError, DictError, ExtraError, MissingError +from .fields import ( MAPPING_LIKE_SHAPES, Field, ModelField, @@ -39,11 +39,11 @@ from pydantic.v1.fields import ( Undefined, is_finalvar_with_default_val, ) -from pydantic.v1.json import custom_pydantic_encoder, pydantic_encoder -from pydantic.v1.parse import Protocol, load_file, load_str_bytes -from pydantic.v1.schema import default_ref_template, model_schema -from pydantic.v1.types import PyObject, StrBytes -from pydantic.v1.typing import ( +from .json import custom_pydantic_encoder, pydantic_encoder +from .parse import Protocol, load_file, load_str_bytes +from .schema import default_ref_template, model_schema +from .types import PyObject, StrBytes +from .typing import ( AnyCallable, get_args, get_origin, @@ -53,7 +53,7 @@ from pydantic.v1.typing import ( resolve_annotations, update_model_forward_refs, ) -from pydantic.v1.utils import ( +from .utils import ( DUNDER_ATTRIBUTES, ROOT_KEY, ClassAttribute, @@ -73,9 +73,9 @@ from pydantic.v1.utils import ( if TYPE_CHECKING: from inspect import Signature - from pydantic.v1.class_validators import ValidatorListDict - from pydantic.v1.types import ModelOrDc - from pydantic.v1.typing import ( + from .class_validators import ValidatorListDict + from .types import ModelOrDc + from .typing import ( AbstractSetIntStr, AnyClassMethod, CallableGenerator, @@ -282,12 +282,6 @@ class ModelMetaclass(ABCMeta): cls = super().__new__(mcs, name, bases, new_namespace, **kwargs) # set __signature__ attr only for model class, but not for its instances cls.__signature__ = ClassAttribute('__signature__', generate_model_signature(cls.__init__, fields, config)) - - if not _is_base_model_class_defined: - # Cython does not understand the `if TYPE_CHECKING:` condition in the - # BaseModel's body (where annotations are set), so clear them manually: - getattr(cls, '__annotations__', {}).clear() - if resolve_forward_refs: cls.__try_update_forward_refs__() @@ -307,7 +301,7 @@ class ModelMetaclass(ABCMeta): See #3829 and python/cpython#92810 """ - return hasattr(instance, '__post_root_validators__') and super().__instancecheck__(instance) + return hasattr(instance, '__fields__') and super().__instancecheck__(instance) object_setattr = object.__setattr__ @@ -675,7 +669,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass): def schema_json( cls, *, by_alias: bool = True, ref_template: str = default_ref_template, **dumps_kwargs: Any ) -> str: - from pydantic.v1.json import pydantic_encoder + from .json import pydantic_encoder return cls.__config__.json_dumps( cls.schema(by_alias=by_alias, ref_template=ref_template), default=pydantic_encoder, **dumps_kwargs @@ -743,6 +737,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass): exclude_defaults: bool, exclude_none: bool, ) -> Any: + if isinstance(v, BaseModel): if to_dict: v_dict = v.dict( @@ -835,6 +830,7 @@ class BaseModel(Representation, metaclass=ModelMetaclass): exclude_defaults: bool = False, exclude_none: bool = False, ) -> 'TupleGenerator': + # Merge field set excludes with explicit exclude parameter with explicit overriding field set options. # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case. if exclude is not None or self.__exclude_fields__ is not None: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/mypy.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/mypy.py index 0a775692..1d6d5ae2 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/mypy.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/mypy.py @@ -57,7 +57,6 @@ from mypy.types import ( Type, TypeOfAny, TypeType, - TypeVarId, TypeVarType, UnionType, get_proper_type, @@ -66,7 +65,7 @@ from mypy.typevars import fill_typevars from mypy.util import get_unique_redefinition_name from mypy.version import __version__ as mypy_version -from pydantic.v1.utils import is_valid_field +from pydantic.utils import is_valid_field try: from mypy.types import TypeVarDef # type: ignore[attr-defined] @@ -209,14 +208,14 @@ class PydanticPlugin(Plugin): default_factory_type = default_factory_type.items()[0] # type: ignore[operator] if isinstance(default_factory_type, CallableType): - ret_type = get_proper_type(default_factory_type.ret_type) - if ( - isinstance(ret_type, Instance) - and ret_type.args - and all(isinstance(arg, TypeVarType) for arg in ret_type.args) - ): - # Looks like the default factory is a type like `list` or `dict`, replace all args with `Any` - ret_type = ret_type.copy_modified(args=[default_any_type] * len(ret_type.args)) + ret_type = default_factory_type.ret_type + # mypy doesn't think `ret_type` has `args`, you'd think mypy should know, + # add this check in case it varies by version + args = getattr(ret_type, 'args', None) + if args: + if all(isinstance(arg, TypeVarType) for arg in args): + # Looks like the default factory is a type like `list` or `dict`, replace all args with `Any` + ret_type.args = tuple(default_any_type for _ in args) # type: ignore[attr-defined] return ret_type return default_any_type @@ -499,11 +498,7 @@ class PydanticModelTransformer: tvd = TypeVarType( self_tvar_name, tvar_fullname, - ( - TypeVarId(-1, namespace=ctx.cls.fullname + '.construct') - if MYPY_VERSION_TUPLE >= (1, 11) - else TypeVarId(-1) - ), + -1, [], obj_type, AnyType(TypeOfAny.from_omitted_generics), # type: ignore[arg-type] @@ -863,9 +858,9 @@ def add_method( arg_kinds.append(arg.kind) function_type = ctx.api.named_type(f'{BUILTINS_NAME}.function') - signature = CallableType( - arg_types, arg_kinds, arg_names, return_type, function_type, variables=[tvar_def] if tvar_def else None - ) + signature = CallableType(arg_types, arg_kinds, arg_names, return_type, function_type) + if tvar_def: + signature.variables = [tvar_def] func = FuncDef(name, args, Block([PassStmt()])) func.info = info diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/networks.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/networks.py index ba07b748..cfebe588 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/networks.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/networks.py @@ -27,17 +27,17 @@ from typing import ( no_type_check, ) -from pydantic.v1 import errors -from pydantic.v1.utils import Representation, update_not_none -from pydantic.v1.validators import constr_length_validator, str_validator +from . import errors +from .utils import Representation, update_not_none +from .validators import constr_length_validator, str_validator if TYPE_CHECKING: import email_validator from typing_extensions import TypedDict - from pydantic.v1.config import BaseConfig - from pydantic.v1.fields import ModelField - from pydantic.v1.typing import AnyCallable + from .config import BaseConfig + from .fields import ModelField + from .typing import AnyCallable CallableGenerator = Generator[AnyCallable, None, None] diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/parse.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/parse.py index 431d75a6..7ac330ca 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/parse.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/parse.py @@ -4,7 +4,7 @@ from enum import Enum from pathlib import Path from typing import Any, Callable, Union -from pydantic.v1.types import StrBytes +from .types import StrBytes class Protocol(str, Enum): diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/schema.py index a91fe2cd..31e8ae37 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/schema.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/schema.py @@ -31,7 +31,7 @@ from uuid import UUID from typing_extensions import Annotated, Literal -from pydantic.v1.fields import ( +from .fields import ( MAPPING_LIKE_SHAPES, SHAPE_DEQUE, SHAPE_FROZENSET, @@ -46,9 +46,9 @@ from pydantic.v1.fields import ( FieldInfo, ModelField, ) -from pydantic.v1.json import pydantic_encoder -from pydantic.v1.networks import AnyUrl, EmailStr -from pydantic.v1.types import ( +from .json import pydantic_encoder +from .networks import AnyUrl, EmailStr +from .types import ( ConstrainedDecimal, ConstrainedFloat, ConstrainedFrozenSet, @@ -69,7 +69,7 @@ from pydantic.v1.types import ( conset, constr, ) -from pydantic.v1.typing import ( +from .typing import ( all_literal_values, get_args, get_origin, @@ -80,11 +80,11 @@ from pydantic.v1.typing import ( is_none_type, is_union, ) -from pydantic.v1.utils import ROOT_KEY, get_model, lenient_issubclass +from .utils import ROOT_KEY, get_model, lenient_issubclass if TYPE_CHECKING: - from pydantic.v1.dataclasses import Dataclass - from pydantic.v1.main import BaseModel + from .dataclasses import Dataclass + from .main import BaseModel default_prefix = '#/definitions/' default_ref_template = '#/definitions/{model}' @@ -198,6 +198,7 @@ def model_schema( def get_field_info_schema(field: ModelField, schema_overrides: bool = False) -> Tuple[Dict[str, Any], bool]: + # If no title is explicitly set, we don't set title in the schema for enums. # The behaviour is the same as `BaseModel` reference, where the default title # is in the definitions part of the schema. @@ -378,7 +379,7 @@ def get_flat_models_from_field(field: ModelField, known_models: TypeModelSet) -> :param known_models: used to solve circular references :return: a set with the model used in the declaration for this field, if any, and all its sub-models """ - from pydantic.v1.main import BaseModel + from .main import BaseModel flat_models: TypeModelSet = set() @@ -445,7 +446,7 @@ def field_type_schema( Take a single ``field`` and generate the schema for its type only, not including additional information as title, etc. Also return additional schema definitions, from sub-models. """ - from pydantic.v1.main import BaseModel # noqa: F811 + from .main import BaseModel # noqa: F811 definitions = {} nested_models: Set[str] = set() @@ -738,7 +739,7 @@ def field_singleton_sub_fields_schema( discriminator_models_refs[discriminator_value] = discriminator_model_ref['$ref'] s['discriminator'] = { - 'propertyName': field.discriminator_alias if by_alias else field.discriminator_key, + 'propertyName': field.discriminator_alias, 'mapping': discriminator_models_refs, } @@ -838,7 +839,7 @@ def field_singleton_schema( # noqa: C901 (ignore complexity) Take a single Pydantic ``ModelField``, and return its schema and any additional definitions from sub-models. """ - from pydantic.v1.main import BaseModel + from .main import BaseModel definitions: Dict[str, Any] = {} nested_models: Set[str] = set() @@ -974,7 +975,7 @@ def multitypes_literal_field_for_schema(values: Tuple[Any, ...], field: ModelFie def encode_default(dft: Any) -> Any: - from pydantic.v1.main import BaseModel + from .main import BaseModel if isinstance(dft, BaseModel) or is_dataclass(dft): dft = cast('dict[str, Any]', pydantic_encoder(dft)) @@ -1090,7 +1091,7 @@ def get_annotation_with_constraints(annotation: Any, field_info: FieldInfo) -> T if issubclass(type_, (SecretStr, SecretBytes)): attrs = ('max_length', 'min_length') - def constraint_func(**kw: Any) -> Type[Any]: # noqa: F811 + def constraint_func(**kw: Any) -> Type[Any]: return type(type_.__name__, (type_,), kw) elif issubclass(type_, str) and not issubclass(type_, (EmailStr, AnyUrl)): diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/tools.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/tools.py index 6838a23e..45be2770 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/tools.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/tools.py @@ -3,16 +3,16 @@ from functools import lru_cache from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Optional, Type, TypeVar, Union -from pydantic.v1.parse import Protocol, load_file, load_str_bytes -from pydantic.v1.types import StrBytes -from pydantic.v1.typing import display_as_type +from .parse import Protocol, load_file, load_str_bytes +from .types import StrBytes +from .typing import display_as_type __all__ = ('parse_file_as', 'parse_obj_as', 'parse_raw_as', 'schema_of', 'schema_json_of') NameFactory = Union[str, Callable[[Type[Any]], str]] if TYPE_CHECKING: - from pydantic.v1.typing import DictStrAny + from .typing import DictStrAny def _generate_parsing_type_name(type_: Any) -> str: @@ -21,7 +21,7 @@ def _generate_parsing_type_name(type_: Any) -> str: @lru_cache(maxsize=2048) def _get_parsing_type(type_: Any, *, type_name: Optional[NameFactory] = None) -> Any: - from pydantic.v1.main import create_model + from .main import create_model if type_name is None: type_name = _generate_parsing_type_name diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/types.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/types.py index e1840d99..5881e745 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/types.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/types.py @@ -28,10 +28,10 @@ from typing import ( from uuid import UUID from weakref import WeakSet -from pydantic.v1 import errors -from pydantic.v1.datetime_parse import parse_date -from pydantic.v1.utils import import_string, update_not_none -from pydantic.v1.validators import ( +from . import errors +from .datetime_parse import parse_date +from .utils import import_string, update_not_none +from .validators import ( bytes_validator, constr_length_validator, constr_lower, @@ -123,9 +123,9 @@ StrIntFloat = Union[str, int, float] if TYPE_CHECKING: from typing_extensions import Annotated - from pydantic.v1.dataclasses import Dataclass - from pydantic.v1.main import BaseModel - from pydantic.v1.typing import CallableGenerator + from .dataclasses import Dataclass + from .main import BaseModel + from .typing import CallableGenerator ModelOrDc = Type[Union[BaseModel, Dataclass]] @@ -481,7 +481,6 @@ else: # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SET TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # This types superclass should be Set[T], but cython chokes on that... class ConstrainedSet(set): # type: ignore # Needed for pydantic to detect that this is a set @@ -570,7 +569,6 @@ def confrozenset( # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LIST TYPES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # This types superclass should be List[T], but cython chokes on that... class ConstrainedList(list): # type: ignore # Needed for pydantic to detect that this is a list @@ -829,7 +827,7 @@ class JsonWrapper: class JsonMeta(type): def __getitem__(self, t: Type[Any]) -> Type[JsonWrapper]: if t is Any: - return Json # allow Json[Any] to replicate plain Json + return Json # allow Json[Any] to replecate plain Json return _registered(type('JsonWrapperValue', (JsonWrapper,), {'inner_type': t})) @@ -1096,6 +1094,7 @@ class ByteSize(int): @classmethod def validate(cls, v: StrIntFloat) -> 'ByteSize': + try: return cls(int(v)) except ValueError: @@ -1117,6 +1116,7 @@ class ByteSize(int): return cls(int(float(scalar) * unit_mult)) def human_readable(self, decimal: bool = False) -> str: + if decimal: divisor = 1000 units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] @@ -1135,6 +1135,7 @@ class ByteSize(int): return f'{num:0.1f}{final_unit}' def to(self, unit: str) -> float: + try: unit_div = BYTE_SIZES[unit.lower()] except KeyError: diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/typing.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/typing.py index 97411618..a690a053 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/typing.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/typing.py @@ -1,5 +1,3 @@ -import functools -import operator import sys import typing from collections.abc import Callable @@ -60,21 +58,12 @@ if sys.version_info < (3, 9): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._evaluate(globalns, localns) -elif sys.version_info < (3, 12, 4): +else: def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: # Even though it is the right signature for python 3.9, mypy complains with # `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast... - # Python 3.13/3.12.4+ made `recursive_guard` a kwarg, so name it explicitly to avoid: - # TypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard' - return cast(Any, type_)._evaluate(globalns, localns, recursive_guard=set()) - -else: - - def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: - # Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid - # warnings: - return cast(Any, type_)._evaluate(globalns, localns, type_params=(), recursive_guard=set()) + return cast(Any, type_)._evaluate(globalns, localns, set()) if sys.version_info < (3, 9): @@ -201,6 +190,9 @@ if sys.version_info < (3, 9): return tp else: + from typing import _UnionGenericAlias # type: ignore + + from typing_extensions import _AnnotatedAlias def convert_generics(tp: Type[Any]) -> Type[Any]: """ @@ -220,7 +212,7 @@ else: # typing.Annotated needs special treatment if origin is Annotated: - return Annotated[(convert_generics(args[0]), *args[1:])] # type: ignore + return _AnnotatedAlias(convert_generics(args[0]), args[1:]) # recursively replace `str` instances inside of `GenericAlias` with `ForwardRef(arg)` converted = tuple( @@ -234,7 +226,7 @@ else: return TypingGenericAlias(origin, converted) elif isinstance(tp, TypesUnionType): # recreate types.UnionType (PEP604, Python >= 3.10) - return functools.reduce(operator.or_, converted) # type: ignore + return _UnionGenericAlias(origin, converted) else: try: setattr(tp, '__args__', converted) @@ -264,7 +256,7 @@ StrPath = Union[str, PathLike] if TYPE_CHECKING: - from pydantic.v1.fields import ModelField + from .fields import ModelField TupleGenerator = Generator[Tuple[str, Any], None, None] DictStrAny = Dict[str, Any] @@ -405,10 +397,7 @@ def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Opti else: value = ForwardRef(value, is_argument=False) try: - if sys.version_info >= (3, 13): - value = _eval_type(value, base_globals, None, type_params=()) - else: - value = _eval_type(value, base_globals, None) + value = _eval_type(value, base_globals, None) except NameError: # this is ok, it can be fixed with update_forward_refs pass @@ -446,7 +435,7 @@ def is_namedtuple(type_: Type[Any]) -> bool: Check if a given class is a named tuple. It can be either a `typing.NamedTuple` or `collections.namedtuple` """ - from pydantic.v1.utils import lenient_issubclass + from .utils import lenient_issubclass return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields') @@ -456,7 +445,7 @@ def is_typeddict(type_: Type[Any]) -> bool: Check if a given class is a typed dict (from `typing` or `typing_extensions`) In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict) """ - from pydantic.v1.utils import lenient_issubclass + from .utils import lenient_issubclass return lenient_issubclass(type_, dict) and hasattr(type_, '__total__') diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/utils.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/utils.py index 02543fd1..4d0f68ed 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/utils.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/utils.py @@ -28,8 +28,8 @@ from typing import ( from typing_extensions import Annotated -from pydantic.v1.errors import ConfigError -from pydantic.v1.typing import ( +from .errors import ConfigError +from .typing import ( NoneType, WithArgsTypes, all_literal_values, @@ -39,17 +39,17 @@ from pydantic.v1.typing import ( is_literal_type, is_union, ) -from pydantic.v1.version import version_info +from .version import version_info if TYPE_CHECKING: from inspect import Signature from pathlib import Path - from pydantic.v1.config import BaseConfig - from pydantic.v1.dataclasses import Dataclass - from pydantic.v1.fields import ModelField - from pydantic.v1.main import BaseModel - from pydantic.v1.typing import AbstractSetIntStr, DictIntStrAny, IntStr, MappingIntStrAny, ReprArgs + from .config import BaseConfig + from .dataclasses import Dataclass + from .fields import ModelField + from .main import BaseModel + from .typing import AbstractSetIntStr, DictIntStrAny, IntStr, MappingIntStrAny, ReprArgs RichReprResult = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]] @@ -66,7 +66,6 @@ __all__ = ( 'almost_equal_floats', 'get_model', 'to_camel', - 'to_lower_camel', 'is_valid_field', 'smart_deepcopy', 'PyObjectStr', @@ -159,7 +158,7 @@ def sequence_like(v: Any) -> bool: return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) -def validate_field_name(bases: Iterable[Type[Any]], field_name: str) -> None: +def validate_field_name(bases: List[Type['BaseModel']], field_name: str) -> None: """ Ensure that the field's name does not shadow an existing attribute of the model. """ @@ -241,7 +240,7 @@ def generate_model_signature( """ from inspect import Parameter, Signature, signature - from pydantic.v1.config import Extra + from .config import Extra present_params = signature(init).parameters.values() merged_params: Dict[str, Parameter] = {} @@ -299,7 +298,7 @@ def generate_model_signature( def get_model(obj: Union[Type['BaseModel'], Type['Dataclass']]) -> Type['BaseModel']: - from pydantic.v1.main import BaseModel + from .main import BaseModel try: model_cls = obj.__pydantic_model__ # type: ignore @@ -708,8 +707,6 @@ DUNDER_ATTRIBUTES = { '__orig_bases__', '__orig_class__', '__qualname__', - '__firstlineno__', - '__static_attributes__', } diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/validators.py index c0940e81..549a235e 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/validators.py @@ -27,11 +27,10 @@ from typing import ( Union, ) from uuid import UUID -from warnings import warn -from pydantic.v1 import errors -from pydantic.v1.datetime_parse import parse_date, parse_datetime, parse_duration, parse_time -from pydantic.v1.typing import ( +from . import errors +from .datetime_parse import parse_date, parse_datetime, parse_duration, parse_time +from .typing import ( AnyCallable, all_literal_values, display_as_type, @@ -42,14 +41,14 @@ from pydantic.v1.typing import ( is_none_type, is_typeddict, ) -from pydantic.v1.utils import almost_equal_floats, lenient_issubclass, sequence_like +from .utils import almost_equal_floats, lenient_issubclass, sequence_like if TYPE_CHECKING: from typing_extensions import Literal, TypedDict - from pydantic.v1.config import BaseConfig - from pydantic.v1.fields import ModelField - from pydantic.v1.types import ConstrainedDecimal, ConstrainedFloat, ConstrainedInt + from .config import BaseConfig + from .fields import ModelField + from .types import ConstrainedDecimal, ConstrainedFloat, ConstrainedInt ConstrainedNumber = Union[ConstrainedDecimal, ConstrainedFloat, ConstrainedInt] AnyOrderedDict = OrderedDict[Any, Any] @@ -595,7 +594,7 @@ NamedTupleT = TypeVar('NamedTupleT', bound=NamedTuple) def make_namedtuple_validator( namedtuple_cls: Type[NamedTupleT], config: Type['BaseConfig'] ) -> Callable[[Tuple[Any, ...]], NamedTupleT]: - from pydantic.v1.annotated_types import create_model_from_namedtuple + from .annotated_types import create_model_from_namedtuple NamedTupleModel = create_model_from_namedtuple( namedtuple_cls, @@ -620,7 +619,7 @@ def make_namedtuple_validator( def make_typeddict_validator( typeddict_cls: Type['TypedDict'], config: Type['BaseConfig'] # type: ignore[valid-type] ) -> Callable[[Any], Dict[str, Any]]: - from pydantic.v1.annotated_types import create_model_from_typeddict + from .annotated_types import create_model_from_typeddict TypedDictModel = create_model_from_typeddict( typeddict_cls, @@ -699,7 +698,7 @@ _VALIDATORS: List[Tuple[Type[Any], List[Any]]] = [ def find_validators( # noqa: C901 (ignore complexity) type_: Type[Any], config: Type['BaseConfig'] ) -> Generator[AnyCallable, None, None]: - from pydantic.v1.dataclasses import is_builtin_dataclass, make_dataclass_validator + from .dataclasses import is_builtin_dataclass, make_dataclass_validator if type_ is Any or type_ is object: return @@ -763,6 +762,4 @@ def find_validators( # noqa: C901 (ignore complexity) if config.arbitrary_types_allowed: yield make_arbitrary_type_validator(type_) else: - if hasattr(type_, '__pydantic_core_schema__'): - warn(f'Mixing V1 and V2 models is not supported. `{type_.__name__}` is a V2 model.', UserWarning) raise RuntimeError(f'no validator found for {type_}, see `arbitrary_types_allowed` in Config') diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/version.py b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/version.py index c77cde12..462c4978 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/v1/version.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/v1/version.py @@ -1,6 +1,6 @@ __all__ = 'compiled', 'VERSION', 'version_info' -VERSION = '1.10.21' +VERSION = '1.10.13' try: import cython # type: ignore diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py b/Backend/venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py index fe4d9c9b..b33017c3 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py @@ -1,14 +1,9 @@ """Decorator for validating function calls.""" - from __future__ import annotations as _annotations -import inspect -from functools import partial -from types import BuiltinFunctionType -from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast, overload +from typing import TYPE_CHECKING, Any, Callable, TypeVar, overload -from ._internal import _generate_schema, _typing_extra, _validate_call -from .errors import PydanticUserError +from ._internal import _validate_call __all__ = ('validate_call',) @@ -18,99 +13,46 @@ if TYPE_CHECKING: AnyCallableT = TypeVar('AnyCallableT', bound=Callable[..., Any]) -_INVALID_TYPE_ERROR_CODE = 'validate-call-type' - - -def _check_function_type(function: object) -> None: - """Check if the input function is a supported type for `validate_call`.""" - if isinstance(function, _generate_schema.VALIDATE_CALL_SUPPORTED_TYPES): - try: - inspect.signature(cast(_generate_schema.ValidateCallSupportedTypes, function)) - except ValueError: - raise PydanticUserError( - f"Input function `{function}` doesn't have a valid signature", code=_INVALID_TYPE_ERROR_CODE - ) - - if isinstance(function, partial): - try: - assert not isinstance(partial.func, partial), 'Partial of partial' - _check_function_type(function.func) - except PydanticUserError as e: - raise PydanticUserError( - f'Partial of `{function.func}` is invalid because the type of `{function.func}` is not supported by `validate_call`', - code=_INVALID_TYPE_ERROR_CODE, - ) from e - - return - - if isinstance(function, BuiltinFunctionType): - raise PydanticUserError(f'Input built-in function `{function}` is not supported', code=_INVALID_TYPE_ERROR_CODE) - if isinstance(function, (classmethod, staticmethod, property)): - name = type(function).__name__ - raise PydanticUserError( - f'The `@{name}` decorator should be applied after `@validate_call` (put `@{name}` on top)', - code=_INVALID_TYPE_ERROR_CODE, - ) - - if inspect.isclass(function): - raise PydanticUserError( - f'Unable to validate {function}: `validate_call` should be applied to functions, not classes (put `@validate_call` on top of `__init__` or `__new__` instead)', - code=_INVALID_TYPE_ERROR_CODE, - ) - if callable(function): - raise PydanticUserError( - f'Unable to validate {function}: `validate_call` should be applied to functions, not instances or other callables. Use `validate_call` explicitly on `__call__` instead.', - code=_INVALID_TYPE_ERROR_CODE, - ) - - raise PydanticUserError( - f'Unable to validate {function}: `validate_call` should be applied to one of the following: function, method, partial, or lambda', - code=_INVALID_TYPE_ERROR_CODE, - ) - - @overload def validate_call( *, config: ConfigDict | None = None, validate_return: bool = False -) -> Callable[[AnyCallableT], AnyCallableT]: ... +) -> Callable[[AnyCallableT], AnyCallableT]: + ... @overload -def validate_call(func: AnyCallableT, /) -> AnyCallableT: ... +def validate_call(__func: AnyCallableT) -> AnyCallableT: + ... def validate_call( - func: AnyCallableT | None = None, - /, + __func: AnyCallableT | None = None, *, config: ConfigDict | None = None, validate_return: bool = False, ) -> AnyCallableT | Callable[[AnyCallableT], AnyCallableT]: - """!!! abstract "Usage Documentation" - [Validation Decorator](../concepts/validation_decorator.md) + """Usage docs: https://docs.pydantic.dev/2.5/concepts/validation_decorator/ Returns a decorated wrapper around the function that validates the arguments and, optionally, the return value. Usage may be either as a plain decorator `@validate_call` or with arguments `@validate_call(...)`. Args: - func: The function to be decorated. + __func: The function to be decorated. config: The configuration dictionary. validate_return: Whether to validate the return value. Returns: The decorated function. """ - parent_namespace = _typing_extra.parent_frame_namespace() def validate(function: AnyCallableT) -> AnyCallableT: - _check_function_type(function) - validate_call_wrapper = _validate_call.ValidateCallWrapper( - cast(_generate_schema.ValidateCallSupportedTypes, function), config, validate_return, parent_namespace - ) - return _validate_call.update_wrapper_attributes(function, validate_call_wrapper.__call__) # type: ignore + if isinstance(function, (classmethod, staticmethod)): + name = type(function).__name__ + raise TypeError(f'The `@{name}` decorator should be applied after `@validate_call` (put `@{name}` on top)') + return _validate_call.ValidateCallWrapper(function, config, validate_return) # type: ignore - if func is not None: - return validate(func) + if __func: + return validate(__func) else: return validate diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/validators.py b/Backend/venv/lib/python3.12/site-packages/pydantic/validators.py index 7921b04f..55b0339e 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/validators.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/validators.py @@ -1,5 +1,4 @@ """The `validators` module is a backport module from V1.""" - from ._migration import getattr_migration __getattr__ = getattr_migration(__name__) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/version.py b/Backend/venv/lib/python3.12/site-packages/pydantic/version.py index eaf2619a..1daa7e09 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/version.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/version.py @@ -1,25 +1,10 @@ """The `version` module holds the version information for Pydantic.""" - from __future__ import annotations as _annotations -import sys - -from pydantic_core import __version__ as __pydantic_core_version__ - __all__ = 'VERSION', 'version_info' -VERSION = '2.12.5' -"""The version of Pydantic. - -This version specifier is guaranteed to be compliant with the [specification], -introduced by [PEP 440]. - -[specification]: https://packaging.python.org/en/latest/specifications/version-specifiers/ -[PEP 440]: https://peps.python.org/pep-0440/ -""" - -# Keep this in sync with the version constraint in the `pyproject.toml` dependencies: -_COMPATIBLE_PYDANTIC_CORE_VERSION = '2.41.5' +VERSION = '2.5.0' +"""The version of Pydantic.""" def version_short() -> str: @@ -32,13 +17,16 @@ def version_short() -> str: def version_info() -> str: """Return complete version information for Pydantic and its dependencies.""" - import importlib.metadata import platform + import sys from pathlib import Path import pydantic_core._pydantic_core as pdc - from ._internal import _git as git + if sys.version_info >= (3, 8): + import importlib.metadata as importlib_metadata + else: + import importlib_metadata # get data about packages that are closely related to pydantic, use pydantic or often conflict with pydantic package_names = { @@ -52,62 +40,36 @@ def version_info() -> str: } related_packages = [] - for dist in importlib.metadata.distributions(): + for dist in importlib_metadata.distributions(): name = dist.metadata['Name'] if name in package_names: related_packages.append(f'{name}-{dist.version}') - pydantic_dir = Path(__file__).parents[1].resolve() - most_recent_commit = ( - git.git_revision(pydantic_dir) if git.is_git_repo(pydantic_dir) and git.have_git() else 'unknown' - ) - info = { 'pydantic version': VERSION, - 'pydantic-core version': __pydantic_core_version__, - 'pydantic-core build': getattr(pdc, 'build_info', None) or pdc.build_profile, # pyright: ignore[reportPrivateImportUsage] + 'pydantic-core version': pdc.__version__, + 'pydantic-core build': getattr(pdc, 'build_info', None) or pdc.build_profile, + 'install path': Path(__file__).resolve().parent, 'python version': sys.version, 'platform': platform.platform(), 'related packages': ' '.join(related_packages), - 'commit': most_recent_commit, } return '\n'.join('{:>30} {}'.format(k + ':', str(v).replace('\n', ' ')) for k, v in info.items()) -def check_pydantic_core_version() -> bool: - """Check that the installed `pydantic-core` dependency is compatible.""" - return __pydantic_core_version__ == _COMPATIBLE_PYDANTIC_CORE_VERSION +def parse_mypy_version(version: str) -> tuple[int, ...]: + """Parse mypy string version to tuple of ints. + This function is included here rather than the mypy plugin file because the mypy plugin file cannot be imported + outside a mypy run. -def _ensure_pydantic_core_version() -> None: # pragma: no cover - if not check_pydantic_core_version(): - raise_error = True - # Do not raise the error if pydantic is installed in editable mode (i.e. in development): - if sys.version_info >= (3, 13): # origin property added in 3.13 - from importlib.metadata import distribution - - dist = distribution('pydantic') - if getattr(getattr(dist.origin, 'dir_info', None), 'editable', False): - raise_error = False - - if raise_error: - raise SystemError( - f'The installed pydantic-core version ({__pydantic_core_version__}) is incompatible ' - f'with the current pydantic version, which requires {_COMPATIBLE_PYDANTIC_CORE_VERSION}. ' - "If you encounter this error, make sure that you haven't upgraded pydantic-core manually." - ) - - -def parse_mypy_version(version: str) -> tuple[int, int, int]: - """Parse `mypy` string version to a 3-tuple of ints. - - It parses normal version like `1.11.0` and extra info followed by a `+` sign - like `1.11.0+dev.d6d9d8cd4f27c52edac1f537e236ec48a01e54cb.dirty`. + It parses normal version like `0.930` and dev version + like `0.940+dev.04cac4b5d911c4f9529e6ce86a27b44f28846f5d.dirty`. Args: version: The mypy version string. Returns: - A triple of ints, e.g. `(1, 11, 0)`. + A tuple of ints. e.g. (0, 930). """ - return tuple(map(int, version.partition('+')[0].split('.'))) # pyright: ignore[reportReturnType] + return tuple(map(int, version.partition('+')[0].split('.'))) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic/warnings.py b/Backend/venv/lib/python3.12/site-packages/pydantic/warnings.py index 2e2dd83c..e6e61fc6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic/warnings.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic/warnings.py @@ -1,22 +1,9 @@ """Pydantic-specific warnings.""" - from __future__ import annotations as _annotations from .version import version_short -__all__ = ( - 'PydanticDeprecatedSince20', - 'PydanticDeprecatedSince26', - 'PydanticDeprecatedSince29', - 'PydanticDeprecatedSince210', - 'PydanticDeprecatedSince211', - 'PydanticDeprecatedSince212', - 'PydanticDeprecationWarning', - 'PydanticExperimentalWarning', - 'ArbitraryTypeWarning', - 'UnsupportedFieldAttributeWarning', - 'TypedDictExtraConfigWarning', -) +__all__ = 'PydanticDeprecatedSince20', 'PydanticDeprecationWarning' class PydanticDeprecationWarning(DeprecationWarning): @@ -60,63 +47,5 @@ class PydanticDeprecatedSince20(PydanticDeprecationWarning): super().__init__(message, *args, since=(2, 0), expected_removal=(3, 0)) -class PydanticDeprecatedSince26(PydanticDeprecationWarning): - """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.6.""" - - def __init__(self, message: str, *args: object) -> None: - super().__init__(message, *args, since=(2, 6), expected_removal=(3, 0)) - - -class PydanticDeprecatedSince29(PydanticDeprecationWarning): - """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.9.""" - - def __init__(self, message: str, *args: object) -> None: - super().__init__(message, *args, since=(2, 9), expected_removal=(3, 0)) - - -class PydanticDeprecatedSince210(PydanticDeprecationWarning): - """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.10.""" - - def __init__(self, message: str, *args: object) -> None: - super().__init__(message, *args, since=(2, 10), expected_removal=(3, 0)) - - -class PydanticDeprecatedSince211(PydanticDeprecationWarning): - """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.11.""" - - def __init__(self, message: str, *args: object) -> None: - super().__init__(message, *args, since=(2, 11), expected_removal=(3, 0)) - - -class PydanticDeprecatedSince212(PydanticDeprecationWarning): - """A specific `PydanticDeprecationWarning` subclass defining functionality deprecated since Pydantic 2.12.""" - - def __init__(self, message: str, *args: object) -> None: - super().__init__(message, *args, since=(2, 12), expected_removal=(3, 0)) - - class GenericBeforeBaseModelWarning(Warning): pass - - -class PydanticExperimentalWarning(Warning): - """A Pydantic specific experimental functionality warning. - - It is raised to warn users that the functionality may change or be removed in future versions of Pydantic. - """ - - -class CoreSchemaGenerationWarning(UserWarning): - """A warning raised during core schema generation.""" - - -class ArbitraryTypeWarning(CoreSchemaGenerationWarning): - """A warning raised when Pydantic fails to generate a core schema for an arbitrary type.""" - - -class UnsupportedFieldAttributeWarning(CoreSchemaGenerationWarning): - """A warning raised when a `Field()` attribute isn't supported in the context it is used.""" - - -class TypedDictExtraConfigWarning(CoreSchemaGenerationWarning): - """A warning raised when the [`extra`][pydantic.ConfigDict.extra] configuration is incompatible with the `closed` or `extra_items` specification.""" diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/METADATA similarity index 68% rename from Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/METADATA rename to Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/METADATA index 468d2a52..b1db0a54 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/METADATA +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/METADATA @@ -1,34 +1,31 @@ -Metadata-Version: 2.4 +Metadata-Version: 2.1 Name: pydantic_core -Version: 2.41.5 +Version: 2.14.1 Classifier: Development Status :: 3 - Alpha Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Python :: Implementation :: GraalPy Classifier: Programming Language :: Rust Classifier: Framework :: Pydantic Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS Classifier: Typing :: Typed -Requires-Dist: typing-extensions>=4.14.1 +Requires-Dist: typing-extensions >=4.6.0, !=4.7.0 License-File: LICENSE -Summary: Core functionality for Pydantic validation and serialization Home-Page: https://github.com/pydantic/pydantic-core -Author-email: Samuel Colvin , Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, David Montague , David Hewitt , Sydney Runkle , Victorien Plot -License-Expression: MIT -Requires-Python: >=3.9 +Author-email: Samuel Colvin +License: MIT +Requires-Python: >=3.7 Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM Project-URL: Homepage, https://github.com/pydantic/pydantic-core Project-URL: Funding, https://github.com/sponsors/samuelcolvin @@ -105,52 +102,35 @@ except ValidationError as e: ## Getting Started -### Prerequisites +You'll need rust stable [installed](https://rustup.rs/), or rust nightly if you want to generate accurate coverage. -You'll need: -1. **[Rust](https://rustup.rs/)** - Rust stable (or nightly for coverage) -2. **[uv](https://docs.astral.sh/uv/getting-started/installation/)** - Fast Python package manager (will install Python 3.9+ automatically) -3. **[git](https://git-scm.com/)** - For version control -4. **[make](https://www.gnu.org/software/make/)** - For running development commands (or use `nmake` on Windows) - -### Quick Start +With rust and python 3.7+ installed, compiling pydantic-core should be possible with roughly the following: ```bash -# Clone the repository (or from your fork) +# clone this repo or your fork git clone git@github.com:pydantic/pydantic-core.git cd pydantic-core - -# Install all dependencies using uv, setup pre-commit hooks, and build the development version +# create a new virtual env +python3 -m venv env +source env/bin/activate +# install dependencies and install pydantic-core make install ``` -Verify your installation by running: +That should be it, the example shown above should now run. -```bash -make -``` +You might find it useful to look at [`python/pydantic_core/_pydantic_core.pyi`](./python/pydantic_core/_pydantic_core.pyi) and +[`python/pydantic_core/core_schema.py`](./python/pydantic_core/core_schema.py) for more information on the python API, +beyond that, [`tests/`](./tests) provide a large number of examples of usage. -This runs a full development cycle: formatting, building, linting, and testing - -### Development Commands - -Run `make help` to see all available commands, or use these common ones: - -```bash -make build-dev # to build the package during development -make build-prod # to perform an optimised build for benchmarking -make test # to run the tests -make testcov # to run the tests and generate a coverage report -make lint # to run the linter -make format # to format python and rust code -make all # to run to run build-dev + format + lint + test -``` - -### Useful Resources - -* [`python/pydantic_core/_pydantic_core.pyi`](./python/pydantic_core/_pydantic_core.pyi) - Python API types -* [`python/pydantic_core/core_schema.py`](./python/pydantic_core/core_schema.py) - Core schema definitions -* [`tests/`](./tests) - Comprehensive usage examples +If you want to contribute to pydantic-core, you'll want to use some other make commands: +* `make build-dev` to build the package during development +* `make build-prod` to perform an optimised build for benchmarking +* `make test` to run the tests +* `make testcov` to run the tests and generate a coverage report +* `make lint` to run the linter +* `make format` to format python and rust code +* `make` to run `format build-dev lint test` ## Profiling diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/RECORD new file mode 100644 index 00000000..ce10a1c1 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/RECORD @@ -0,0 +1,12 @@ +pydantic_core-2.14.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pydantic_core-2.14.1.dist-info/METADATA,sha256=kcfzHdPYXbwcX-iXWpsKYrM9lMUj30dEvfX6h7fVOG8,6514 +pydantic_core-2.14.1.dist-info/RECORD,, +pydantic_core-2.14.1.dist-info/WHEEL,sha256=jAOrfkJsBGHW-KSv5AFDOItlU2AAZh8UbbXywEon8CY,129 +pydantic_core-2.14.1.dist-info/license_files/LICENSE,sha256=Kv3TDVS01itvSIprzBVG6E7FBh8T9CCcA9ASNIeDeVo,1080 +pydantic_core/__init__.py,sha256=H-OvjcLVZnP4v4DQ6CAXIGqXN_2bK3PIEIv1lcH5HaQ,4197 +pydantic_core/__pycache__/__init__.cpython-312.pyc,, +pydantic_core/__pycache__/core_schema.cpython-312.pyc,, +pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so,sha256=d0K9hX5Q2HHcJB9s2puX2Dy8986ngRHlnPVPDERNVdQ,5179648 +pydantic_core/_pydantic_core.pyi,sha256=PwmhpPKZ7QRXdk2uKAD1AekkdIY4gC9Yf8MT6eGKiXQ,32260 +pydantic_core/core_schema.py,sha256=7rHKegevzVE5dIhGzv6T1dWCRbppbrqDZnCE0tHSb7I,132810 +pydantic_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/WHEEL similarity index 79% rename from Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/WHEEL index ecc07619..054c3460 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/WHEEL @@ -1,4 +1,4 @@ Wheel-Version: 1.0 -Generator: maturin (1.9.6) +Generator: maturin (1.3.1) Root-Is-Purelib: false Tag: cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64 diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/license_files/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/licenses/LICENSE rename to Backend/venv/lib/python3.12/site-packages/pydantic_core-2.14.1.dist-info/license_files/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/RECORD deleted file mode 100644 index 24582880..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/RECORD +++ /dev/null @@ -1,12 +0,0 @@ -pydantic_core-2.41.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pydantic_core-2.41.5.dist-info/METADATA,sha256=Cfg7qjIC7D2piihKVq_fG6aZduSvcXJIiIflsrIFkak,7277 -pydantic_core-2.41.5.dist-info/RECORD,, -pydantic_core-2.41.5.dist-info/WHEEL,sha256=AUS7tHOBvWg1bDsPcHg1j3P_rKxqebEdeR--lIGHkyI,129 -pydantic_core-2.41.5.dist-info/licenses/LICENSE,sha256=Kv3TDVS01itvSIprzBVG6E7FBh8T9CCcA9ASNIeDeVo,1080 -pydantic_core/__init__.py,sha256=nK1ikrdSVK9gapcKrpv_blrp8LCAic1jrK-jkbYHlNI,5115 -pydantic_core/__pycache__/__init__.cpython-312.pyc,, -pydantic_core/__pycache__/core_schema.cpython-312.pyc,, -pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so,sha256=sfwayXRW_oTj75OQmcampiKiPl-_d6Q5yWOQqXdPcls,4883472 -pydantic_core/_pydantic_core.pyi,sha256=PqHb1BgvCM-TQfJLPFz323egWzU1_-niNSUSejYXoR8,44927 -pydantic_core/core_schema.py,sha256=u9yFC3LWhRM6DiUP7SY7M2kdzfOBNJLzwOMQAePUYAU,154730 -pydantic_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__init__.py b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__init__.py index d5facd16..5b2655c9 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__init__.py @@ -3,8 +3,6 @@ from __future__ import annotations import sys as _sys from typing import Any as _Any -from typing_extensions import Sentinel - from ._pydantic_core import ( ArgsKwargs, MultiHostUrl, @@ -27,6 +25,7 @@ from ._pydantic_core import ( from_json, to_json, to_jsonable_python, + validate_core_schema, ) from .core_schema import CoreConfig, CoreSchema, CoreSchemaType, ErrorType @@ -35,14 +34,13 @@ if _sys.version_info < (3, 11): else: from typing import NotRequired as _NotRequired -if _sys.version_info < (3, 12): +if _sys.version_info < (3, 9): from typing_extensions import TypedDict as _TypedDict else: from typing import TypedDict as _TypedDict __all__ = [ '__version__', - 'UNSET', 'CoreConfig', 'CoreSchema', 'CoreSchemaType', @@ -68,6 +66,7 @@ __all__ = [ 'to_json', 'from_json', 'to_jsonable_python', + 'validate_core_schema', ] @@ -90,16 +89,11 @@ class ErrorDetails(_TypedDict): Values which are required to render the error message, and could hence be useful in rendering custom error messages. Also useful for passing custom error data forward. """ - url: _NotRequired[str] - """ - The documentation URL giving information about the error. No URL is available if - a [`PydanticCustomError`][pydantic_core.PydanticCustomError] is used. - """ class InitErrorDetails(_TypedDict): type: str | PydanticCustomError - """The type of error that occurred, this should be a "slug" identifier that changes rarely or never.""" + """The type of error that occurred, this should a "slug" identifier that changes rarely or never.""" loc: _NotRequired[tuple[int | str, ...]] """Tuple of strings and ints identifying where in the schema the error occurred.""" input: _Any @@ -117,7 +111,7 @@ class ErrorTypeInfo(_TypedDict): """ type: ErrorType - """The type of error that occurred, this should be a "slug" identifier that changes rarely or never.""" + """The type of error that occurred, this should a "slug" identifier that changes rarely or never.""" message_template_python: str """String template to render a human readable error message from using context, when the input is Python.""" example_message_python: str @@ -143,29 +137,3 @@ class MultiHostHost(_TypedDict): """The host part of this host, or `None`.""" port: int | None """The port part of this host, or `None`.""" - - -MISSING = Sentinel('MISSING') -"""A singleton indicating a field value was not provided during validation. - -This singleton can be used a default value, as an alternative to `None` when it has -an explicit meaning. During serialization, any field with `MISSING` as a value is excluded -from the output. - -Example: - ```python - from pydantic import BaseModel - - from pydantic_core import MISSING - - - class Configuration(BaseModel): - timeout: int | None | MISSING = MISSING - - - # configuration defaults, stored somewhere else: - defaults = {'timeout': 200} - - conf = Configuration.model_validate({...}) - timeout = conf.timeout if timeout.timeout is not MISSING else defaults['timeout'] -""" diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc index 2fdad40d..9eabd605 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc index d0b8b947..22c9a16f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so b/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so index 1b4819c8..ac0e95c2 100755 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so and b/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.pyi b/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.pyi index 8ae631ab..b452d2f1 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.pyi +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.pyi @@ -1,12 +1,23 @@ -import datetime -from collections.abc import Mapping -from typing import Any, Callable, Generic, Literal, TypeVar, final +from __future__ import annotations -from _typeshed import SupportsAllComparisons -from typing_extensions import LiteralString, Self, TypeAlias +import datetime +import sys +from typing import Any, Callable, Generic, Optional, Type, TypeVar from pydantic_core import ErrorDetails, ErrorTypeInfo, InitErrorDetails, MultiHostHost -from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType, ExtraBehavior +from pydantic_core.core_schema import CoreConfig, CoreSchema, ErrorType + +if sys.version_info < (3, 8): + from typing_extensions import final +else: + from typing import final + +if sys.version_info < (3, 11): + from typing_extensions import Literal, LiteralString, Self, TypeAlias +else: + from typing import Literal, LiteralString, Self, TypeAlias + +from _typeshed import SupportsAllComparisons __all__ = [ '__version__', @@ -34,6 +45,7 @@ __all__ = [ 'to_jsonable_python', 'list_all_errors', 'TzInfo', + 'validate_core_schema', ] __version__: str build_profile: str @@ -61,7 +73,7 @@ class Some(Generic[_T]): Returns the value wrapped by `Some`. """ @classmethod - def __class_getitem__(cls, item: Any, /) -> type[Self]: ... + def __class_getitem__(cls, __item: Any) -> Type[Self]: ... @final class SchemaValidator: @@ -70,18 +82,14 @@ class SchemaValidator: `CombinedValidator` which may in turn own more `CombinedValidator`s which make up the full schema validator. """ - # note: pyo3 currently supports __new__, but not __init__, though we include __init__ stubs - # and docstrings here (and in the following classes) for documentation purposes - - def __init__(self, schema: CoreSchema, config: CoreConfig | None = None) -> None: - """Initializes the `SchemaValidator`. + def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None) -> Self: + """ + Create a new SchemaValidator. Arguments: - schema: The `CoreSchema` to use for validation. + schema: The [`CoreSchema`][pydantic_core.core_schema.CoreSchema] to use for validation. config: Optionally a [`CoreConfig`][pydantic_core.core_schema.CoreConfig] to configure validation. """ - - def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None) -> Self: ... @property def title(self) -> str: """ @@ -92,13 +100,9 @@ class SchemaValidator: input: Any, *, strict: bool | None = None, - extra: ExtraBehavior | None = None, from_attributes: bool | None = None, - context: Any | None = None, + context: 'dict[str, Any] | None' = None, self_instance: Any | None = None, - allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, ) -> Any: """ Validate a Python object against the schema and return the validated object. @@ -107,19 +111,12 @@ class SchemaValidator: input: The Python object to validate. strict: Whether to validate the object in strict mode. If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. - extra: Whether to ignore, allow, or forbid extra data during model validation. - If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. from_attributes: Whether to validate objects as inputs to models by extracting attributes. If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used. context: The context to use for validation, this is passed to functional validators as [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. self_instance: An instance of a model set attributes on from validation, this is used when running validation from the `__init__` method of a model. - allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences - and mappings are ignored. - `'trailing-strings'` means any final unfinished JSON string is included in the result. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Raises: ValidationError: If validation fails. @@ -133,12 +130,9 @@ class SchemaValidator: input: Any, *, strict: bool | None = None, - extra: ExtraBehavior | None = None, from_attributes: bool | None = None, - context: Any | None = None, + context: 'dict[str, Any] | None' = None, self_instance: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, ) -> bool: """ Similar to [`validate_python()`][pydantic_core.SchemaValidator.validate_python] but returns a boolean. @@ -154,12 +148,8 @@ class SchemaValidator: input: str | bytes | bytearray, *, strict: bool | None = None, - extra: ExtraBehavior | None = None, - context: Any | None = None, + context: 'dict[str, Any] | None' = None, self_instance: Any | None = None, - allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, ) -> Any: """ Validate JSON data directly against the schema and return the validated Python object. @@ -174,16 +164,9 @@ class SchemaValidator: input: The JSON data to validate. strict: Whether to validate the object in strict mode. If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. - extra: Whether to ignore, allow, or forbid extra data during model validation. - If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. context: The context to use for validation, this is passed to functional validators as [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. self_instance: An instance of a model set attributes on from validation. - allow_partial: Whether to allow partial validation; if `True` incomplete JSON will be parsed successfully - and errors in the last element of sequences and mappings are ignored. - `'trailing-strings'` means any final unfinished JSON string is included in the result. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Raises: ValidationError: If validation fails or if the JSON data is invalid. @@ -193,15 +176,7 @@ class SchemaValidator: The validated Python object. """ def validate_strings( - self, - input: _StringInput, - *, - strict: bool | None = None, - extra: ExtraBehavior | None = None, - context: Any | None = None, - allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, - by_alias: bool | None = None, - by_name: bool | None = None, + self, input: _StringInput, *, strict: bool | None = None, context: 'dict[str, Any] | None' = None ) -> Any: """ Validate a string against the schema and return the validated Python object. @@ -213,15 +188,8 @@ class SchemaValidator: input: The input as a string, or bytes/bytearray if `strict=False`. strict: Whether to validate the object in strict mode. If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. - extra: Whether to ignore, allow, or forbid extra data during model validation. - If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. context: The context to use for validation, this is passed to functional validators as [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. - allow_partial: Whether to allow partial validation; if `True` errors in the last element of sequences - and mappings are ignored. - `'trailing-strings'` means any final unfinished JSON string is included in the result. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Raises: ValidationError: If validation fails or if the JSON data is invalid. @@ -237,11 +205,8 @@ class SchemaValidator: field_value: Any, *, strict: bool | None = None, - extra: ExtraBehavior | None = None, from_attributes: bool | None = None, - context: Any | None = None, - by_alias: bool | None = None, - by_name: bool | None = None, + context: 'dict[str, Any] | None' = None, ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any] | None, set[str]]: """ Validate an assignment to a field on a model. @@ -252,14 +217,10 @@ class SchemaValidator: field_value: The value to assign to the field. strict: Whether to validate the object in strict mode. If `None`, the value of [`CoreConfig.strict`][pydantic_core.core_schema.CoreConfig] is used. - extra: Whether to ignore, allow, or forbid extra data during model validation. - If `None`, the value of [`CoreConfig.extra_fields_behavior`][pydantic_core.core_schema.CoreConfig] is used. from_attributes: Whether to validate objects as inputs to models by extracting attributes. If `None`, the value of [`CoreConfig.from_attributes`][pydantic_core.core_schema.CoreConfig] is used. context: The context to use for validation, this is passed to functional validators as [`info.context`][pydantic_core.core_schema.ValidationInfo.context]. - by_alias: Whether to use the field's alias when validating against the provided input data. - by_name: Whether to use the field's name when validating against the provided input data. Raises: ValidationError: If validation fails. @@ -286,9 +247,7 @@ class SchemaValidator: `None` if the schema has no default value, otherwise a [`Some`][pydantic_core.Some] containing the default. """ -# In reality, `bool` should be replaced by `Literal[True]` but mypy fails to correctly apply bidirectional type inference -# (e.g. when using `{'a': {'b': True}}`). -_IncEx: TypeAlias = set[int] | set[str] | Mapping[int, _IncEx | bool] | Mapping[str, _IncEx | bool] +_IncEx: TypeAlias = set[int] | set[str] | dict[int, _IncEx] | dict[str, _IncEx] | None @final class SchemaSerializer: @@ -297,32 +256,28 @@ class SchemaSerializer: `CombinedSerializer` which may in turn own more `CombinedSerializer`s which make up the full schema serializer. """ - def __init__(self, schema: CoreSchema, config: CoreConfig | None = None) -> None: - """Initializes the `SchemaSerializer`. + def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None) -> Self: + """ + Create a new SchemaSerializer. Arguments: - schema: The `CoreSchema` to use for serialization. + schema: The [`CoreSchema`][pydantic_core.core_schema.CoreSchema] to use for serialization. config: Optionally a [`CoreConfig`][pydantic_core.core_schema.CoreConfig] to to configure serialization. """ - - def __new__(cls, schema: CoreSchema, config: CoreConfig | None = None) -> Self: ... def to_python( self, value: Any, *, mode: str | None = None, - include: _IncEx | None = None, - exclude: _IncEx | None = None, - by_alias: bool | None = None, + include: _IncEx = None, + exclude: _IncEx = None, + by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, + warnings: bool = True, fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, ) -> Any: """ Serialize/marshal a Python object to a Python object including transforming and filtering data. @@ -338,15 +293,10 @@ class SchemaSerializer: e.g. are not included in `__pydantic_fields_set__`. exclude_defaults: Whether to exclude fields that are equal to their default value. exclude_none: Whether to exclude fields that have a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. round_trip: Whether to enable serialization and validation round-trip support. - warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + warnings: Whether to log warnings when invalid fields are encountered. fallback: A function to call when an unknown value is encountered, if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: The context to use for serialization, this is passed to functional serializers as - [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. Raises: PydanticSerializationError: If serialization fails and no `fallback` function is provided. @@ -359,19 +309,15 @@ class SchemaSerializer: value: Any, *, indent: int | None = None, - ensure_ascii: bool = False, - include: _IncEx | None = None, - exclude: _IncEx | None = None, - by_alias: bool | None = None, + include: _IncEx = None, + exclude: _IncEx = None, + by_alias: bool = True, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, - exclude_computed_fields: bool = False, round_trip: bool = False, - warnings: bool | Literal['none', 'warn', 'error'] = True, + warnings: bool = True, fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, ) -> bytes: """ Serialize a Python object to JSON including transforming and filtering data. @@ -379,8 +325,6 @@ class SchemaSerializer: Arguments: value: The Python object to serialize. indent: If `None`, the JSON will be compact, otherwise it will be pretty-printed with the indent provided. - ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. - If `False` (the default), these characters will be output as-is. include: A set of fields to include, if `None` all fields are included. exclude: A set of fields to exclude, if `None` no fields are excluded. by_alias: Whether to use the alias names of fields. @@ -388,15 +332,10 @@ class SchemaSerializer: e.g. are not included in `__pydantic_fields_set__`. exclude_defaults: Whether to exclude fields that are equal to their default value. exclude_none: Whether to exclude fields that have a value of `None`. - exclude_computed_fields: Whether to exclude computed fields. round_trip: Whether to enable serialization and validation round-trip support. - warnings: How to handle invalid fields. False/"none" ignores them, True/"warn" logs errors, - "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError]. + warnings: Whether to log warnings when invalid fields are encountered. fallback: A function to call when an unknown value is encountered, if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: The context to use for serialization, this is passed to functional serializers as - [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. Raises: PydanticSerializationError: If serialization fails and no `fallback` function is provided. @@ -409,23 +348,15 @@ def to_json( value: Any, *, indent: int | None = None, - ensure_ascii: bool = False, - include: _IncEx | None = None, - exclude: _IncEx | None = None, - # Note: In Pydantic 2.11, the default value of `by_alias` on `SchemaSerializer` was changed from `True` to `None`, - # to be consistent with the Pydantic "dump" methods. However, the default of `True` was kept here for - # backwards compatibility. In Pydantic V3, `by_alias` is expected to default to `True` everywhere: + include: _IncEx = None, + exclude: _IncEx = None, by_alias: bool = True, exclude_none: bool = False, round_trip: bool = False, timedelta_mode: Literal['iso8601', 'float'] = 'iso8601', - temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601', - bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8', - inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants', + bytes_mode: Literal['utf8', 'base64'] = 'utf8', serialize_unknown: bool = False, fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, ) -> bytes: """ Serialize a Python object to JSON including transforming and filtering data. @@ -435,26 +366,17 @@ def to_json( Arguments: value: The Python object to serialize. indent: If `None`, the JSON will be compact, otherwise it will be pretty-printed with the indent provided. - ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. - If `False` (the default), these characters will be output as-is. include: A set of fields to include, if `None` all fields are included. exclude: A set of fields to exclude, if `None` no fields are excluded. by_alias: Whether to use the alias names of fields. exclude_none: Whether to exclude fields that have a value of `None`. round_trip: Whether to enable serialization and validation round-trip support. timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`. - temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`. - `iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float. - - bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`. - inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`. + bytes_mode: How to serialize `bytes` objects, either `'utf8'` or `'base64'`. serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails `""` will be used. fallback: A function to call when an unknown value is encountered, if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: The context to use for serialization, this is passed to functional serializers as - [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. Raises: PydanticSerializationError: If serialization fails and no `fallback` function is provided. @@ -463,27 +385,15 @@ def to_json( JSON bytes. """ -def from_json( - data: str | bytes | bytearray, - *, - allow_inf_nan: bool = True, - cache_strings: bool | Literal['all', 'keys', 'none'] = True, - allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False, -) -> Any: +def from_json(data: str | bytes | bytearray, *, allow_inf_nan: bool = True) -> Any: """ Deserialize JSON data to a Python object. - This is effectively a faster version of `json.loads()`, with some extra functionality. + This is effectively a faster version of [`json.loads()`][json.loads]. Arguments: data: The JSON data to deserialize. allow_inf_nan: Whether to allow `Infinity`, `-Infinity` and `NaN` values as `json.loads()` does by default. - cache_strings: Whether to cache strings to avoid constructing new Python objects, - this should have a significant impact on performance while increasing memory usage slightly, - `all/True` means cache all strings, `keys` means cache only dict keys, `none/False` means no caching. - allow_partial: Whether to allow partial deserialization, if `True` JSON data is returned if the end of the - input is reached before the full object is deserialized, e.g. `["aa", "bb", "c` would return `['aa', 'bb']`. - `'trailing-strings'` means any final unfinished JSON string is included in the result. Raises: ValueError: If deserialization fails. @@ -495,22 +405,15 @@ def from_json( def to_jsonable_python( value: Any, *, - include: _IncEx | None = None, - exclude: _IncEx | None = None, - # Note: In Pydantic 2.11, the default value of `by_alias` on `SchemaSerializer` was changed from `True` to `None`, - # to be consistent with the Pydantic "dump" methods. However, the default of `True` was kept here for - # backwards compatibility. In Pydantic V3, `by_alias` is expected to default to `True` everywhere: + include: _IncEx = None, + exclude: _IncEx = None, by_alias: bool = True, exclude_none: bool = False, round_trip: bool = False, timedelta_mode: Literal['iso8601', 'float'] = 'iso8601', - temporal_mode: Literal['iso8601', 'seconds', 'milliseconds'] = 'iso8601', - bytes_mode: Literal['utf8', 'base64', 'hex'] = 'utf8', - inf_nan_mode: Literal['null', 'constants', 'strings'] = 'constants', + bytes_mode: Literal['utf8', 'base64'] = 'utf8', serialize_unknown: bool = False, fallback: Callable[[Any], Any] | None = None, - serialize_as_any: bool = False, - context: Any | None = None, ) -> Any: """ Serialize/marshal a Python object to a JSON-serializable Python object including transforming and filtering data. @@ -526,18 +429,11 @@ def to_jsonable_python( exclude_none: Whether to exclude fields that have a value of `None`. round_trip: Whether to enable serialization and validation round-trip support. timedelta_mode: How to serialize `timedelta` objects, either `'iso8601'` or `'float'`. - temporal_mode: How to serialize datetime-like objects (`datetime`, `date`, `time`), either `'iso8601'`, `'seconds'`, or `'milliseconds'`. - `iso8601` returns an ISO 8601 string; `seconds` returns the Unix timestamp in seconds as a float; `milliseconds` returns the Unix timestamp in milliseconds as a float. - - bytes_mode: How to serialize `bytes` objects, either `'utf8'`, `'base64'`, or `'hex'`. - inf_nan_mode: How to serialize `Infinity`, `-Infinity` and `NaN` values, either `'null'`, `'constants'`, or `'strings'`. + bytes_mode: How to serialize `bytes` objects, either `'utf8'` or `'base64'`. serialize_unknown: Attempt to serialize unknown types, `str(value)` will be used, if that fails `""` will be used. fallback: A function to call when an unknown value is encountered, if `None` a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised. - serialize_as_any: Whether to serialize fields with duck-typing serialization behavior. - context: The context to use for serialization, this is passed to functional serializers as - [`info.context`][pydantic_core.core_schema.SerializationInfo.context]. Raises: PydanticSerializationError: If serialization fails and no `fallback` function is provided. @@ -552,43 +448,133 @@ class Url(SupportsAllComparisons): by Mozilla. """ - def __init__(self, url: str) -> None: ... - def __new__(cls, url: str) -> Self: ... + def __new__(cls, url: str) -> Self: + """ + Create a new `Url` instance. + + Args: + url: String representation of a URL. + + Returns: + A new `Url` instance. + + Raises: + ValidationError: If the URL is invalid. + """ @property - def scheme(self) -> str: ... + def scheme(self) -> str: + """ + The scheme part of the URL. + + e.g. `https` in `https://user:pass@host:port/path?query#fragment` + """ @property - def username(self) -> str | None: ... + def username(self) -> str | None: + """ + The username part of the URL, or `None`. + + e.g. `user` in `https://user:pass@host:port/path?query#fragment` + """ @property - def password(self) -> str | None: ... + def password(self) -> str | None: + """ + The password part of the URL, or `None`. + + e.g. `pass` in `https://user:pass@host:port/path?query#fragment` + """ @property - def host(self) -> str | None: ... - def unicode_host(self) -> str | None: ... + def host(self) -> str | None: + """ + The host part of the URL, or `None`. + + If the URL must be punycode encoded, this is the encoded host, e.g if the input URL is `https://£££.com`, + `host` will be `xn--9aaa.com` + """ + def unicode_host(self) -> str | None: + """ + The host part of the URL as a unicode string, or `None`. + + e.g. `host` in `https://user:pass@host:port/path?query#fragment` + + If the URL must be punycode encoded, this is the decoded host, e.g if the input URL is `https://£££.com`, + `unicode_host()` will be `£££.com` + """ @property - def port(self) -> int | None: ... + def port(self) -> int | None: + """ + The port part of the URL, or `None`. + + e.g. `port` in `https://user:pass@host:port/path?query#fragment` + """ @property - def path(self) -> str | None: ... + def path(self) -> str | None: + """ + The path part of the URL, or `None`. + + e.g. `/path` in `https://user:pass@host:port/path?query#fragment` + """ @property - def query(self) -> str | None: ... - def query_params(self) -> list[tuple[str, str]]: ... + def query(self) -> str | None: + """ + The query part of the URL, or `None`. + + e.g. `query` in `https://user:pass@host:port/path?query#fragment` + """ + def query_params(self) -> list[tuple[str, str]]: + """ + The query part of the URL as a list of key-value pairs. + + e.g. `[('foo', 'bar')]` in `https://user:pass@host:port/path?foo=bar#fragment` + """ @property - def fragment(self) -> str | None: ... - def unicode_string(self) -> str: ... + def fragment(self) -> str | None: + """ + The fragment part of the URL, or `None`. + + e.g. `fragment` in `https://user:pass@host:port/path?query#fragment` + """ + def unicode_string(self) -> str: + """ + The URL as a unicode string, unlike `__str__()` this will not punycode encode the host. + + If the URL must be punycode encoded, this is the decoded string, e.g if the input URL is `https://£££.com`, + `unicode_string()` will be `https://£££.com` + """ def __repr__(self) -> str: ... - def __str__(self) -> str: ... + def __str__(self) -> str: + """ + The URL as a string, this will punycode encode the host if required. + """ def __deepcopy__(self, memo: dict) -> str: ... @classmethod def build( cls, *, scheme: str, - username: str | None = None, - password: str | None = None, + username: Optional[str] = None, + password: Optional[str] = None, host: str, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, - ) -> Self: ... + port: Optional[int] = None, + path: Optional[str] = None, + query: Optional[str] = None, + fragment: Optional[str] = None, + ) -> Self: + """ + Build a new `Url` instance from its component parts. + + Args: + scheme: The scheme part of the URL. + username: The username part of the URL, or omit for no username. + password: The password part of the URL, or omit for no password. + host: The host part of the URL. + port: The port part of the URL, or omit for no port. + path: The path part of the URL, or omit for no path. + query: The query part of the URL, or omit for no query. + fragment: The fragment part of the URL, or omit for no fragment. + + Returns: + An instance of URL + """ class MultiHostUrl(SupportsAllComparisons): """ @@ -598,36 +584,116 @@ class MultiHostUrl(SupportsAllComparisons): by Mozilla. """ - def __init__(self, url: str) -> None: ... - def __new__(cls, url: str) -> Self: ... + def __new__(cls, url: str) -> Self: + """ + Create a new `MultiHostUrl` instance. + + Args: + url: String representation of a URL. + + Returns: + A new `MultiHostUrl` instance. + + Raises: + ValidationError: If the URL is invalid. + """ @property - def scheme(self) -> str: ... + def scheme(self) -> str: + """ + The scheme part of the URL. + + e.g. `https` in `https://foo.com,bar.com/path?query#fragment` + """ @property - def path(self) -> str | None: ... + def path(self) -> str | None: + """ + The path part of the URL, or `None`. + + e.g. `/path` in `https://foo.com,bar.com/path?query#fragment` + """ @property - def query(self) -> str | None: ... - def query_params(self) -> list[tuple[str, str]]: ... + def query(self) -> str | None: + """ + The query part of the URL, or `None`. + + e.g. `query` in `https://foo.com,bar.com/path?query#fragment` + """ + def query_params(self) -> list[tuple[str, str]]: + """ + The query part of the URL as a list of key-value pairs. + + e.g. `[('foo', 'bar')]` in `https://foo.com,bar.com/path?query#fragment` + """ @property - def fragment(self) -> str | None: ... - def hosts(self) -> list[MultiHostHost]: ... - def unicode_string(self) -> str: ... + def fragment(self) -> str | None: + """ + The fragment part of the URL, or `None`. + + e.g. `fragment` in `https://foo.com,bar.com/path?query#fragment` + """ + def hosts(self) -> list[MultiHostHost]: + ''' + + The hosts of the `MultiHostUrl` as [`MultiHostHost`][pydantic_core.MultiHostHost] typed dicts. + + ```py + from pydantic_core import MultiHostUrl + + mhu = MultiHostUrl('https://foo.com:123,foo:bar@bar.com/path') + print(mhu.hosts()) + """ + [ + {'username': None, 'password': None, 'host': 'foo.com', 'port': 123}, + {'username': 'foo', 'password': 'bar', 'host': 'bar.com', 'port': 443} + ] + ``` + Returns: + A list of dicts, each representing a host. + ''' + def unicode_string(self) -> str: + """ + The URL as a unicode string, unlike `__str__()` this will not punycode encode the hosts. + """ def __repr__(self) -> str: ... - def __str__(self) -> str: ... + def __str__(self) -> str: + """ + The URL as a string, this will punycode encode the hosts if required. + """ def __deepcopy__(self, memo: dict) -> Self: ... @classmethod def build( cls, *, scheme: str, - hosts: list[MultiHostHost] | None = None, - username: str | None = None, - password: str | None = None, - host: str | None = None, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, - ) -> Self: ... + hosts: Optional[list[MultiHostHost]] = None, + username: Optional[str] = None, + password: Optional[str] = None, + host: Optional[str] = None, + port: Optional[int] = None, + path: Optional[str] = None, + query: Optional[str] = None, + fragment: Optional[str] = None, + ) -> Self: + """ + Build a new `MultiHostUrl` instance from its component parts. + + This method takes either `hosts` - a list of `MultiHostHost` typed dicts, or the individual components + `username`, `password`, `host` and `port`. + + Args: + scheme: The scheme part of the URL. + hosts: Multiple hosts to build the URL from. + username: The username part of the URL. + password: The password part of the URL. + host: The host part of the URL. + port: The port part of the URL. + path: The path part of the URL. + query: The query part of the URL, or omit for no query. + fragment: The fragment part of the URL, or omit for no fragment. + + Returns: + An instance of `MultiHostUrl` + """ @final class SchemaError(Exception): @@ -647,22 +713,26 @@ class SchemaError(Exception): A list of [`ErrorDetails`][pydantic_core.ErrorDetails] for each error in the schema. """ +@final class ValidationError(ValueError): """ `ValidationError` is the exception raised by `pydantic-core` when validation fails, it contains a list of errors which detail why validation failed. """ - @classmethod + + @staticmethod def from_exception_data( - cls, title: str, line_errors: list[InitErrorDetails], input_type: Literal['python', 'json'] = 'python', hide_input: bool = False, - ) -> Self: + ) -> ValidationError: """ Python constructor for a Validation Error. + The API for constructing validation errors will probably change in the future, + hence the static method rather than `__init__`. + Arguments: title: The title of the error, as used in the heading of `str(validation_error)` line_errors: A list of [`InitErrorDetails`][pydantic_core.InitErrorDetails] which contain information @@ -715,285 +785,56 @@ class ValidationError(ValueError): a JSON string. """ - def __repr__(self) -> str: - """ - A string representation of the validation error. - - Whether or not documentation URLs are included in the repr is controlled by the - environment variable `PYDANTIC_ERRORS_INCLUDE_URL` being set to `1` or - `true`; by default, URLs are shown. - - Due to implementation details, this environment variable can only be set once, - before the first validation error is created. - """ - +@final class PydanticCustomError(ValueError): - """A custom exception providing flexible error handling for Pydantic validators. - - You can raise this error in custom validators when you'd like flexibility in regards to the error type, message, and context. - - Example: - ```py - from pydantic_core import PydanticCustomError - - def custom_validator(v) -> None: - if v <= 10: - raise PydanticCustomError('custom_value_error', 'Value must be greater than {value}', {'value': 10, 'extra_context': 'extra_data'}) - return v - ``` - - Arguments: - error_type: The error type. - message_template: The message template. - context: The data to inject into the message template. - """ - - def __init__( - self, error_type: LiteralString, message_template: LiteralString, context: dict[str, Any] | None = None, / - ) -> None: ... + def __new__( + cls, error_type: LiteralString, message_template: LiteralString, context: dict[str, Any] | None = None + ) -> Self: ... @property - def context(self) -> dict[str, Any] | None: - """Values which are required to render the error message, and could hence be useful in passing error data forward.""" - + def context(self) -> dict[str, Any] | None: ... @property - def type(self) -> str: - """The error type associated with the error. For consistency with Pydantic, this is typically a snake_case string.""" - + def type(self) -> str: ... @property - def message_template(self) -> str: - """The message template associated with the error. This is a string that can be formatted with context variables in `{curly_braces}`.""" - - def message(self) -> str: - """The formatted message associated with the error. This presents as the message template with context variables appropriately injected.""" + def message_template(self) -> str: ... + def message(self) -> str: ... @final class PydanticKnownError(ValueError): - """A helper class for raising exceptions that mimic Pydantic's built-in exceptions, with more flexibility in regards to context. - - Unlike [`PydanticCustomError`][pydantic_core.PydanticCustomError], the `error_type` argument must be a known `ErrorType`. - - Example: - ```py - from pydantic_core import PydanticKnownError - - def custom_validator(v) -> None: - if v <= 10: - raise PydanticKnownError('greater_than', {'gt': 10}) - return v - ``` - - Arguments: - error_type: The error type. - context: The data to inject into the message template. - """ - - def __init__(self, error_type: ErrorType, context: dict[str, Any] | None = None, /) -> None: ... + def __new__(cls, error_type: ErrorType, context: dict[str, Any] | None = None) -> Self: ... @property - def context(self) -> dict[str, Any] | None: - """Values which are required to render the error message, and could hence be useful in passing error data forward.""" - + def context(self) -> dict[str, Any] | None: ... @property - def type(self) -> ErrorType: - """The type of the error.""" - + def type(self) -> ErrorType: ... @property - def message_template(self) -> str: - """The message template associated with the provided error type. This is a string that can be formatted with context variables in `{curly_braces}`.""" - - def message(self) -> str: - """The formatted message associated with the error. This presents as the message template with context variables appropriately injected.""" + def message_template(self) -> str: ... + def message(self) -> str: ... @final class PydanticOmit(Exception): - """An exception to signal that a field should be omitted from a generated result. - - This could span from omitting a field from a JSON Schema to omitting a field from a serialized result. - Upcoming: more robust support for using PydanticOmit in custom serializers is still in development. - Right now, this is primarily used in the JSON Schema generation process. - - Example: - ```py - from typing import Callable - - from pydantic_core import PydanticOmit - - from pydantic import BaseModel - from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue - - - class MyGenerateJsonSchema(GenerateJsonSchema): - def handle_invalid_for_json_schema(self, schema, error_info) -> JsonSchemaValue: - raise PydanticOmit - - - class Predicate(BaseModel): - name: str = 'no-op' - func: Callable = lambda x: x - - - instance_example = Predicate() - - validation_schema = instance_example.model_json_schema(schema_generator=MyGenerateJsonSchema, mode='validation') - print(validation_schema) - ''' - {'properties': {'name': {'default': 'no-op', 'title': 'Name', 'type': 'string'}}, 'title': 'Predicate', 'type': 'object'} - ''' - ``` - - For a more in depth example / explanation, see the [customizing JSON schema](../concepts/json_schema.md#customizing-the-json-schema-generation-process) docs. - """ - def __new__(cls) -> Self: ... @final class PydanticUseDefault(Exception): - """An exception to signal that standard validation either failed or should be skipped, and the default value should be used instead. - - This warning can be raised in custom valiation functions to redirect the flow of validation. - - Example: - ```py - from pydantic_core import PydanticUseDefault - from datetime import datetime - from pydantic import BaseModel, field_validator - - - class Event(BaseModel): - name: str = 'meeting' - time: datetime - - @field_validator('name', mode='plain') - def name_must_be_present(cls, v) -> str: - if not v or not isinstance(v, str): - raise PydanticUseDefault() - return v - - - event1 = Event(name='party', time=datetime(2024, 1, 1, 12, 0, 0)) - print(repr(event1)) - # > Event(name='party', time=datetime.datetime(2024, 1, 1, 12, 0)) - event2 = Event(time=datetime(2024, 1, 1, 12, 0, 0)) - print(repr(event2)) - # > Event(name='meeting', time=datetime.datetime(2024, 1, 1, 12, 0)) - ``` - - For an additional example, see the [validating partial json data](../concepts/json.md#partial-json-parsing) section of the Pydantic documentation. - """ - def __new__(cls) -> Self: ... @final class PydanticSerializationError(ValueError): - """An error raised when an issue occurs during serialization. - - In custom serializers, this error can be used to indicate that serialization has failed. - - Arguments: - message: The message associated with the error. - """ - - def __init__(self, message: str, /) -> None: ... + def __new__(cls, message: str) -> Self: ... @final class PydanticSerializationUnexpectedValue(ValueError): - """An error raised when an unexpected value is encountered during serialization. - - This error is often caught and coerced into a warning, as `pydantic-core` generally makes a best attempt - at serializing values, in contrast with validation where errors are eagerly raised. - - Example: - ```py - from pydantic import BaseModel, field_serializer - from pydantic_core import PydanticSerializationUnexpectedValue - - class BasicPoint(BaseModel): - x: int - y: int - - @field_serializer('*') - def serialize(self, v): - if not isinstance(v, int): - raise PydanticSerializationUnexpectedValue(f'Expected type `int`, got {type(v)} with value {v}') - return v - - point = BasicPoint(x=1, y=2) - # some sort of mutation - point.x = 'a' - - print(point.model_dump()) - ''' - UserWarning: Pydantic serializer warnings: - PydanticSerializationUnexpectedValue(Expected type `int`, got with value a) - return self.__pydantic_serializer__.to_python( - {'x': 'a', 'y': 2} - ''' - ``` - - This is often used internally in `pydantic-core` when unexpected types are encountered during serialization, - but it can also be used by users in custom serializers, as seen above. - - Arguments: - message: The message associated with the unexpected value. - """ - - def __init__(self, message: str, /) -> None: ... + def __new__(cls, message: str | None = None) -> Self: ... @final class ArgsKwargs: - """A construct used to store arguments and keyword arguments for a function call. - - This data structure is generally used to store information for core schemas associated with functions (like in an arguments schema). - This data structure is also currently used for some validation against dataclasses. - - Example: - ```py - from pydantic.dataclasses import dataclass - from pydantic import model_validator - - - @dataclass - class Model: - a: int - b: int - - @model_validator(mode="before") - @classmethod - def no_op_validator(cls, values): - print(values) - return values - - Model(1, b=2) - #> ArgsKwargs((1,), {"b": 2}) - - Model(1, 2) - #> ArgsKwargs((1, 2), {}) - - Model(a=1, b=2) - #> ArgsKwargs((), {"a": 1, "b": 2}) - ``` - """ - - def __init__(self, args: tuple[Any, ...], kwargs: dict[str, Any] | None = None) -> None: - """Initializes the `ArgsKwargs`. - - Arguments: - args: The arguments (inherently ordered) for a function call. - kwargs: The keyword arguments for a function call - """ - def __new__(cls, args: tuple[Any, ...], kwargs: dict[str, Any] | None = None) -> Self: ... @property - def args(self) -> tuple[Any, ...]: - """The arguments (inherently ordered) for a function call.""" - + def args(self) -> tuple[Any, ...]: ... @property - def kwargs(self) -> dict[str, Any] | None: - """The keyword arguments for a function call.""" + def kwargs(self) -> dict[str, Any] | None: ... @final class PydanticUndefinedType: - """A type used as a sentinel for undefined values.""" - def __copy__(self) -> Self: ... def __deepcopy__(self, memo: Any) -> Self: ... @@ -1008,39 +849,16 @@ def list_all_errors() -> list[ErrorTypeInfo]: """ @final class TzInfo(datetime.tzinfo): - """An `pydantic-core` implementation of the abstract [`datetime.tzinfo`][] class.""" + def tzname(self, _dt: datetime.datetime | None) -> str | None: ... + def utcoffset(self, _dt: datetime.datetime | None) -> datetime.timedelta: ... + def dst(self, _dt: datetime.datetime | None) -> datetime.timedelta: ... + def fromutc(self, dt: datetime.datetime) -> datetime.datetime: ... + def __deepcopy__(self, _memo: dict[Any, Any]) -> 'TzInfo': ... - def __init__(self, seconds: float = 0.0) -> None: - """Initializes the `TzInfo`. - - Arguments: - seconds: The offset from UTC in seconds. Defaults to 0.0 (UTC). - """ - - def __new__(cls, seconds: float = 0.0) -> Self: ... - - # Docstrings for attributes sourced from the abstract base class, [`datetime.tzinfo`](https://docs.python.org/3/library/datetime.html#datetime.tzinfo). - - def tzname(self, dt: datetime.datetime | None) -> str | None: - """Return the time zone name corresponding to the [`datetime`][datetime.datetime] object _dt_, as a string. - - For more info, see [`tzinfo.tzname`][datetime.tzinfo.tzname]. - """ - - def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: - """Return offset of local time from UTC, as a [`timedelta`][datetime.timedelta] object that is positive east of UTC. If local time is west of UTC, this should be negative. - - More info can be found at [`tzinfo.utcoffset`][datetime.tzinfo.utcoffset]. - """ - - def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: - """Return the daylight saving time (DST) adjustment, as a [`timedelta`][datetime.timedelta] object or `None` if DST information isn’t known. - - More info can be found at[`tzinfo.dst`][datetime.tzinfo.dst].""" - - def fromutc(self, dt: datetime.datetime) -> datetime.datetime: - """Adjust the date and time data associated datetime object _dt_, returning an equivalent datetime in self’s local time. - - More info can be found at [`tzinfo.fromutc`][datetime.tzinfo.fromutc].""" - - def __deepcopy__(self, _memo: dict[Any, Any]) -> TzInfo: ... +def validate_core_schema(schema: CoreSchema, *, strict: bool | None = None) -> CoreSchema: + """Validate a CoreSchema + This currently uses lax mode for validation (i.e. will coerce strings to dates and such) + but may use strict mode in the future. + We may also remove this function altogether, do not rely on it being present if you are + using pydantic-core directly. + """ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_core/core_schema.py b/Backend/venv/lib/python3.12/site-packages/pydantic_core/core_schema.py index c8a3b6da..fec3b996 100644 --- a/Backend/venv/lib/python3.12/site-packages/pydantic_core/core_schema.py +++ b/Backend/venv/lib/python3.12/site-packages/pydantic_core/core_schema.py @@ -7,13 +7,12 @@ from __future__ import annotations as _annotations import sys import warnings -from collections.abc import Hashable, Mapping +from collections.abc import Mapping from datetime import date, datetime, time, timedelta from decimal import Decimal -from re import Pattern -from typing import TYPE_CHECKING, Any, Callable, Literal, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Hashable, List, Set, Tuple, Type, Union -from typing_extensions import TypeVar, deprecated +from typing_extensions import deprecated if sys.version_info < (3, 12): from typing_extensions import TypedDict @@ -25,6 +24,11 @@ if sys.version_info < (3, 11): else: from typing import Protocol, Required, TypeAlias +if sys.version_info < (3, 9): + from typing_extensions import Literal +else: + from typing import Literal + if TYPE_CHECKING: from pydantic_core import PydanticUndefined else: @@ -54,6 +58,8 @@ class CoreConfig(TypedDict, total=False): `field_names` to construct error `loc`s. Default is `True`. revalidate_instances: Whether instances of models and dataclasses should re-validate. Default is 'never'. validate_default: Whether to validate default values during validation. Default is `False`. + populate_by_name: Whether an aliased field may be populated by its name as given by the model attribute, + as well as the alias. (Replaces 'allow_population_by_field_name' in Pydantic v1.) Default is `False`. str_max_length: The maximum length for string fields. str_min_length: The minimum length for string fields. str_strip_whitespace: Whether to strip whitespace from string fields. @@ -61,25 +67,14 @@ class CoreConfig(TypedDict, total=False): str_to_upper: Whether to convert string fields to uppercase. allow_inf_nan: Whether to allow infinity and NaN values for float fields. Default is `True`. ser_json_timedelta: The serialization option for `timedelta` values. Default is 'iso8601'. - Note that if ser_json_temporal is set, then this param will be ignored. - ser_json_temporal: The serialization option for datetime like values. Default is 'iso8601'. - The types this covers are datetime, date, time and timedelta. - If this is set, it will take precedence over ser_json_timedelta ser_json_bytes: The serialization option for `bytes` values. Default is 'utf8'. ser_json_inf_nan: The serialization option for infinity and NaN values in float fields. Default is 'null'. - val_json_bytes: The validation option for `bytes` values, complementing ser_json_bytes. Default is 'utf8'. hide_input_in_errors: Whether to hide input data from `ValidationError` representation. validation_error_cause: Whether to add user-python excs to the __cause__ of a ValidationError. Requires exceptiongroup backport pre Python 3.11. coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). regex_engine: The regex engine to use for regex pattern validation. Default is 'rust-regex'. See `StringSchema`. - cache_strings: Whether to cache strings. Default is `True`, `True` or `'all'` is required to cache strings - during general validation since validators don't know if they're in a key or a value. - validate_by_alias: Whether to use the field's alias when validating against the provided input data. Default is `True`. - validate_by_name: Whether to use the field's name when validating against the provided input data. Default is `False`. Replacement for `populate_by_name`. - serialize_by_alias: Whether to serialize by alias. Default is `False`, expected to change to `True` in V3. - url_preserve_empty_path: Whether to preserve empty URL paths when validating values for a URL type. Defaults to `False`. """ title: str @@ -97,6 +92,7 @@ class CoreConfig(TypedDict, total=False): # whether to validate default values during validation, default False validate_default: bool # used on typed-dicts and arguments + populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1 # fields related to string fields only str_max_length: int str_min_length: int @@ -107,107 +103,75 @@ class CoreConfig(TypedDict, total=False): allow_inf_nan: bool # default: True # the config options are used to customise serialization to JSON ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601' - ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds'] # default: 'iso8601' ser_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8' - ser_json_inf_nan: Literal['null', 'constants', 'strings'] # default: 'null' - val_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8' + ser_json_inf_nan: Literal['null', 'constants'] # default: 'null' # used to hide input data from ValidationError repr hide_input_in_errors: bool validation_error_cause: bool # default: False coerce_numbers_to_str: bool # default: False regex_engine: Literal['rust-regex', 'python-re'] # default: 'rust-regex' - cache_strings: Union[bool, Literal['all', 'keys', 'none']] # default: 'True' - validate_by_alias: bool # default: True - validate_by_name: bool # default: False - serialize_by_alias: bool # default: False - url_preserve_empty_path: bool # default: False IncExCall: TypeAlias = 'set[int | str] | dict[int | str, IncExCall] | None' -ContextT = TypeVar('ContextT', covariant=True, default='Any | None') - - -class SerializationInfo(Protocol[ContextT]): - """Extra data used during serialization.""" +class SerializationInfo(Protocol): @property def include(self) -> IncExCall: - """The `include` argument set during serialization.""" ... @property def exclude(self) -> IncExCall: - """The `exclude` argument set during serialization.""" ... @property - def context(self) -> ContextT: - """The current serialization context.""" - ... - - @property - def mode(self) -> Literal['python', 'json'] | str: - """The serialization mode set during serialization.""" + def mode(self) -> str: ... @property def by_alias(self) -> bool: - """The `by_alias` argument set during serialization.""" ... @property def exclude_unset(self) -> bool: - """The `exclude_unset` argument set during serialization.""" ... @property def exclude_defaults(self) -> bool: - """The `exclude_defaults` argument set during serialization.""" ... @property def exclude_none(self) -> bool: - """The `exclude_none` argument set during serialization.""" - ... - - @property - def exclude_computed_fields(self) -> bool: - """The `exclude_computed_fields` argument set during serialization.""" - ... - - @property - def serialize_as_any(self) -> bool: - """The `serialize_as_any` argument set during serialization.""" ... @property def round_trip(self) -> bool: - """The `round_trip` argument set during serialization.""" ... - def mode_is_json(self) -> bool: ... + def mode_is_json(self) -> bool: + ... - def __str__(self) -> str: ... + def __str__(self) -> str: + ... - def __repr__(self) -> str: ... + def __repr__(self) -> str: + ... -class FieldSerializationInfo(SerializationInfo[ContextT], Protocol): - """Extra data used during field serialization.""" - +class FieldSerializationInfo(SerializationInfo, Protocol): @property def field_name(self) -> str: - """The name of the current field being serialized.""" ... -class ValidationInfo(Protocol[ContextT]): - """Extra data used during validation.""" +class ValidationInfo(Protocol): + """ + Argument passed to validation functions. + """ @property - def context(self) -> ContextT: - """The current validation context.""" + def context(self) -> Any | None: + """Current validation context.""" ... @property @@ -217,11 +181,11 @@ class ValidationInfo(Protocol[ContextT]): @property def mode(self) -> Literal['python', 'json']: - """The type of input data we are currently validating.""" + """The type of input data we are currently validating""" ... @property - def data(self) -> dict[str, Any]: + def data(self) -> Dict[str, Any]: """The data being validated for this model.""" ... @@ -256,7 +220,6 @@ ExpectedSerializationTypes = Literal[ 'multi-host-url', 'json', 'uuid', - 'any', ] @@ -274,14 +237,14 @@ def simple_ser_schema(type: ExpectedSerializationTypes) -> SimpleSerSchema: return SimpleSerSchema(type=type) -# (input_value: Any, /) -> Any +# (__input_value: Any) -> Any GeneralPlainNoInfoSerializerFunction = Callable[[Any], Any] -# (input_value: Any, info: FieldSerializationInfo, /) -> Any -GeneralPlainInfoSerializerFunction = Callable[[Any, SerializationInfo[Any]], Any] -# (model: Any, input_value: Any, /) -> Any +# (__input_value: Any, __info: FieldSerializationInfo) -> Any +GeneralPlainInfoSerializerFunction = Callable[[Any, SerializationInfo], Any] +# (__model: Any, __input_value: Any) -> Any FieldPlainNoInfoSerializerFunction = Callable[[Any, Any], Any] -# (model: Any, input_value: Any, info: FieldSerializationInfo, /) -> Any -FieldPlainInfoSerializerFunction = Callable[[Any, Any, FieldSerializationInfo[Any]], Any] +# (__model: Any, __input_value: Any, __info: FieldSerializationInfo) -> Any +FieldPlainInfoSerializerFunction = Callable[[Any, Any, FieldSerializationInfo], Any] SerializerFunction = Union[ GeneralPlainNoInfoSerializerFunction, GeneralPlainInfoSerializerFunction, @@ -324,7 +287,7 @@ def plain_serializer_function_ser_schema( function: The function to use for serialization is_field_serializer: Whether the serializer is for a field, e.g. takes `model` as the first argument, and `info` includes `field_name` - info_arg: Whether the function takes an `info` argument + info_arg: Whether the function takes an `__info` argument return_schema: Schema to use for serializing return value when_used: When the function should be called """ @@ -342,17 +305,18 @@ def plain_serializer_function_ser_schema( class SerializerFunctionWrapHandler(Protocol): # pragma: no cover - def __call__(self, input_value: Any, index_key: int | str | None = None, /) -> Any: ... + def __call__(self, __input_value: Any, __index_key: int | str | None = None) -> Any: + ... -# (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any +# (__input_value: Any, __serializer: SerializerFunctionWrapHandler) -> Any GeneralWrapNoInfoSerializerFunction = Callable[[Any, SerializerFunctionWrapHandler], Any] -# (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any -GeneralWrapInfoSerializerFunction = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any] -# (model: Any, input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any +# (__input_value: Any, __serializer: SerializerFunctionWrapHandler, __info: SerializationInfo) -> Any +GeneralWrapInfoSerializerFunction = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo], Any] +# (__model: Any, __input_value: Any, __serializer: SerializerFunctionWrapHandler) -> Any FieldWrapNoInfoSerializerFunction = Callable[[Any, Any, SerializerFunctionWrapHandler], Any] -# (model: Any, input_value: Any, serializer: SerializerFunctionWrapHandler, info: FieldSerializationInfo, /) -> Any -FieldWrapInfoSerializerFunction = Callable[[Any, Any, SerializerFunctionWrapHandler, FieldSerializationInfo[Any]], Any] +# (__model: Any, __input_value: Any, __serializer: SerializerFunctionWrapHandler, __info: FieldSerializationInfo) -> Any +FieldWrapInfoSerializerFunction = Callable[[Any, Any, SerializerFunctionWrapHandler, FieldSerializationInfo], Any] WrapSerializerFunction = Union[ GeneralWrapNoInfoSerializerFunction, GeneralWrapInfoSerializerFunction, @@ -387,7 +351,7 @@ def wrap_serializer_function_ser_schema( function: The function to use for serialization is_field_serializer: Whether the serializer is for a field, e.g. takes `model` as the first argument, and `info` includes `field_name` - info_arg: Whether the function takes an `info` argument + info_arg: Whether the function takes an `__info` argument schema: The schema to use for the inner serialization return_schema: Schema to use for serializing return value when_used: When the function should be called @@ -447,11 +411,11 @@ def to_string_ser_schema(*, when_used: WhenUsed = 'json-unless-none') -> ToStrin class ModelSerSchema(TypedDict, total=False): type: Required[Literal['model']] - cls: Required[type[Any]] + cls: Required[Type[Any]] schema: Required[CoreSchema] -def model_ser_schema(cls: type[Any], schema: CoreSchema) -> ModelSerSchema: +def model_ser_schema(cls: Type[Any], schema: CoreSchema) -> ModelSerSchema: """ Returns a schema for serialization using a model. @@ -472,39 +436,16 @@ SerSchema = Union[ ] -class InvalidSchema(TypedDict, total=False): - type: Required[Literal['invalid']] - ref: str - metadata: dict[str, Any] - # note, we never plan to use this, but include it for type checking purposes to match - # all other CoreSchema union members - serialization: SerSchema - - -def invalid_schema(ref: str | None = None, metadata: dict[str, Any] | None = None) -> InvalidSchema: - """ - Returns an invalid schema, used to indicate that a schema is invalid. - - Returns a schema that matches any value, e.g.: - - Args: - ref: optional unique identifier of the schema, used to reference the schema in other places - metadata: Any other information you want to include with the schema, not used by pydantic-core - """ - - return _dict_not_none(type='invalid', ref=ref, metadata=metadata) - - class ComputedField(TypedDict, total=False): type: Required[Literal['computed-field']] property_name: Required[str] return_schema: Required[CoreSchema] alias: str - metadata: dict[str, Any] + metadata: Any def computed_field( - property_name: str, return_schema: CoreSchema, *, alias: str | None = None, metadata: dict[str, Any] | None = None + property_name: str, return_schema: CoreSchema, *, alias: str | None = None, metadata: Any = None ) -> ComputedField: """ ComputedFields are properties of a model or dataclass that are included in serialization. @@ -523,13 +464,11 @@ def computed_field( class AnySchema(TypedDict, total=False): type: Required[Literal['any']] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema -def any_schema( - *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None -) -> AnySchema: +def any_schema(*, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None) -> AnySchema: """ Returns a schema that matches any value, e.g.: @@ -552,13 +491,11 @@ def any_schema( class NoneSchema(TypedDict, total=False): type: Required[Literal['none']] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema -def none_schema( - *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None -) -> NoneSchema: +def none_schema(*, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None) -> NoneSchema: """ Returns a schema that matches a None value, e.g.: @@ -582,15 +519,12 @@ class BoolSchema(TypedDict, total=False): type: Required[Literal['bool']] strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def bool_schema( - strict: bool | None = None, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, + strict: bool | None = None, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None ) -> BoolSchema: """ Returns a schema that matches a bool value, e.g.: @@ -621,7 +555,7 @@ class IntSchema(TypedDict, total=False): gt: int strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -634,7 +568,7 @@ def int_schema( gt: int | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> IntSchema: """ @@ -683,7 +617,7 @@ class FloatSchema(TypedDict, total=False): gt: float strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -697,7 +631,7 @@ def float_schema( gt: float | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> FloatSchema: """ @@ -750,13 +684,13 @@ class DecimalSchema(TypedDict, total=False): decimal_places: int strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def decimal_schema( *, - allow_inf_nan: bool | None = None, + allow_inf_nan: bool = None, multiple_of: Decimal | None = None, le: Decimal | None = None, ge: Decimal | None = None, @@ -766,7 +700,7 @@ def decimal_schema( decimal_places: int | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> DecimalSchema: """ @@ -812,51 +746,9 @@ def decimal_schema( ) -class ComplexSchema(TypedDict, total=False): - type: Required[Literal['complex']] - strict: bool - ref: str - metadata: dict[str, Any] - serialization: SerSchema - - -def complex_schema( - *, - strict: bool | None = None, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, -) -> ComplexSchema: - """ - Returns a schema that matches a complex value, e.g.: - - ```py - from pydantic_core import SchemaValidator, core_schema - - schema = core_schema.complex_schema() - v = SchemaValidator(schema) - assert v.validate_python('1+2j') == complex(1, 2) - assert v.validate_python(complex(1, 2)) == complex(1, 2) - ``` - - Args: - strict: Whether the value should be a complex object instance or a value that can be converted to a complex object - ref: optional unique identifier of the schema, used to reference the schema in other places - metadata: Any other information you want to include with the schema, not used by pydantic-core - serialization: Custom serialization schema - """ - return _dict_not_none( - type='complex', - strict=strict, - ref=ref, - metadata=metadata, - serialization=serialization, - ) - - class StringSchema(TypedDict, total=False): type: Required[Literal['str']] - pattern: Union[str, Pattern[str]] + pattern: str max_length: int min_length: int strip_whitespace: bool @@ -864,15 +756,14 @@ class StringSchema(TypedDict, total=False): to_upper: bool regex_engine: Literal['rust-regex', 'python-re'] # default: 'rust-regex' strict: bool - coerce_numbers_to_str: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def str_schema( *, - pattern: str | Pattern[str] | None = None, + pattern: str | None = None, max_length: int | None = None, min_length: int | None = None, strip_whitespace: bool | None = None, @@ -880,9 +771,8 @@ def str_schema( to_upper: bool | None = None, regex_engine: Literal['rust-regex', 'python-re'] | None = None, strict: bool | None = None, - coerce_numbers_to_str: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> StringSchema: """ @@ -910,7 +800,6 @@ def str_schema( - `python-re` use the [`re`](https://docs.python.org/3/library/re.html) module, which supports all regex features, but may be slower. strict: Whether the value should be a string or a value that can be converted to a string - coerce_numbers_to_str: Whether to enable coercion of any `Number` type to `str` (not applicable in `strict` mode). ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema @@ -925,7 +814,6 @@ def str_schema( to_upper=to_upper, regex_engine=regex_engine, strict=strict, - coerce_numbers_to_str=coerce_numbers_to_str, ref=ref, metadata=metadata, serialization=serialization, @@ -938,7 +826,7 @@ class BytesSchema(TypedDict, total=False): min_length: int strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -948,7 +836,7 @@ def bytes_schema( min_length: int | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> BytesSchema: """ @@ -990,10 +878,10 @@ class DateSchema(TypedDict, total=False): gt: date now_op: Literal['past', 'future'] # defaults to current local utc offset from `time.localtime().tm_gmtoff` - # value is restricted to -86_400 < offset < 86_400: + # value is restricted to -86_400 < offset < 86_400 by bounds in generate_self_schema.py now_utc_offset: int ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1007,7 +895,7 @@ def date_schema( now_op: Literal['past', 'future'] | None = None, now_utc_offset: int | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> DateSchema: """ @@ -1059,7 +947,7 @@ class TimeSchema(TypedDict, total=False): tz_constraint: Union[Literal['aware', 'naive'], int] microseconds_precision: Literal['truncate', 'error'] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1073,7 +961,7 @@ def time_schema( tz_constraint: Literal['aware', 'naive'] | int | None = None, microseconds_precision: Literal['truncate', 'error'] = 'truncate', ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> TimeSchema: """ @@ -1129,7 +1017,7 @@ class DatetimeSchema(TypedDict, total=False): now_utc_offset: int microseconds_precision: Literal['truncate', 'error'] # default: 'truncate' ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1145,7 +1033,7 @@ def datetime_schema( now_utc_offset: int | None = None, microseconds_precision: Literal['truncate', 'error'] = 'truncate', ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> DatetimeSchema: """ @@ -1202,7 +1090,7 @@ class TimedeltaSchema(TypedDict, total=False): gt: timedelta microseconds_precision: Literal['truncate', 'error'] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1215,7 +1103,7 @@ def timedelta_schema( gt: timedelta | None = None, microseconds_precision: Literal['truncate', 'error'] = 'truncate', ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> TimedeltaSchema: """ @@ -1257,18 +1145,14 @@ def timedelta_schema( class LiteralSchema(TypedDict, total=False): type: Required[Literal['literal']] - expected: Required[list[Any]] + expected: Required[List[Any]] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def literal_schema( - expected: list[Any], - *, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, + expected: list[Any], *, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None ) -> LiteralSchema: """ Returns a schema that matches a literal value, e.g.: @@ -1290,88 +1174,6 @@ def literal_schema( return _dict_not_none(type='literal', expected=expected, ref=ref, metadata=metadata, serialization=serialization) -class EnumSchema(TypedDict, total=False): - type: Required[Literal['enum']] - cls: Required[Any] - members: Required[list[Any]] - sub_type: Literal['str', 'int', 'float'] - missing: Callable[[Any], Any] - strict: bool - ref: str - metadata: dict[str, Any] - serialization: SerSchema - - -def enum_schema( - cls: Any, - members: list[Any], - *, - sub_type: Literal['str', 'int', 'float'] | None = None, - missing: Callable[[Any], Any] | None = None, - strict: bool | None = None, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, -) -> EnumSchema: - """ - Returns a schema that matches an enum value, e.g.: - - ```py - from enum import Enum - from pydantic_core import SchemaValidator, core_schema - - class Color(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - - schema = core_schema.enum_schema(Color, list(Color.__members__.values())) - v = SchemaValidator(schema) - assert v.validate_python(2) is Color.GREEN - ``` - - Args: - cls: The enum class - members: The members of the enum, generally `list(MyEnum.__members__.values())` - sub_type: The type of the enum, either 'str' or 'int' or None for plain enums - missing: A function to use when the value is not found in the enum, from `_missing_` - strict: Whether to use strict mode, defaults to False - ref: optional unique identifier of the schema, used to reference the schema in other places - metadata: Any other information you want to include with the schema, not used by pydantic-core - serialization: Custom serialization schema - """ - return _dict_not_none( - type='enum', - cls=cls, - members=members, - sub_type=sub_type, - missing=missing, - strict=strict, - ref=ref, - metadata=metadata, - serialization=serialization, - ) - - -class MissingSentinelSchema(TypedDict, total=False): - type: Required[Literal['missing-sentinel']] - metadata: dict[str, Any] - serialization: SerSchema - - -def missing_sentinel_schema( - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, -) -> MissingSentinelSchema: - """Returns a schema for the `MISSING` sentinel.""" - - return _dict_not_none( - type='missing-sentinel', - metadata=metadata, - serialization=serialization, - ) - - # must match input/parse_json.rs::JsonType::try_from JsonType = Literal['null', 'bool', 'int', 'float', 'str', 'list', 'dict'] @@ -1381,7 +1183,7 @@ class IsInstanceSchema(TypedDict, total=False): cls: Required[Any] cls_repr: str ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1390,11 +1192,11 @@ def is_instance_schema( *, cls_repr: str | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> IsInstanceSchema: """ - Returns a schema that checks if a value is an instance of a class, equivalent to python's `isinstance` method, e.g.: + Returns a schema that checks if a value is an instance of a class, equivalent to python's `isinstnace` method, e.g.: ```py from pydantic_core import SchemaValidator, core_schema @@ -1421,19 +1223,19 @@ def is_instance_schema( class IsSubclassSchema(TypedDict, total=False): type: Required[Literal['is-subclass']] - cls: Required[type[Any]] + cls: Required[Type[Any]] cls_repr: str ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def is_subclass_schema( - cls: type[Any], + cls: Type[Any], *, cls_repr: str | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> IsInstanceSchema: """ @@ -1468,12 +1270,12 @@ def is_subclass_schema( class CallableSchema(TypedDict, total=False): type: Required[Literal['callable']] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def callable_schema( - *, ref: str | None = None, metadata: dict[str, Any] | None = None, serialization: SerSchema | None = None + *, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None ) -> CallableSchema: """ Returns a schema that checks if a value is callable, equivalent to python's `callable` method, e.g.: @@ -1496,19 +1298,19 @@ def callable_schema( class UuidSchema(TypedDict, total=False): type: Required[Literal['uuid']] - version: Literal[1, 3, 4, 5, 7] + version: Literal[1, 3, 4, 5] strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def uuid_schema( *, - version: Literal[1, 3, 4, 5, 6, 7, 8] | None = None, + version: Literal[1, 3, 4, 5] | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> UuidSchema: return _dict_not_none( @@ -1518,11 +1320,11 @@ def uuid_schema( class IncExSeqSerSchema(TypedDict, total=False): type: Required[Literal['include-exclude-sequence']] - include: set[int] - exclude: set[int] + include: Set[int] + exclude: Set[int] -def filter_seq_schema(*, include: set[int] | None = None, exclude: set[int] | None = None) -> IncExSeqSerSchema: +def filter_seq_schema(*, include: Set[int] | None = None, exclude: Set[int] | None = None) -> IncExSeqSerSchema: return _dict_not_none(type='include-exclude-sequence', include=include, exclude=exclude) @@ -1534,10 +1336,9 @@ class ListSchema(TypedDict, total=False): items_schema: CoreSchema min_length: int max_length: int - fail_fast: bool strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: IncExSeqOrElseSerSchema @@ -1546,10 +1347,9 @@ def list_schema( *, min_length: int | None = None, max_length: int | None = None, - fail_fast: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: IncExSeqOrElseSerSchema | None = None, ) -> ListSchema: """ @@ -1567,7 +1367,6 @@ def list_schema( items_schema: The value must be a list of items that match this schema min_length: The value must be a list with at least this many items max_length: The value must be a list with at most this many items - fail_fast: Stop validation on the first error strict: The value must be a list with exactly this many items ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -1578,7 +1377,6 @@ def list_schema( items_schema=items_schema, min_length=min_length, max_length=max_length, - fail_fast=fail_fast, strict=strict, ref=ref, metadata=metadata, @@ -1586,16 +1384,25 @@ def list_schema( ) -# @deprecated('tuple_positional_schema is deprecated. Use pydantic_core.core_schema.tuple_schema instead.') +class TuplePositionalSchema(TypedDict, total=False): + type: Required[Literal['tuple-positional']] + items_schema: Required[List[CoreSchema]] + extras_schema: CoreSchema + strict: bool + ref: str + metadata: Any + serialization: IncExSeqOrElseSerSchema + + def tuple_positional_schema( items_schema: list[CoreSchema], *, extras_schema: CoreSchema | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: IncExSeqOrElseSerSchema | None = None, -) -> TupleSchema: +) -> TuplePositionalSchema: """ Returns a schema that matches a tuple of schemas, e.g.: @@ -1620,14 +1427,10 @@ def tuple_positional_schema( metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - if extras_schema is not None: - variadic_item_index = len(items_schema) - items_schema = items_schema + [extras_schema] - else: - variadic_item_index = None - return tuple_schema( + return _dict_not_none( + type='tuple-positional', items_schema=items_schema, - variadic_item_index=variadic_item_index, + extras_schema=extras_schema, strict=strict, ref=ref, metadata=metadata, @@ -1635,7 +1438,17 @@ def tuple_positional_schema( ) -# @deprecated('tuple_variable_schema is deprecated. Use pydantic_core.core_schema.tuple_schema instead.') +class TupleVariableSchema(TypedDict, total=False): + type: Required[Literal['tuple-variable']] + items_schema: CoreSchema + min_length: int + max_length: int + strict: bool + ref: str + metadata: Any + serialization: IncExSeqOrElseSerSchema + + def tuple_variable_schema( items_schema: CoreSchema | None = None, *, @@ -1643,9 +1456,9 @@ def tuple_variable_schema( max_length: int | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: IncExSeqOrElseSerSchema | None = None, -) -> TupleSchema: +) -> TupleVariableSchema: """ Returns a schema that matches a tuple of a given schema, e.g.: @@ -1664,79 +1477,15 @@ def tuple_variable_schema( min_length: The value must be a tuple with at least this many items max_length: The value must be a tuple with at most this many items strict: The value must be a tuple with exactly this many items - ref: Optional unique identifier of the schema, used to reference the schema in other places - metadata: Any other information you want to include with the schema, not used by pydantic-core - serialization: Custom serialization schema - """ - return tuple_schema( - items_schema=[items_schema or any_schema()], - variadic_item_index=0, - min_length=min_length, - max_length=max_length, - strict=strict, - ref=ref, - metadata=metadata, - serialization=serialization, - ) - - -class TupleSchema(TypedDict, total=False): - type: Required[Literal['tuple']] - items_schema: Required[list[CoreSchema]] - variadic_item_index: int - min_length: int - max_length: int - fail_fast: bool - strict: bool - ref: str - metadata: dict[str, Any] - serialization: IncExSeqOrElseSerSchema - - -def tuple_schema( - items_schema: list[CoreSchema], - *, - variadic_item_index: int | None = None, - min_length: int | None = None, - max_length: int | None = None, - fail_fast: bool | None = None, - strict: bool | None = None, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: IncExSeqOrElseSerSchema | None = None, -) -> TupleSchema: - """ - Returns a schema that matches a tuple of schemas, with an optional variadic item, e.g.: - - ```py - from pydantic_core import SchemaValidator, core_schema - - schema = core_schema.tuple_schema( - [core_schema.int_schema(), core_schema.str_schema(), core_schema.float_schema()], - variadic_item_index=1, - ) - v = SchemaValidator(schema) - assert v.validate_python((1, 'hello', 'world', 1.5)) == (1, 'hello', 'world', 1.5) - ``` - - Args: - items_schema: The value must be a tuple with items that match these schemas - variadic_item_index: The index of the schema in `items_schema` to be treated as variadic (following PEP 646) - min_length: The value must be a tuple with at least this many items - max_length: The value must be a tuple with at most this many items - fail_fast: Stop validation on the first error - strict: The value must be a tuple with exactly this many items - ref: Optional unique identifier of the schema, used to reference the schema in other places + ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ return _dict_not_none( - type='tuple', + type='tuple-variable', items_schema=items_schema, - variadic_item_index=variadic_item_index, min_length=min_length, max_length=max_length, - fail_fast=fail_fast, strict=strict, ref=ref, metadata=metadata, @@ -1749,10 +1498,9 @@ class SetSchema(TypedDict, total=False): items_schema: CoreSchema min_length: int max_length: int - fail_fast: bool strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1761,10 +1509,9 @@ def set_schema( *, min_length: int | None = None, max_length: int | None = None, - fail_fast: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> SetSchema: """ @@ -1784,7 +1531,6 @@ def set_schema( items_schema: The value must be a set with items that match this schema min_length: The value must be a set with at least this many items max_length: The value must be a set with at most this many items - fail_fast: Stop validation on the first error strict: The value must be a set with exactly this many items ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -1795,7 +1541,6 @@ def set_schema( items_schema=items_schema, min_length=min_length, max_length=max_length, - fail_fast=fail_fast, strict=strict, ref=ref, metadata=metadata, @@ -1808,10 +1553,9 @@ class FrozenSetSchema(TypedDict, total=False): items_schema: CoreSchema min_length: int max_length: int - fail_fast: bool strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -1820,10 +1564,9 @@ def frozenset_schema( *, min_length: int | None = None, max_length: int | None = None, - fail_fast: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> FrozenSetSchema: """ @@ -1843,7 +1586,6 @@ def frozenset_schema( items_schema: The value must be a frozenset with items that match this schema min_length: The value must be a frozenset with at least this many items max_length: The value must be a frozenset with at most this many items - fail_fast: Stop validation on the first error strict: The value must be a frozenset with exactly this many items ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -1854,7 +1596,6 @@ def frozenset_schema( items_schema=items_schema, min_length=min_length, max_length=max_length, - fail_fast=fail_fast, strict=strict, ref=ref, metadata=metadata, @@ -1868,7 +1609,7 @@ class GeneratorSchema(TypedDict, total=False): min_length: int max_length: int ref: str - metadata: dict[str, Any] + metadata: Any serialization: IncExSeqOrElseSerSchema @@ -1878,7 +1619,7 @@ def generator_schema( min_length: int | None = None, max_length: int | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: IncExSeqOrElseSerSchema | None = None, ) -> GeneratorSchema: """ @@ -1919,7 +1660,7 @@ def generator_schema( ) -IncExDict = set[Union[int, str]] +IncExDict = Set[Union[int, str]] class IncExDictSerSchema(TypedDict, total=False): @@ -1941,10 +1682,9 @@ class DictSchema(TypedDict, total=False): values_schema: CoreSchema # default: AnySchema min_length: int max_length: int - fail_fast: bool strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: IncExDictOrElseSerSchema @@ -1954,10 +1694,9 @@ def dict_schema( *, min_length: int | None = None, max_length: int | None = None, - fail_fast: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> DictSchema: """ @@ -1978,7 +1717,6 @@ def dict_schema( values_schema: The value must be a dict with values that match this schema min_length: The value must be a dict with at least this many items max_length: The value must be a dict with at most this many items - fail_fast: Stop validation on the first error strict: Whether the keys and values should be validated with strict mode ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -1990,7 +1728,6 @@ def dict_schema( values_schema=values_schema, min_length=min_length, max_length=max_length, - fail_fast=fail_fast, strict=strict, ref=ref, metadata=metadata, @@ -1998,7 +1735,7 @@ def dict_schema( ) -# (input_value: Any, /) -> Any +# (__input_value: Any) -> Any NoInfoValidatorFunction = Callable[[Any], Any] @@ -2007,14 +1744,14 @@ class NoInfoValidatorFunctionSchema(TypedDict): function: NoInfoValidatorFunction -# (input_value: Any, info: ValidationInfo, /) -> Any -WithInfoValidatorFunction = Callable[[Any, ValidationInfo[Any]], Any] +# (__input_value: Any, __info: ValidationInfo) -> Any +WithInfoValidatorFunction = Callable[[Any, ValidationInfo], Any] class WithInfoValidatorFunctionSchema(TypedDict, total=False): type: Required[Literal['with-info']] function: Required[WithInfoValidatorFunction] - field_name: str # deprecated + field_name: str ValidationFunction = Union[NoInfoValidatorFunctionSchema, WithInfoValidatorFunctionSchema] @@ -2024,13 +1761,12 @@ class _ValidatorFunctionSchema(TypedDict, total=False): function: Required[ValidationFunction] schema: Required[CoreSchema] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema class BeforeValidatorFunctionSchema(_ValidatorFunctionSchema, total=False): type: Required[Literal['function-before']] - json_schema_input_schema: CoreSchema def no_info_before_validator_function( @@ -2038,8 +1774,7 @@ def no_info_before_validator_function( schema: CoreSchema, *, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> BeforeValidatorFunctionSchema: """ @@ -2064,7 +1799,6 @@ def no_info_before_validator_function( function: The validator function to call schema: The schema to validate the output of the validator function ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ @@ -2073,7 +1807,6 @@ def no_info_before_validator_function( function={'type': 'no-info', 'function': function}, schema=schema, ref=ref, - json_schema_input_schema=json_schema_input_schema, metadata=metadata, serialization=serialization, ) @@ -2085,8 +1818,7 @@ def with_info_before_validator_function( *, field_name: str | None = None, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> BeforeValidatorFunctionSchema: """ @@ -2102,7 +1834,7 @@ def with_info_before_validator_function( return v.decode() + 'world' func_schema = core_schema.with_info_before_validator_function( - function=fn, schema=core_schema.str_schema() + function=fn, schema=core_schema.str_schema(), field_name='a' ) schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) @@ -2112,26 +1844,17 @@ def with_info_before_validator_function( Args: function: The validator function to call - field_name: The name of the field this validator is applied to, if any (deprecated) + field_name: The name of the field schema: The schema to validate the output of the validator function ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - if field_name is not None: - warnings.warn( - 'The `field_name` argument on `with_info_before_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', - DeprecationWarning, - stacklevel=2, - ) - return _dict_not_none( type='function-before', function=_dict_not_none(type='with-info', function=function, field_name=field_name), schema=schema, ref=ref, - json_schema_input_schema=json_schema_input_schema, metadata=metadata, serialization=serialization, ) @@ -2146,8 +1869,7 @@ def no_info_after_validator_function( schema: CoreSchema, *, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> AfterValidatorFunctionSchema: """ @@ -2170,7 +1892,6 @@ def no_info_after_validator_function( function: The validator function to call after the schema is validated schema: The schema to validate before the validator function ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ @@ -2179,7 +1900,6 @@ def no_info_after_validator_function( function={'type': 'no-info', 'function': function}, schema=schema, ref=ref, - json_schema_input_schema=json_schema_input_schema, metadata=metadata, serialization=serialization, ) @@ -2191,7 +1911,7 @@ def with_info_after_validator_function( *, field_name: str | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> AfterValidatorFunctionSchema: """ @@ -2207,7 +1927,7 @@ def with_info_after_validator_function( return v + 'world' func_schema = core_schema.with_info_after_validator_function( - function=fn, schema=core_schema.str_schema() + function=fn, schema=core_schema.str_schema(), field_name='a' ) schema = core_schema.typed_dict_schema({'a': core_schema.typed_dict_field(func_schema)}) @@ -2218,18 +1938,11 @@ def with_info_after_validator_function( Args: function: The validator function to call after the schema is validated schema: The schema to validate before the validator function - field_name: The name of the field this validator is applied to, if any (deprecated) + field_name: The name of the field this validators is applied to, if any ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - if field_name is not None: - warnings.warn( - 'The `field_name` argument on `with_info_after_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', - DeprecationWarning, - stacklevel=2, - ) - return _dict_not_none( type='function-after', function=_dict_not_none(type='with-info', function=function, field_name=field_name), @@ -2241,11 +1954,11 @@ def with_info_after_validator_function( class ValidatorFunctionWrapHandler(Protocol): - def __call__(self, input_value: Any, outer_location: str | int | None = None, /) -> Any: # pragma: no cover + def __call__(self, input_value: Any, outer_location: str | int | None = None) -> Any: # pragma: no cover ... -# (input_value: Any, validator: ValidatorFunctionWrapHandler, /) -> Any +# (__input_value: Any, __validator: ValidatorFunctionWrapHandler) -> Any NoInfoWrapValidatorFunction = Callable[[Any, ValidatorFunctionWrapHandler], Any] @@ -2254,14 +1967,14 @@ class NoInfoWrapValidatorFunctionSchema(TypedDict): function: NoInfoWrapValidatorFunction -# (input_value: Any, validator: ValidatorFunctionWrapHandler, info: ValidationInfo, /) -> Any -WithInfoWrapValidatorFunction = Callable[[Any, ValidatorFunctionWrapHandler, ValidationInfo[Any]], Any] +# (__input_value: Any, __validator: ValidatorFunctionWrapHandler, __info: ValidationInfo) -> Any +WithInfoWrapValidatorFunction = Callable[[Any, ValidatorFunctionWrapHandler, ValidationInfo], Any] class WithInfoWrapValidatorFunctionSchema(TypedDict, total=False): type: Required[Literal['with-info']] function: Required[WithInfoWrapValidatorFunction] - field_name: str # deprecated + field_name: str WrapValidatorFunction = Union[NoInfoWrapValidatorFunctionSchema, WithInfoWrapValidatorFunctionSchema] @@ -2272,8 +1985,7 @@ class WrapValidatorFunctionSchema(TypedDict, total=False): function: Required[WrapValidatorFunction] schema: Required[CoreSchema] ref: str - json_schema_input_schema: CoreSchema - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2282,8 +1994,7 @@ def no_info_wrap_validator_function( schema: CoreSchema, *, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> WrapValidatorFunctionSchema: """ @@ -2311,7 +2022,6 @@ def no_info_wrap_validator_function( function: The validator function to call schema: The schema to validate the output of the validator function ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ @@ -2319,7 +2029,6 @@ def no_info_wrap_validator_function( type='function-wrap', function={'type': 'no-info', 'function': function}, schema=schema, - json_schema_input_schema=json_schema_input_schema, ref=ref, metadata=metadata, serialization=serialization, @@ -2331,9 +2040,8 @@ def with_info_wrap_validator_function( schema: CoreSchema, *, field_name: str | None = None, - json_schema_input_schema: CoreSchema | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> WrapValidatorFunctionSchema: """ @@ -2361,24 +2069,15 @@ def with_info_wrap_validator_function( Args: function: The validator function to call schema: The schema to validate the output of the validator function - field_name: The name of the field this validator is applied to, if any (deprecated) - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type + field_name: The name of the field this validators is applied to, if any ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - if field_name is not None: - warnings.warn( - 'The `field_name` argument on `with_info_wrap_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', - DeprecationWarning, - stacklevel=2, - ) - return _dict_not_none( type='function-wrap', function=_dict_not_none(type='with-info', function=function, field_name=field_name), schema=schema, - json_schema_input_schema=json_schema_input_schema, ref=ref, metadata=metadata, serialization=serialization, @@ -2389,8 +2088,7 @@ class PlainValidatorFunctionSchema(TypedDict, total=False): type: Required[Literal['function-plain']] function: Required[ValidationFunction] ref: str - json_schema_input_schema: CoreSchema - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2398,8 +2096,7 @@ def no_info_plain_validator_function( function: NoInfoValidatorFunction, *, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> PlainValidatorFunctionSchema: """ @@ -2420,7 +2117,6 @@ def no_info_plain_validator_function( Args: function: The validator function to call ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ @@ -2428,7 +2124,6 @@ def no_info_plain_validator_function( type='function-plain', function={'type': 'no-info', 'function': function}, ref=ref, - json_schema_input_schema=json_schema_input_schema, metadata=metadata, serialization=serialization, ) @@ -2439,8 +2134,7 @@ def with_info_plain_validator_function( *, field_name: str | None = None, ref: str | None = None, - json_schema_input_schema: CoreSchema | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> PlainValidatorFunctionSchema: """ @@ -2460,24 +2154,15 @@ def with_info_plain_validator_function( Args: function: The validator function to call - field_name: The name of the field this validator is applied to, if any (deprecated) + field_name: The name of the field this validators is applied to, if any ref: optional unique identifier of the schema, used to reference the schema in other places - json_schema_input_schema: The core schema to be used to generate the corresponding JSON Schema input type metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - if field_name is not None: - warnings.warn( - 'The `field_name` argument on `with_info_plain_validator_function` is deprecated, it will be passed to the function through `ValidationState` instead.', - DeprecationWarning, - stacklevel=2, - ) - return _dict_not_none( type='function-plain', function=_dict_not_none(type='with-info', function=function, field_name=field_name), ref=ref, - json_schema_input_schema=json_schema_input_schema, metadata=metadata, serialization=serialization, ) @@ -2487,13 +2172,12 @@ class WithDefaultSchema(TypedDict, total=False): type: Required[Literal['default']] schema: Required[CoreSchema] default: Any - default_factory: Union[Callable[[], Any], Callable[[dict[str, Any]], Any]] - default_factory_takes_data: bool + default_factory: Callable[[], Any] on_error: Literal['raise', 'omit', 'default'] # default: 'raise' validate_default: bool # default: False strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2501,13 +2185,12 @@ def with_default_schema( schema: CoreSchema, *, default: Any = PydanticUndefined, - default_factory: Union[Callable[[], Any], Callable[[dict[str, Any]], Any], None] = None, - default_factory_takes_data: bool | None = None, + default_factory: Callable[[], Any] | None = None, on_error: Literal['raise', 'omit', 'default'] | None = None, validate_default: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> WithDefaultSchema: """ @@ -2527,8 +2210,7 @@ def with_default_schema( Args: schema: The schema to add a default value to default: The default value to use - default_factory: A callable that returns the default value to use - default_factory_takes_data: Whether the default factory takes a validated data argument + default_factory: A function that returns the default value to use on_error: What to do if the schema validation fails. One of 'raise', 'omit', 'default' validate_default: Whether the default value should be validated strict: Whether the underlying schema should be validated with strict mode @@ -2540,7 +2222,6 @@ def with_default_schema( type='default', schema=schema, default_factory=default_factory, - default_factory_takes_data=default_factory_takes_data, on_error=on_error, validate_default=validate_default, strict=strict, @@ -2558,7 +2239,7 @@ class NullableSchema(TypedDict, total=False): schema: Required[CoreSchema] strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2567,7 +2248,7 @@ def nullable_schema( *, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> NullableSchema: """ @@ -2595,16 +2276,16 @@ def nullable_schema( class UnionSchema(TypedDict, total=False): type: Required[Literal['union']] - choices: Required[list[Union[CoreSchema, tuple[CoreSchema, str]]]] + choices: Required[List[Union[CoreSchema, Tuple[CoreSchema, str]]]] # default true, whether to automatically collapse unions with one element to the inner validator auto_collapse: bool custom_error_type: str custom_error_message: str - custom_error_context: dict[str, Union[str, int, float]] + custom_error_context: Dict[str, Union[str, int, float]] mode: Literal['smart', 'left_to_right'] # default: 'smart' strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2616,8 +2297,9 @@ def union_schema( custom_error_message: str | None = None, custom_error_context: dict[str, str | int] | None = None, mode: Literal['smart', 'left_to_right'] | None = None, + strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> UnionSchema: """ @@ -2641,6 +2323,7 @@ def union_schema( mode: How to select which choice to return * `smart` (default) will try to return the choice which is the closest match to the input value * `left_to_right` will return the first choice in `choices` which succeeds validation + strict: Whether the underlying schemas should be validated with strict mode ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema @@ -2653,6 +2336,7 @@ def union_schema( custom_error_message=custom_error_message, custom_error_context=custom_error_context, mode=mode, + strict=strict, ref=ref, metadata=metadata, serialization=serialization, @@ -2661,21 +2345,21 @@ def union_schema( class TaggedUnionSchema(TypedDict, total=False): type: Required[Literal['tagged-union']] - choices: Required[dict[Hashable, CoreSchema]] - discriminator: Required[Union[str, list[Union[str, int]], list[list[Union[str, int]]], Callable[[Any], Hashable]]] + choices: Required[Dict[Hashable, CoreSchema]] + discriminator: Required[Union[str, List[Union[str, int]], List[List[Union[str, int]]], Callable[[Any], Hashable]]] custom_error_type: str custom_error_message: str - custom_error_context: dict[str, Union[str, int, float]] + custom_error_context: Dict[str, Union[str, int, float]] strict: bool from_attributes: bool # default: True ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def tagged_union_schema( - choices: dict[Any, CoreSchema], - discriminator: str | list[str | int] | list[list[str | int]] | Callable[[Any], Any], + choices: Dict[Hashable, CoreSchema], + discriminator: str | list[str | int] | list[list[str | int]] | Callable[[Any], Hashable], *, custom_error_type: str | None = None, custom_error_message: str | None = None, @@ -2683,7 +2367,7 @@ def tagged_union_schema( strict: bool | None = None, from_attributes: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> TaggedUnionSchema: """ @@ -2758,18 +2442,14 @@ def tagged_union_schema( class ChainSchema(TypedDict, total=False): type: Required[Literal['chain']] - steps: Required[list[CoreSchema]] + steps: Required[List[CoreSchema]] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def chain_schema( - steps: list[CoreSchema], - *, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, + steps: list[CoreSchema], *, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None ) -> ChainSchema: """ Returns a schema that chains the provided validation schemas, e.g.: @@ -2804,7 +2484,7 @@ class LaxOrStrictSchema(TypedDict, total=False): strict_schema: Required[CoreSchema] strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2814,7 +2494,7 @@ def lax_or_strict_schema( *, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> LaxOrStrictSchema: """ @@ -2867,7 +2547,7 @@ class JsonOrPythonSchema(TypedDict, total=False): json_schema: Required[CoreSchema] python_schema: Required[CoreSchema] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -2876,7 +2556,7 @@ def json_or_python_schema( python_schema: CoreSchema, *, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> JsonOrPythonSchema: """ @@ -2923,11 +2603,10 @@ class TypedDictField(TypedDict, total=False): type: Required[Literal['typed-dict-field']] schema: Required[CoreSchema] required: bool - validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + validation_alias: Union[str, List[Union[str, int]], List[List[Union[str, int]]]] serialization_alias: str serialization_exclude: bool # default: False - metadata: dict[str, Any] - serialization_exclude_if: Callable[[Any], bool] # default None + metadata: Any def typed_dict_field( @@ -2937,8 +2616,7 @@ def typed_dict_field( validation_alias: str | list[str | int] | list[list[str | int]] | None = None, serialization_alias: str | None = None, serialization_exclude: bool | None = None, - metadata: dict[str, Any] | None = None, - serialization_exclude_if: Callable[[Any], bool] | None = None, + metadata: Any = None, ) -> TypedDictField: """ Returns a schema that matches a typed dict field, e.g.: @@ -2951,11 +2629,10 @@ def typed_dict_field( Args: schema: The schema to use for the field - required: Whether the field is required, otherwise uses the value from `total` on the typed dict + required: Whether the field is required validation_alias: The alias(es) to use to find the field in the validation data serialization_alias: The alias to use as a key when serializing serialization_exclude: Whether to exclude the field when serializing - serialization_exclude_if: A callable that determines whether to exclude the field when serializing based on its value. metadata: Any other information you want to include with the schema, not used by pydantic-core """ return _dict_not_none( @@ -2965,40 +2642,37 @@ def typed_dict_field( validation_alias=validation_alias, serialization_alias=serialization_alias, serialization_exclude=serialization_exclude, - serialization_exclude_if=serialization_exclude_if, metadata=metadata, ) class TypedDictSchema(TypedDict, total=False): type: Required[Literal['typed-dict']] - fields: Required[dict[str, TypedDictField]] - cls: type[Any] - cls_name: str - computed_fields: list[ComputedField] + fields: Required[Dict[str, TypedDictField]] + computed_fields: List[ComputedField] strict: bool extras_schema: CoreSchema # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior total: bool # default: True + populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1 ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema config: CoreConfig def typed_dict_schema( - fields: dict[str, TypedDictField], + fields: Dict[str, TypedDictField], *, - cls: type[Any] | None = None, - cls_name: str | None = None, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, extras_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, total: bool | None = None, + populate_by_name: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, config: CoreConfig | None = None, ) -> TypedDictSchema: @@ -3006,15 +2680,10 @@ def typed_dict_schema( Returns a schema that matches a typed dict, e.g.: ```py - from typing_extensions import TypedDict - from pydantic_core import SchemaValidator, core_schema - class MyTypedDict(TypedDict): - a: str - wrapper_schema = core_schema.typed_dict_schema( - {'a': core_schema.typed_dict_field(core_schema.str_schema())}, cls=MyTypedDict + {'a': core_schema.typed_dict_field(core_schema.str_schema())} ) v = SchemaValidator(wrapper_schema) assert v.validate_python({'a': 'hello'}) == {'a': 'hello'} @@ -3022,28 +2691,25 @@ def typed_dict_schema( Args: fields: The fields to use for the typed dict - cls: The class to use for the typed dict - cls_name: The name to use in error locations. Falls back to `cls.__name__`, or the validator name if no class - is provided. computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model strict: Whether the typed dict is strict extras_schema: The extra validator to use for the typed dict ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core extra_behavior: The extra behavior to use for the typed dict - total: Whether the typed dict is total, otherwise uses `typed_dict_total` from config + total: Whether the typed dict is total + populate_by_name: Whether the typed dict should populate by name serialization: Custom serialization schema """ return _dict_not_none( type='typed-dict', fields=fields, - cls=cls, - cls_name=cls_name, computed_fields=computed_fields, strict=strict, extras_schema=extras_schema, extra_behavior=extra_behavior, total=total, + populate_by_name=populate_by_name, ref=ref, metadata=metadata, serialization=serialization, @@ -3054,12 +2720,11 @@ def typed_dict_schema( class ModelField(TypedDict, total=False): type: Required[Literal['model-field']] schema: Required[CoreSchema] - validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + validation_alias: Union[str, List[Union[str, int]], List[List[Union[str, int]]]] serialization_alias: str serialization_exclude: bool # default: False - serialization_exclude_if: Callable[[Any], bool] # default: None frozen: bool - metadata: dict[str, Any] + metadata: Any def model_field( @@ -3068,9 +2733,8 @@ def model_field( validation_alias: str | list[str | int] | list[list[str | int]] | None = None, serialization_alias: str | None = None, serialization_exclude: bool | None = None, - serialization_exclude_if: Callable[[Any], bool] | None = None, frozen: bool | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, ) -> ModelField: """ Returns a schema for a model field, e.g.: @@ -3086,7 +2750,6 @@ def model_field( validation_alias: The alias(es) to use to find the field in the validation data serialization_alias: The alias to use as a key when serializing serialization_exclude: Whether to exclude the field when serializing - serialization_exclude_if: A Callable that determines whether to exclude a field during serialization based on its value. frozen: Whether the field is frozen metadata: Any other information you want to include with the schema, not used by pydantic-core """ @@ -3096,7 +2759,6 @@ def model_field( validation_alias=validation_alias, serialization_alias=serialization_alias, serialization_exclude=serialization_exclude, - serialization_exclude_if=serialization_exclude_if, frozen=frozen, metadata=metadata, ) @@ -3104,35 +2766,36 @@ def model_field( class ModelFieldsSchema(TypedDict, total=False): type: Required[Literal['model-fields']] - fields: Required[dict[str, ModelField]] + fields: Required[Dict[str, ModelField]] model_name: str - computed_fields: list[ComputedField] + computed_fields: List[ComputedField] strict: bool extras_schema: CoreSchema - extras_keys_schema: CoreSchema + # all these values can be set via config, equivalent fields have `typed_dict_` prefix extra_behavior: ExtraBehavior + populate_by_name: bool # replaces `allow_population_by_field_name` in pydantic v1 from_attributes: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def model_fields_schema( - fields: dict[str, ModelField], + fields: Dict[str, ModelField], *, model_name: str | None = None, computed_fields: list[ComputedField] | None = None, strict: bool | None = None, extras_schema: CoreSchema | None = None, - extras_keys_schema: CoreSchema | None = None, extra_behavior: ExtraBehavior | None = None, + populate_by_name: bool | None = None, from_attributes: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> ModelFieldsSchema: """ - Returns a schema that matches the fields of a Pydantic model, e.g.: + Returns a schema that matches a typed dict, e.g.: ```py from pydantic_core import SchemaValidator, core_schema @@ -3146,16 +2809,16 @@ def model_fields_schema( ``` Args: - fields: The fields of the model + fields: The fields to use for the typed dict model_name: The name of the model, used for error messages, defaults to "Model" computed_fields: Computed fields to use when serializing the model, only applies when directly inside a model - strict: Whether the model is strict - extras_schema: The schema to use when validating extra input data - extras_keys_schema: The schema to use when validating the keys of extra input data + strict: Whether the typed dict is strict + extras_schema: The extra validator to use for the typed dict ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core - extra_behavior: The extra behavior to use for the model fields - from_attributes: Whether the model fields should be populated from attributes + extra_behavior: The extra behavior to use for the typed dict + populate_by_name: Whether the typed dict should populate by name + from_attributes: Whether the typed dict should be populated from attributes serialization: Custom serialization schema """ return _dict_not_none( @@ -3165,8 +2828,8 @@ def model_fields_schema( computed_fields=computed_fields, strict=strict, extras_schema=extras_schema, - extras_keys_schema=extras_keys_schema, extra_behavior=extra_behavior, + populate_by_name=populate_by_name, from_attributes=from_attributes, ref=ref, metadata=metadata, @@ -3176,8 +2839,7 @@ def model_fields_schema( class ModelSchema(TypedDict, total=False): type: Required[Literal['model']] - cls: Required[type[Any]] - generic_origin: type[Any] + cls: Required[Type[Any]] schema: Required[CoreSchema] custom_init: bool root_model: bool @@ -3188,15 +2850,14 @@ class ModelSchema(TypedDict, total=False): extra_behavior: ExtraBehavior config: CoreConfig ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def model_schema( - cls: type[Any], + cls: Type[Any], schema: CoreSchema, *, - generic_origin: type[Any] | None = None, custom_init: bool | None = None, root_model: bool | None = None, post_init: str | None = None, @@ -3206,7 +2867,7 @@ def model_schema( extra_behavior: ExtraBehavior | None = None, config: CoreConfig | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> ModelSchema: """ @@ -3243,8 +2904,6 @@ def model_schema( Args: cls: The class to use for the model schema: The schema to use for the model - generic_origin: The origin type used for this model, if it's a parametrized generic. Ex, - if this model schema represents `SomeModel[int]`, generic_origin is `SomeModel` custom_init: Whether the model has a custom init method root_model: Whether the model is a `RootModel` post_init: The call after init to use for the model @@ -3261,7 +2920,6 @@ def model_schema( return _dict_not_none( type='model', cls=cls, - generic_origin=generic_origin, schema=schema, custom_init=custom_init, root_model=root_model, @@ -3282,14 +2940,12 @@ class DataclassField(TypedDict, total=False): name: Required[str] schema: Required[CoreSchema] kw_only: bool # default: True - init: bool # default: True init_only: bool # default: False frozen: bool # default: False - validation_alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + validation_alias: Union[str, List[Union[str, int]], List[List[Union[str, int]]]] serialization_alias: str serialization_exclude: bool # default: False - metadata: dict[str, Any] - serialization_exclude_if: Callable[[Any], bool] # default: None + metadata: Any def dataclass_field( @@ -3297,13 +2953,11 @@ def dataclass_field( schema: CoreSchema, *, kw_only: bool | None = None, - init: bool | None = None, init_only: bool | None = None, validation_alias: str | list[str | int] | list[list[str | int]] | None = None, serialization_alias: str | None = None, serialization_exclude: bool | None = None, - metadata: dict[str, Any] | None = None, - serialization_exclude_if: Callable[[Any], bool] | None = None, + metadata: Any = None, frozen: bool | None = None, ) -> DataclassField: """ @@ -3324,12 +2978,10 @@ def dataclass_field( name: The name to use for the argument parameter schema: The schema to use for the argument parameter kw_only: Whether the field can be set with a positional argument as well as a keyword argument - init: Whether the field should be validated during initialization init_only: Whether the field should be omitted from `__dict__` and passed to `__post_init__` validation_alias: The alias(es) to use to find the field in the validation data serialization_alias: The alias to use as a key when serializing serialization_exclude: Whether to exclude the field when serializing - serialization_exclude_if: A callable that determines whether to exclude the field when serializing based on its value. metadata: Any other information you want to include with the schema, not used by pydantic-core frozen: Whether the field is frozen """ @@ -3338,12 +2990,10 @@ def dataclass_field( name=name, schema=schema, kw_only=kw_only, - init=init, init_only=init_only, validation_alias=validation_alias, serialization_alias=serialization_alias, serialization_exclude=serialization_exclude, - serialization_exclude_if=serialization_exclude_if, metadata=metadata, frozen=frozen, ) @@ -3352,11 +3002,12 @@ def dataclass_field( class DataclassArgsSchema(TypedDict, total=False): type: Required[Literal['dataclass-args']] dataclass_name: Required[str] - fields: Required[list[DataclassField]] - computed_fields: list[ComputedField] + fields: Required[List[DataclassField]] + computed_fields: List[ComputedField] + populate_by_name: bool # default: False collect_init_only: bool # default: False ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema extra_behavior: ExtraBehavior @@ -3365,10 +3016,11 @@ def dataclass_args_schema( dataclass_name: str, fields: list[DataclassField], *, - computed_fields: list[ComputedField] | None = None, + computed_fields: List[ComputedField] | None = None, + populate_by_name: bool | None = None, collect_init_only: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, extra_behavior: ExtraBehavior | None = None, ) -> DataclassArgsSchema: @@ -3393,6 +3045,7 @@ def dataclass_args_schema( dataclass_name: The name of the dataclass being validated fields: The fields to use for the dataclass computed_fields: Computed fields to use when serializing the dataclass + populate_by_name: Whether to populate by name collect_init_only: Whether to collect init only fields into a dict to pass to `__post_init__` ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -3404,6 +3057,7 @@ def dataclass_args_schema( dataclass_name=dataclass_name, fields=fields, computed_fields=computed_fields, + populate_by_name=populate_by_name, collect_init_only=collect_init_only, ref=ref, metadata=metadata, @@ -3414,34 +3068,32 @@ def dataclass_args_schema( class DataclassSchema(TypedDict, total=False): type: Required[Literal['dataclass']] - cls: Required[type[Any]] - generic_origin: type[Any] + cls: Required[Type[Any]] schema: Required[CoreSchema] - fields: Required[list[str]] + fields: Required[List[str]] cls_name: str post_init: bool # default: False revalidate_instances: Literal['always', 'never', 'subclass-instances'] # default: 'never' strict: bool # default: False frozen: bool # default False ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema slots: bool config: CoreConfig def dataclass_schema( - cls: type[Any], + cls: Type[Any], schema: CoreSchema, - fields: list[str], + fields: List[str], *, - generic_origin: type[Any] | None = None, cls_name: str | None = None, post_init: bool | None = None, revalidate_instances: Literal['always', 'never', 'subclass-instances'] | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, frozen: bool | None = None, slots: bool | None = None, @@ -3456,8 +3108,6 @@ def dataclass_schema( schema: The schema to use for the dataclass fields fields: Fields of the dataclass, this is used in serialization and in validation during re-validation and while validating assignment - generic_origin: The origin type used for this dataclass, if it's a parametrized generic. Ex, - if this model schema represents `SomeDataclass[int]`, generic_origin is `SomeDataclass` cls_name: The name to use in error locs, etc; this is useful for generics (default: `cls.__name__`) post_init: Whether to call `__post_init__` after validation revalidate_instances: whether instances of models and dataclasses (including subclass instances) @@ -3473,7 +3123,6 @@ def dataclass_schema( return _dict_not_none( type='dataclass', cls=cls, - generic_origin=generic_origin, fields=fields, cls_name=cls_name, schema=schema, @@ -3493,7 +3142,7 @@ class ArgumentsParameter(TypedDict, total=False): name: Required[str] schema: Required[CoreSchema] mode: Literal['positional_only', 'positional_or_keyword', 'keyword_only'] # default positional_or_keyword - alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] + alias: Union[str, List[Union[str, int]], List[List[Union[str, int]]]] def arguments_parameter( @@ -3526,32 +3175,25 @@ def arguments_parameter( return _dict_not_none(name=name, schema=schema, mode=mode, alias=alias) -VarKwargsMode: TypeAlias = Literal['uniform', 'unpacked-typed-dict'] - - class ArgumentsSchema(TypedDict, total=False): type: Required[Literal['arguments']] - arguments_schema: Required[list[ArgumentsParameter]] - validate_by_name: bool - validate_by_alias: bool + arguments_schema: Required[List[ArgumentsParameter]] + populate_by_name: bool var_args_schema: CoreSchema - var_kwargs_mode: VarKwargsMode var_kwargs_schema: CoreSchema ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def arguments_schema( arguments: list[ArgumentsParameter], *, - validate_by_name: bool | None = None, - validate_by_alias: bool | None = None, + populate_by_name: bool | None = None, var_args_schema: CoreSchema | None = None, - var_kwargs_mode: VarKwargsMode | None = None, var_kwargs_schema: CoreSchema | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> ArgumentsSchema: """ @@ -3573,12 +3215,8 @@ def arguments_schema( Args: arguments: The arguments to use for the arguments schema - validate_by_name: Whether to populate by the parameter names, defaults to `False`. - validate_by_alias: Whether to populate by the parameter aliases, defaults to `True`. + populate_by_name: Whether to populate by name var_args_schema: The variable args schema to use for the arguments schema - var_kwargs_mode: The validation mode to use for variadic keyword arguments. If `'uniform'`, every value of the - keyword arguments will be validated against the `var_kwargs_schema` schema. If `'unpacked-typed-dict'`, - the `var_kwargs_schema` argument must be a [`typed_dict_schema`][pydantic_core.core_schema.typed_dict_schema] var_kwargs_schema: The variable kwargs schema to use for the arguments schema ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -3587,10 +3225,8 @@ def arguments_schema( return _dict_not_none( type='arguments', arguments_schema=arguments, - validate_by_name=validate_by_name, - validate_by_alias=validate_by_alias, + populate_by_name=populate_by_name, var_args_schema=var_args_schema, - var_kwargs_mode=var_kwargs_mode, var_kwargs_schema=var_kwargs_schema, ref=ref, metadata=metadata, @@ -3598,120 +3234,6 @@ def arguments_schema( ) -class ArgumentsV3Parameter(TypedDict, total=False): - name: Required[str] - schema: Required[CoreSchema] - mode: Literal[ - 'positional_only', - 'positional_or_keyword', - 'keyword_only', - 'var_args', - 'var_kwargs_uniform', - 'var_kwargs_unpacked_typed_dict', - ] # default positional_or_keyword - alias: Union[str, list[Union[str, int]], list[list[Union[str, int]]]] - - -def arguments_v3_parameter( - name: str, - schema: CoreSchema, - *, - mode: Literal[ - 'positional_only', - 'positional_or_keyword', - 'keyword_only', - 'var_args', - 'var_kwargs_uniform', - 'var_kwargs_unpacked_typed_dict', - ] - | None = None, - alias: str | list[str | int] | list[list[str | int]] | None = None, -) -> ArgumentsV3Parameter: - """ - Returns a schema that matches an argument parameter, e.g.: - - ```py - from pydantic_core import SchemaValidator, core_schema - - param = core_schema.arguments_v3_parameter( - name='a', schema=core_schema.str_schema(), mode='positional_only' - ) - schema = core_schema.arguments_v3_schema([param]) - v = SchemaValidator(schema) - assert v.validate_python({'a': 'hello'}) == (('hello',), {}) - ``` - - Args: - name: The name to use for the argument parameter - schema: The schema to use for the argument parameter - mode: The mode to use for the argument parameter - alias: The alias to use for the argument parameter - """ - return _dict_not_none(name=name, schema=schema, mode=mode, alias=alias) - - -class ArgumentsV3Schema(TypedDict, total=False): - type: Required[Literal['arguments-v3']] - arguments_schema: Required[list[ArgumentsV3Parameter]] - validate_by_name: bool - validate_by_alias: bool - extra_behavior: Literal['forbid', 'ignore'] # 'allow' doesn't make sense here. - ref: str - metadata: dict[str, Any] - serialization: SerSchema - - -def arguments_v3_schema( - arguments: list[ArgumentsV3Parameter], - *, - validate_by_name: bool | None = None, - validate_by_alias: bool | None = None, - extra_behavior: Literal['forbid', 'ignore'] | None = None, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, -) -> ArgumentsV3Schema: - """ - Returns a schema that matches an arguments schema, e.g.: - - ```py - from pydantic_core import SchemaValidator, core_schema - - param_a = core_schema.arguments_v3_parameter( - name='a', schema=core_schema.str_schema(), mode='positional_only' - ) - param_b = core_schema.arguments_v3_parameter( - name='kwargs', schema=core_schema.bool_schema(), mode='var_kwargs_uniform' - ) - schema = core_schema.arguments_v3_schema([param_a, param_b]) - v = SchemaValidator(schema) - assert v.validate_python({'a': 'hi', 'kwargs': {'b': True}}) == (('hi',), {'b': True}) - ``` - - This schema is currently not used by other Pydantic components. In V3, it will most likely - become the default arguments schema for the `'call'` schema. - - Args: - arguments: The arguments to use for the arguments schema. - validate_by_name: Whether to populate by the parameter names, defaults to `False`. - validate_by_alias: Whether to populate by the parameter aliases, defaults to `True`. - extra_behavior: The extra behavior to use. - ref: optional unique identifier of the schema, used to reference the schema in other places. - metadata: Any other information you want to include with the schema, not used by pydantic-core. - serialization: Custom serialization schema. - """ - return _dict_not_none( - type='arguments-v3', - arguments_schema=arguments, - validate_by_name=validate_by_name, - validate_by_alias=validate_by_alias, - extra_behavior=extra_behavior, - ref=ref, - metadata=metadata, - serialization=serialization, - ) - - class CallSchema(TypedDict, total=False): type: Required[Literal['call']] arguments_schema: Required[CoreSchema] @@ -3719,7 +3241,7 @@ class CallSchema(TypedDict, total=False): function_name: str # default function.__name__ return_schema: CoreSchema ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -3730,7 +3252,7 @@ def call_schema( function_name: str | None = None, return_schema: CoreSchema | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> CallSchema: """ @@ -3782,9 +3304,9 @@ class CustomErrorSchema(TypedDict, total=False): schema: Required[CoreSchema] custom_error_type: Required[str] custom_error_message: str - custom_error_context: dict[str, Union[str, int, float]] + custom_error_context: Dict[str, Union[str, int, float]] ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -3795,7 +3317,7 @@ def custom_error_schema( custom_error_message: str | None = None, custom_error_context: dict[str, Any] | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> CustomErrorSchema: """ @@ -3838,7 +3360,7 @@ class JsonSchema(TypedDict, total=False): type: Required[Literal['json']] schema: CoreSchema ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -3846,7 +3368,7 @@ def json_schema( schema: CoreSchema | None = None, *, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> JsonSchema: """ @@ -3891,14 +3413,14 @@ def json_schema( class UrlSchema(TypedDict, total=False): type: Required[Literal['url']] max_length: int - allowed_schemes: list[str] + allowed_schemes: List[str] host_required: bool # default False default_host: str default_port: int default_path: str strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -3910,10 +3432,9 @@ def url_schema( default_host: str | None = None, default_port: int | None = None, default_path: str | None = None, - preserve_empty_path: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> UrlSchema: """ @@ -3935,7 +3456,6 @@ def url_schema( default_host: The default host to use if the URL does not have a host default_port: The default port to use if the URL does not have a port default_path: The default path to use if the URL does not have a path - preserve_empty_path: Whether to preserve an empty path or convert it to '/', default False strict: Whether to use strict URL parsing ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -3949,7 +3469,6 @@ def url_schema( default_host=default_host, default_port=default_port, default_path=default_path, - preserve_empty_path=preserve_empty_path, strict=strict, ref=ref, metadata=metadata, @@ -3960,14 +3479,14 @@ def url_schema( class MultiHostUrlSchema(TypedDict, total=False): type: Required[Literal['multi-host-url']] max_length: int - allowed_schemes: list[str] + allowed_schemes: List[str] host_required: bool # default False default_host: str default_port: int default_path: str strict: bool ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema @@ -3979,10 +3498,9 @@ def multi_host_url_schema( default_host: str | None = None, default_port: int | None = None, default_path: str | None = None, - preserve_empty_path: bool | None = None, strict: bool | None = None, ref: str | None = None, - metadata: dict[str, Any] | None = None, + metadata: Any = None, serialization: SerSchema | None = None, ) -> MultiHostUrlSchema: """ @@ -4004,7 +3522,6 @@ def multi_host_url_schema( default_host: The default host to use if the URL does not have a host default_port: The default port to use if the URL does not have a port default_path: The default path to use if the URL does not have a path - preserve_empty_path: Whether to preserve an empty path or convert it to '/', default False strict: Whether to use strict URL parsing ref: optional unique identifier of the schema, used to reference the schema in other places metadata: Any other information you want to include with the schema, not used by pydantic-core @@ -4018,7 +3535,6 @@ def multi_host_url_schema( default_host=default_host, default_port=default_port, default_path=default_path, - preserve_empty_path=preserve_empty_path, strict=strict, ref=ref, metadata=metadata, @@ -4029,8 +3545,8 @@ def multi_host_url_schema( class DefinitionsSchema(TypedDict, total=False): type: Required[Literal['definitions']] schema: Required[CoreSchema] - definitions: Required[list[CoreSchema]] - metadata: dict[str, Any] + definitions: Required[List[CoreSchema]] + metadata: Any serialization: SerSchema @@ -4060,16 +3576,12 @@ def definitions_schema(schema: CoreSchema, definitions: list[CoreSchema]) -> Def class DefinitionReferenceSchema(TypedDict, total=False): type: Required[Literal['definition-ref']] schema_ref: Required[str] - ref: str - metadata: dict[str, Any] + metadata: Any serialization: SerSchema def definition_reference_schema( - schema_ref: str, - ref: str | None = None, - metadata: dict[str, Any] | None = None, - serialization: SerSchema | None = None, + schema_ref: str, metadata: Any = None, serialization: SerSchema | None = None ) -> DefinitionReferenceSchema: """ Returns a schema that points to a schema stored in "definitions", this is useful for nested recursive @@ -4094,9 +3606,7 @@ def definition_reference_schema( metadata: Any other information you want to include with the schema, not used by pydantic-core serialization: Custom serialization schema """ - return _dict_not_none( - type='definition-ref', schema_ref=schema_ref, ref=ref, metadata=metadata, serialization=serialization - ) + return _dict_not_none(type='definition-ref', schema_ref=schema_ref, metadata=metadata, serialization=serialization) MYPY = False @@ -4104,7 +3614,6 @@ MYPY = False # union which kills performance not just for pydantic, but even for code using pydantic if not MYPY: CoreSchema = Union[ - InvalidSchema, AnySchema, NoneSchema, BoolSchema, @@ -4118,13 +3627,12 @@ if not MYPY: DatetimeSchema, TimedeltaSchema, LiteralSchema, - MissingSentinelSchema, - EnumSchema, IsInstanceSchema, IsSubclassSchema, CallableSchema, ListSchema, - TupleSchema, + TuplePositionalSchema, + TupleVariableSchema, SetSchema, FrozenSetSchema, GeneratorSchema, @@ -4146,7 +3654,6 @@ if not MYPY: DataclassArgsSchema, DataclassSchema, ArgumentsSchema, - ArgumentsV3Schema, CallSchema, CustomErrorSchema, JsonSchema, @@ -4155,7 +3662,6 @@ if not MYPY: DefinitionsSchema, DefinitionReferenceSchema, UuidSchema, - ComplexSchema, ] elif False: CoreSchema: TypeAlias = Mapping[str, Any] @@ -4163,7 +3669,6 @@ elif False: # to update this, call `pytest -k test_core_schema_type_literal` and copy the output CoreSchemaType = Literal[ - 'invalid', 'any', 'none', 'bool', @@ -4177,13 +3682,12 @@ CoreSchemaType = Literal[ 'datetime', 'timedelta', 'literal', - 'missing-sentinel', - 'enum', 'is-instance', 'is-subclass', 'callable', 'list', - 'tuple', + 'tuple-positional', + 'tuple-variable', 'set', 'frozenset', 'generator', @@ -4205,7 +3709,6 @@ CoreSchemaType = Literal[ 'dataclass-args', 'dataclass', 'arguments', - 'arguments-v3', 'call', 'custom-error', 'json', @@ -4214,7 +3717,6 @@ CoreSchemaType = Literal[ 'definitions', 'definition-ref', 'uuid', - 'complex', ] CoreSchemaFieldType = Literal['model-field', 'dataclass-field', 'typed-dict-field', 'computed-field'] @@ -4226,7 +3728,6 @@ ErrorType = Literal[ 'no_such_attribute', 'json_invalid', 'json_type', - 'needs_python_object', 'recursion_loop', 'missing', 'frozen_field', @@ -4238,7 +3739,6 @@ ErrorType = Literal[ 'model_attributes_type', 'dataclass_type', 'dataclass_exact_type', - 'default_factory_not_called', 'none_required', 'greater_than', 'greater_than_equal', @@ -4262,7 +3762,6 @@ ErrorType = Literal[ 'list_type', 'tuple_type', 'set_type', - 'set_item_not_hashable', 'bool_type', 'bool_parsing', 'int_type', @@ -4274,11 +3773,9 @@ ErrorType = Literal[ 'bytes_type', 'bytes_too_short', 'bytes_too_long', - 'bytes_invalid_encoding', 'value_error', 'assertion_error', 'literal_error', - 'missing_sentinel_error', 'date_type', 'date_parsing', 'date_from_datetime_parsing', @@ -4290,7 +3787,6 @@ ErrorType = Literal[ 'datetime_type', 'datetime_parsing', 'datetime_object_invalid', - 'datetime_from_date_parsing', 'datetime_past', 'datetime_future', 'timezone_naive', @@ -4324,8 +3820,6 @@ ErrorType = Literal[ 'decimal_max_digits', 'decimal_max_places', 'decimal_whole_digits', - 'complex_type', - 'complex_str_parsing', ] @@ -4368,7 +3862,7 @@ def field_after_validator_function(function: WithInfoValidatorFunction, field_na @deprecated('`general_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.') def general_after_validator_function(*args, **kwargs): warnings.warn( - '`general_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.', + '`with_info_after_validator_function` is deprecated, use `with_info_after_validator_function` instead.', DeprecationWarning, ) return with_info_after_validator_function(*args, **kwargs) diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/__init__.cpython-312.pyc index 0a78465f..449a2f6e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/main.cpython-312.pyc index 87d11f37..4bc7e3bb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/sources.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/sources.cpython-312.pyc index c714ac41..2b619c54 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/sources.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/sources.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/utils.cpython-312.pyc index 96c7c71a..947aca4d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/version.cpython-312.pyc index c081c9d0..ccaeb83a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/__init__.cpython-312.pyc index acda9a50..81fca558 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/filter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/filter.cpython-312.pyc index 99ea4bea..7e221f2d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/filter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/filter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/formatter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/formatter.cpython-312.pyc index 36a6b748..d0cb30cb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/formatter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/formatter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/lexer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/lexer.cpython-312.pyc index 7c3fbdb1..df49b554 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/lexer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/lexer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/modeline.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/modeline.cpython-312.pyc index 80b50db4..453b6765 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/modeline.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/modeline.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/plugin.cpython-312.pyc index 95c3ef5c..970ba60e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/plugin.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/regexopt.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/regexopt.cpython-312.pyc index fea4616e..a0ad9cfb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/regexopt.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/regexopt.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/style.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/style.cpython-312.pyc index 3a25edb0..f33427ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/style.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/style.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/token.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/token.cpython-312.pyc index 50b426df..06b18884 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/token.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/token.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/unistring.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/unistring.cpython-312.pyc index ffada4ac..21d57184 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/unistring.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/unistring.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/util.cpython-312.pyc index 85a5e80b..6daca8d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/filters/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/filters/__pycache__/__init__.cpython-312.pyc index 61cbd188..f954610a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/filters/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/filters/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/__init__.cpython-312.pyc index 50178757..8bc2d035 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/_mapping.cpython-312.pyc index ef792eb3..980a2089 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/_mapping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/html.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/html.cpython-312.pyc index 71d6d7b0..a84f6073 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/html.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/formatters/__pycache__/html.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/__init__.cpython-312.pyc index 2b5c0226..03487260 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-312.pyc index cb64f89e..a3f2c346 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_css_builtins.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-312.pyc index 6c188465..728501ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_lua_builtins.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_mapping.cpython-312.pyc index 676f2cb1..e77f3ea9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_mapping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-312.pyc index ef37291a..03035556 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/_scheme_builtins.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/actionscript.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/actionscript.cpython-312.pyc index b93e2d37..2b75fe9b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/actionscript.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/actionscript.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/agile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/agile.cpython-312.pyc index 72ff9e8a..9d84352e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/agile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/agile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/css.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/css.cpython-312.pyc index ba547685..f2f86a9f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/css.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/css.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/d.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/d.cpython-312.pyc index 886e0b97..f88c7927 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/d.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/d.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/data.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/data.cpython-312.pyc index 8033c4f2..0bf8ec6c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/data.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/data.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/factor.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/factor.cpython-312.pyc index 3a97f1d3..4177a04c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/factor.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/factor.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/html.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/html.cpython-312.pyc index 24bceb50..15099ab5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/html.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/html.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/iolang.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/iolang.cpython-312.pyc index 197dcb3e..4cda6dcc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/iolang.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/iolang.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/javascript.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/javascript.cpython-312.pyc index 266a0acf..098b6966 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/javascript.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/javascript.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/jvm.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/jvm.cpython-312.pyc index 9ad238ac..1c5ef18f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/jvm.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/jvm.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/lisp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/lisp.cpython-312.pyc index 243cf40a..a4348e91 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/lisp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/lisp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/perl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/perl.cpython-312.pyc index bd73b9e0..0f2f5970 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/perl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/perl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/php.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/php.cpython-312.pyc index 782b947e..a719d0b7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/php.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/php.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/python.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/python.cpython-312.pyc index 427a9c93..d90b6262 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/python.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/python.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/ruby.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/ruby.cpython-312.pyc index 9da7d592..89fa42b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/ruby.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/ruby.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/scripting.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/scripting.cpython-312.pyc index 34a8dd58..838e6c0d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/scripting.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/scripting.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/tcl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/tcl.cpython-312.pyc index 0963bff4..e08d00c4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/tcl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/tcl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/web.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/web.cpython-312.pyc index a735e659..ee74e363 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/web.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/web.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/webmisc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/webmisc.cpython-312.pyc index f5e4d1f1..6743ffda 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/webmisc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/lexers/__pycache__/webmisc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/__init__.cpython-312.pyc index 08ab876f..1693bc19 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/_mapping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/_mapping.cpython-312.pyc index 1e431ae7..5500be90 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/_mapping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/_mapping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/default.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/default.cpython-312.pyc index 2553eea5..2c9cf0aa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/default.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pygments/styles/__pycache__/default.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/__init__.cpython-312.pyc index 5f16911b..33aacb84 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/_auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/_auth.cpython-312.pyc index 0301067c..d15007c9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/_auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/_auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/charset.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/charset.cpython-312.pyc index 0441f169..9706b51b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/charset.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/charset.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/connections.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/connections.cpython-312.pyc index 002d3679..bc3f7733 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/connections.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/connections.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/converters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/converters.cpython-312.pyc index 94915045..6b92b3f0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/converters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/converters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/cursors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/cursors.cpython-312.pyc index 32002978..41ff71b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/cursors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/cursors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/err.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/err.cpython-312.pyc index 56ede1a0..59dc56ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/err.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/err.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/optionfile.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/optionfile.cpython-312.pyc index ec390210..fd580e80 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/optionfile.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/optionfile.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/protocol.cpython-312.pyc index d86895b7..7d6f5f1e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/times.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/times.cpython-312.pyc index dbe092a1..5e1e717b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/times.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/__pycache__/times.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CLIENT.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CLIENT.cpython-312.pyc index 8be34637..60a09cb5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CLIENT.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CLIENT.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/COMMAND.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/COMMAND.cpython-312.pyc index e158ee8c..c5566d47 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/COMMAND.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/COMMAND.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CR.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CR.cpython-312.pyc index 6ee63076..7c8272b3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CR.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/CR.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/ER.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/ER.cpython-312.pyc index 96e36387..af84e972 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/ER.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/ER.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/FIELD_TYPE.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/FIELD_TYPE.cpython-312.pyc index 775da774..304984b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/FIELD_TYPE.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/FIELD_TYPE.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/SERVER_STATUS.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/SERVER_STATUS.cpython-312.pyc index f7f2dab4..e01889a3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/SERVER_STATUS.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/SERVER_STATUS.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/__init__.cpython-312.pyc index 7c1e4fb0..fb19c749 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pymysql/constants/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc index 995dc8ae..0b646cf8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc index 525cb088..7a4d8bf3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc index 4b9be985..3c53d731 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/hotp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc index 3345a265..7e59761d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/otp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc index a80630f6..0def662b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/totp.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc index 20c5771f..25997b66 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc index 67a4daac..a29097a7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc index 3e71e64d..367df05d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/pyotp/contrib/__pycache__/steam.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/licenses/LICENSE rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/METADATA similarity index 73% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/METADATA rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/METADATA index 63192825..02147416 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/METADATA +++ b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/METADATA @@ -1,50 +1,57 @@ -Metadata-Version: 2.4 +Metadata-Version: 2.1 Name: pytest -Version: 9.0.1 +Version: 7.4.3 Summary: pytest: simple powerful testing with Python -Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin, Others (See AUTHORS) -License-Expression: MIT +Home-page: https://docs.pytest.org/en/latest/ +Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others +License: MIT Project-URL: Changelog, https://docs.pytest.org/en/stable/changelog.html -Project-URL: Contact, https://docs.pytest.org/en/stable/contact.html -Project-URL: Funding, https://docs.pytest.org/en/stable/sponsor.html -Project-URL: Homepage, https://docs.pytest.org/en/latest/ +Project-URL: Twitter, https://twitter.com/pytestdotorg Project-URL: Source, https://github.com/pytest-dev/pytest Project-URL: Tracker, https://github.com/pytest-dev/pytest/issues Keywords: test,unittest +Platform: unix +Platform: linux +Platform: osx +Platform: cygwin +Platform: win32 Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers -Classifier: Operating System :: MacOS +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX -Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities -Requires-Python: >=3.10 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE -Requires-Dist: colorama>=0.4; sys_platform == "win32" -Requires-Dist: exceptiongroup>=1; python_version < "3.11" -Requires-Dist: iniconfig>=1.0.1 -Requires-Dist: packaging>=22 -Requires-Dist: pluggy<2,>=1.5 -Requires-Dist: pygments>=2.7.2 -Requires-Dist: tomli>=1; python_version < "3.11" -Provides-Extra: dev -Requires-Dist: argcomplete; extra == "dev" -Requires-Dist: attrs>=19.2; extra == "dev" -Requires-Dist: hypothesis>=3.56; extra == "dev" -Requires-Dist: mock; extra == "dev" -Requires-Dist: requests; extra == "dev" -Requires-Dist: setuptools; extra == "dev" -Requires-Dist: xmlschema; extra == "dev" -Dynamic: license-file +Requires-Dist: iniconfig +Requires-Dist: packaging +Requires-Dist: pluggy <2.0,>=0.12 +Requires-Dist: exceptiongroup >=1.0.0rc8 ; python_version < "3.11" +Requires-Dist: tomli >=1.0.0 ; python_version < "3.11" +Requires-Dist: importlib-metadata >=0.12 ; python_version < "3.8" +Requires-Dist: colorama ; sys_platform == "win32" +Provides-Extra: testing +Requires-Dist: argcomplete ; extra == 'testing' +Requires-Dist: attrs >=19.2.0 ; extra == 'testing' +Requires-Dist: hypothesis >=3.56 ; extra == 'testing' +Requires-Dist: mock ; extra == 'testing' +Requires-Dist: nose ; extra == 'testing' +Requires-Dist: pygments >=2.7.2 ; extra == 'testing' +Requires-Dist: requests ; extra == 'testing' +Requires-Dist: setuptools ; extra == 'testing' +Requires-Dist: xmlschema ; extra == 'testing' .. image:: https://github.com/pytest-dev/pytest/raw/main/doc/en/img/pytest_logo_curves.svg :target: https://docs.pytest.org/en/stable/ @@ -68,13 +75,16 @@ Dynamic: license-file :target: https://codecov.io/gh/pytest-dev/pytest :alt: Code coverage Status -.. image:: https://github.com/pytest-dev/pytest/actions/workflows/test.yml/badge.svg +.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest .. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main :alt: pre-commit.ci status +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg :target: https://www.codetriage.com/pytest-dev/pytest @@ -127,7 +137,7 @@ To execute it:: ========================== 1 failed in 0.04 seconds =========================== -Thanks to ``pytest``'s detailed assertion introspection, you can simply use plain ``assert`` statements. See `getting-started `_ for more examples. +Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. Features @@ -142,12 +152,12 @@ Features - `Modular fixtures `_ for managing small or parametrized long-lived test resources -- Can run `unittest `_ (or trial) - test suites out of the box +- Can run `unittest `_ (or trial), + `nose `_ test suites out of the box -- Python 3.10+ or PyPy3 +- Python 3.7+ or PyPy3 -- Rich plugin architecture, with over 1300+ `external plugins `_ and thriving community +- Rich plugin architecture, with over 850+ `external plugins `_ and thriving community Documentation diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/RECORD new file mode 100644 index 00000000..b8e61a88 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/RECORD @@ -0,0 +1,154 @@ +../../../bin/py.test,sha256=YrujSlw57P6lYgfcoF2OIoA2hiYZx1GE8qoTsEll7pM,233 +../../../bin/pytest,sha256=YrujSlw57P6lYgfcoF2OIoA2hiYZx1GE8qoTsEll7pM,233 +__pycache__/py.cpython-312.pyc,, +_pytest/__init__.py,sha256=4K-_CZFPuvNtJXNwxyTtnbmpjVkSb-dC75bs29Sg0d4,356 +_pytest/__pycache__/__init__.cpython-312.pyc,, +_pytest/__pycache__/_argcomplete.cpython-312.pyc,, +_pytest/__pycache__/_version.cpython-312.pyc,, +_pytest/__pycache__/cacheprovider.cpython-312.pyc,, +_pytest/__pycache__/capture.cpython-312.pyc,, +_pytest/__pycache__/compat.cpython-312.pyc,, +_pytest/__pycache__/debugging.cpython-312.pyc,, +_pytest/__pycache__/deprecated.cpython-312.pyc,, +_pytest/__pycache__/doctest.cpython-312.pyc,, +_pytest/__pycache__/faulthandler.cpython-312.pyc,, +_pytest/__pycache__/fixtures.cpython-312.pyc,, +_pytest/__pycache__/freeze_support.cpython-312.pyc,, +_pytest/__pycache__/helpconfig.cpython-312.pyc,, +_pytest/__pycache__/hookspec.cpython-312.pyc,, +_pytest/__pycache__/junitxml.cpython-312.pyc,, +_pytest/__pycache__/legacypath.cpython-312.pyc,, +_pytest/__pycache__/logging.cpython-312.pyc,, +_pytest/__pycache__/main.cpython-312.pyc,, +_pytest/__pycache__/monkeypatch.cpython-312.pyc,, +_pytest/__pycache__/nodes.cpython-312.pyc,, +_pytest/__pycache__/nose.cpython-312.pyc,, +_pytest/__pycache__/outcomes.cpython-312.pyc,, +_pytest/__pycache__/pastebin.cpython-312.pyc,, +_pytest/__pycache__/pathlib.cpython-312.pyc,, +_pytest/__pycache__/pytester.cpython-312.pyc,, +_pytest/__pycache__/pytester_assertions.cpython-312.pyc,, +_pytest/__pycache__/python.cpython-312.pyc,, +_pytest/__pycache__/python_api.cpython-312.pyc,, +_pytest/__pycache__/python_path.cpython-312.pyc,, +_pytest/__pycache__/recwarn.cpython-312.pyc,, +_pytest/__pycache__/reports.cpython-312.pyc,, +_pytest/__pycache__/runner.cpython-312.pyc,, +_pytest/__pycache__/scope.cpython-312.pyc,, +_pytest/__pycache__/setuponly.cpython-312.pyc,, +_pytest/__pycache__/setupplan.cpython-312.pyc,, +_pytest/__pycache__/skipping.cpython-312.pyc,, +_pytest/__pycache__/stash.cpython-312.pyc,, +_pytest/__pycache__/stepwise.cpython-312.pyc,, +_pytest/__pycache__/terminal.cpython-312.pyc,, +_pytest/__pycache__/threadexception.cpython-312.pyc,, +_pytest/__pycache__/timing.cpython-312.pyc,, +_pytest/__pycache__/tmpdir.cpython-312.pyc,, +_pytest/__pycache__/unittest.cpython-312.pyc,, +_pytest/__pycache__/unraisableexception.cpython-312.pyc,, +_pytest/__pycache__/warning_types.cpython-312.pyc,, +_pytest/__pycache__/warnings.cpython-312.pyc,, +_pytest/_argcomplete.py,sha256=YpnQdf25q066cF9hAQKXIw55HmAx-HWLOPg3wKmT1so,3794 +_pytest/_code/__init__.py,sha256=S_sBUyBt-DdDWGJKJviYTWFHhhDFBM7pIMaENaocwaM,483 +_pytest/_code/__pycache__/__init__.cpython-312.pyc,, +_pytest/_code/__pycache__/code.cpython-312.pyc,, +_pytest/_code/__pycache__/source.cpython-312.pyc,, +_pytest/_code/code.py,sha256=q74apRbmc8m9UYFSRZRRIIVzHnfG3JsCKNc29hsAmIc,46740 +_pytest/_code/source.py,sha256=URY36RBYU0mtBZF4HQoNC0OqVRjmHLetIrjNnvzjh9g,7436 +_pytest/_io/__init__.py,sha256=NWs125Ln6IqP5BZNw-V2iN_yYPwGM7vfrAP5ta6MhPA,154 +_pytest/_io/__pycache__/__init__.cpython-312.pyc,, +_pytest/_io/__pycache__/saferepr.cpython-312.pyc,, +_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc,, +_pytest/_io/__pycache__/wcwidth.cpython-312.pyc,, +_pytest/_io/saferepr.py,sha256=r222Mkvyl_TXXQvGqGURDaQZBH55l0y7VDxyzBqNw9k,5394 +_pytest/_io/terminalwriter.py,sha256=aLbaFJ3KO-B8ZgeWonQ4-dZEcAt1ReX7xAW5BRoaODE,8152 +_pytest/_io/wcwidth.py,sha256=YhE3To-vBI7udLtV4B-g-04S3l8VoRD5ki935QipmJA,1253 +_pytest/_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +_pytest/_py/__pycache__/__init__.cpython-312.pyc,, +_pytest/_py/__pycache__/error.cpython-312.pyc,, +_pytest/_py/__pycache__/path.cpython-312.pyc,, +_pytest/_py/error.py,sha256=Ocm93fwIaLxWvXBQV-0-GbItURWOCdQg8uG6994QhLI,3014 +_pytest/_py/path.py,sha256=Fc6aZ7rvsB-7xiM5ZOJr6cHLBh3nNnuPbxkXwbRkNjE,49149 +_pytest/_version.py,sha256=zXCqUT2NXJ-8bP-KIuQaloadpUbraJC_vHATA4CZu4A,411 +_pytest/assertion/__init__.py,sha256=9eQINJEUWPPvHx3neW5SI6SWf0ntPp2iQXihzPGJA9Q,6458 +_pytest/assertion/__pycache__/__init__.cpython-312.pyc,, +_pytest/assertion/__pycache__/rewrite.cpython-312.pyc,, +_pytest/assertion/__pycache__/truncate.cpython-312.pyc,, +_pytest/assertion/__pycache__/util.cpython-312.pyc,, +_pytest/assertion/rewrite.py,sha256=M9z9TC2XOS7A__bETdiGh86mFcd5GO3vW8h84fNQzwk,47566 +_pytest/assertion/truncate.py,sha256=68YnKJcR34tkKU146CzFmiWXdNE6NmKgBXpQb_HNUSI,4382 +_pytest/assertion/util.py,sha256=4i5ZfojA1CX-RFjYnyGpHZzXbR4ql9J11okAj9oiIB8,18009 +_pytest/cacheprovider.py,sha256=2uTxVGIdcXPUsIqGA2owpugK3THrVoaBBA7VvWlxakc,21659 +_pytest/capture.py,sha256=5p7ak0e5XBi0qfcaU0ZPKO47cy-vGoGzn6S7Os-M1Gg,34737 +_pytest/compat.py,sha256=ta-0FxRsHe4N2FRsDRG8RXi-_lJeQXevQCVo_6bUbzE,13637 +_pytest/config/__init__.py,sha256=Behe_CVnLJpkd8Ct4u8hXkx5pE9quk6jcgNZGodh2LU,64177 +_pytest/config/__pycache__/__init__.cpython-312.pyc,, +_pytest/config/__pycache__/argparsing.cpython-312.pyc,, +_pytest/config/__pycache__/compat.cpython-312.pyc,, +_pytest/config/__pycache__/exceptions.cpython-312.pyc,, +_pytest/config/__pycache__/findpaths.cpython-312.pyc,, +_pytest/config/argparsing.py,sha256=VcBUsFlK2Th9dtAwjD5UIYquXkFYZtbJpOAWAFLiBw4,21225 +_pytest/config/compat.py,sha256=fj_LbkWm9yJKeY64C_8AeKqbYHr5k9MDrZugTJs8AWI,2393 +_pytest/config/exceptions.py,sha256=21I5MARt26OLRmgvaAPu0BblFgYZXp2cxNZBpRRciAE,260 +_pytest/config/findpaths.py,sha256=B1LaW1JunqZXYsPPXhZUGU1_rOziO7ybVbnacVbLYgY,7615 +_pytest/debugging.py,sha256=cQxelK2gKBogv_c4e9q0xybHCcbsdLJmz4L5WBE68cs,13498 +_pytest/deprecated.py,sha256=Me3lX-KEKCxpSjPh9qNPDKMX16eltg5ben0Zn-Id0qg,5487 +_pytest/doctest.py,sha256=ec6FDUBzjLsOvjyKba2XazIp_ZnfkigmjrtA08EREn4,26845 +_pytest/faulthandler.py,sha256=psYlEcaVR413KSTA40GB5pMdv9uSYuVVu70r9H5OSdI,3091 +_pytest/fixtures.py,sha256=KdU2XEdUm0VdmN9zd9oM8VCknzgYYH8oSrwXvTD6GPs,67085 +_pytest/freeze_support.py,sha256=Wmx-CJvzCbOAK3brpNJO6X_WHXcCA6Tr6-Tb_tjIyVQ,1339 +_pytest/helpconfig.py,sha256=gbtjfcN-anYzCbVTC43jsOe3psMjWNsx7xB0z0SW7kQ,8658 +_pytest/hookspec.py,sha256=pSGZ5hQeI7yIbLWdm_uXRHDkMGLH44wrMOWjUammxos,32558 +_pytest/junitxml.py,sha256=AcY_LVtZXsXjWrMY_XhxoLg1JpZbWUqRYndGn8bhkQc,25709 +_pytest/legacypath.py,sha256=CRBfhIuToQNTFDzA6fdhTgnQFVN-V_EQOPOY7KUk2HE,16929 +_pytest/logging.py,sha256=fazFyIeZkkH812KqOBWrz-MUra7ARY9SrtRk9EtXW88,34130 +_pytest/main.py,sha256=-BRQXXwMrbTMtSdD0chYyDHHHzuHPsIUBQnzCckYxv0,32658 +_pytest/mark/__init__.py,sha256=tfeYUQwpIDqfcvZWOjcb07F1mnyoeqXLtjX-ZWTVg1Q,8468 +_pytest/mark/__pycache__/__init__.cpython-312.pyc,, +_pytest/mark/__pycache__/expression.cpython-312.pyc,, +_pytest/mark/__pycache__/structures.cpython-312.pyc,, +_pytest/mark/expression.py,sha256=Se6Cl15lBb92RGa2g30pLpi9ozn72PKjiTS6B_bTeNg,6507 +_pytest/mark/structures.py,sha256=yvqwKM0lx6xdOP4iI2YIvK1yEHHpOHfSf11sEYb-w9U,21219 +_pytest/monkeypatch.py,sha256=vT0wc75BgW4ElVtDhflPqvbyEc3ndxaz28EYcu_HLM0,14857 +_pytest/nodes.py,sha256=oRPPZKT224rsIZQ71A1e5eWfgjmFUkmFReZsyEd9r68,26694 +_pytest/nose.py,sha256=mjb1d2J0PlCc7YnQvfAt3LhCMBGjsPrx5MZX59Ri-mU,1688 +_pytest/outcomes.py,sha256=tYW9z-32fexDcGSI0LGoOKCtU9x1qBZsFKbArv09J6U,10256 +_pytest/pastebin.py,sha256=l-Jm8hJ_zuT_VdilatBUzvtuAfAN27Oxs7nS1UM8d-M,3949 +_pytest/pathlib.py,sha256=r_Vbw3FQ6FGjAs6KcTRur2ViH9xjCVU6e_II9wbMgWY,27059 +_pytest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +_pytest/pytester.py,sha256=SAqqj0gYfzCsjLnlHKhe_D6LdtXvmmENrSdHFoYWSrQ,62001 +_pytest/pytester_assertions.py,sha256=1BW3jDRSiHqGqdzmGSc7LfQt7nwc0w1lVRHMHHcFEQs,2327 +_pytest/python.py,sha256=5BVlKnGb02ixGDIOi0PJgAKKnx8fA2Izgs7R0ffpT2Q,71399 +_pytest/python_api.py,sha256=zQunfXCbgOE69O0DIu1ACoQ4Fm2w9J7OtxFvjjTQ0pk,38534 +_pytest/python_path.py,sha256=TD7qJJ0S91XctgtpIjaq21DWh3rlxxVwXMvrjsjevaU,709 +_pytest/recwarn.py,sha256=KOUdXBVOc3ZqHDvOCZSVxBbT4SUezs68uMaWH0ujasA,10930 +_pytest/reports.py,sha256=TVt5M3EtGrZfROJF23U8gX5_YDjumS34vlw3VXHPbhc,20840 +_pytest/runner.py,sha256=7BD2m-Rhpf5b2DlT3e1uvZUWqUGtlE6ADBff6n21sO4,18447 +_pytest/scope.py,sha256=dNx6zm8ZWPrwsz8v7sAoemp537tEsdl1-_EOegPrwYE,2882 +_pytest/setuponly.py,sha256=KEmb8N4On3_yH1T5cyo9_QYbxWgm3H3QkvshDf77z3o,3261 +_pytest/setupplan.py,sha256=0HVsIdKbYfJEbAiwidBfQZwNE7RziZ1BI0vrFeohAOc,1214 +_pytest/skipping.py,sha256=P4BvQ73DnQhI0s7ezGuc2F6h3APigHKLkELjpxlfhDs,10200 +_pytest/stash.py,sha256=x_ywAeTfX84tI0vUyXmKmCDxwcXIETqnCrVkOUAtqQ8,3055 +_pytest/stepwise.py,sha256=oaLyCsqteCgi4QEu_rMeJq7adUhaBv3aINQSETQZ0d8,4714 +_pytest/terminal.py,sha256=Fh9Bb-8YsyGPzJiQvtFa5gMEECHUDBlwO3NQ6e56MYg,53509 +_pytest/threadexception.py,sha256=TEohIXnQcof6D7cg10Ly4oMSRgHLCNsXPF6Du9FV4K8,2915 +_pytest/timing.py,sha256=vufB2Wrk_Bf4uol6U16WfpikCBttEmmtGKBNBshPN_k,375 +_pytest/tmpdir.py,sha256=NfyrD4hF3axsMBx74P9K-PfhlPXyuRpiqScolKLZW5k,11708 +_pytest/unittest.py,sha256=fvKUT_OBB0nHfog5ApCzqRBwqwuKtn6qz503gQHALag,14809 +_pytest/unraisableexception.py,sha256=FJmftKtjMHmUnlYyg1o9B_oQjvA_U0p1ABSNlKx1K2I,3191 +_pytest/warning_types.py,sha256=ZqFZR7e0CNeb6V6lXf37qdTKOaKI5TsqkDgbzYtwgds,4474 +_pytest/warnings.py,sha256=pBY3hIrOZobaWk9vHgW_ac44jXYhlyUuferDOhwaMGI,5070 +py.py,sha256=UEzy74zelHEaKeqgb96pBWOmeEEtqhOszJdX7UuwTsQ,263 +pytest-7.4.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pytest-7.4.3.dist-info/LICENSE,sha256=yoNqX57Mo7LzUCMPqiCkj7ixRWU7VWjXhIYt-GRwa5s,1091 +pytest-7.4.3.dist-info/METADATA,sha256=IDNHBfn17ormmiUNJyDOG0Ew8n80u23MVcbxVTTNFp4,7946 +pytest-7.4.3.dist-info/RECORD,, +pytest-7.4.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pytest-7.4.3.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92 +pytest-7.4.3.dist-info/entry_points.txt,sha256=8IPrHPH3LNZQ7v5tNEOcNTZYk_SheNg64jsTM9erqL4,77 +pytest-7.4.3.dist-info/top_level.txt,sha256=yyhjvmXH7-JOaoQIdmNQHPuoBCxOyXS3jIths_6C8A4,18 +pytest/__init__.py,sha256=_yW6iMPyE3fU_LiXPCwILu-rWcMYouVSmWQ49S4vmkc,5237 +pytest/__main__.py,sha256=PJoBBgRxbsenpjfDenJmkO0-UGzTad7Htcxgstu4g30,116 +pytest/__pycache__/__init__.cpython-312.pyc,, +pytest/__pycache__/__main__.cpython-312.pyc,, +pytest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/REQUESTED similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/REQUESTED rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/REQUESTED diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/WHEEL similarity index 65% rename from Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/WHEEL index e7fa31b6..7e688737 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: setuptools (80.9.0) +Generator: bdist_wheel (0.41.2) Root-Is-Purelib: true Tag: py3-none-any diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/entry_points.txt b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/entry_points.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/entry_points.txt rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/entry_points.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/top_level.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/top_level.txt rename to Backend/venv/lib/python3.12/site-packages/pytest-7.4.3.dist-info/top_level.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/RECORD deleted file mode 100644 index a9739773..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/RECORD +++ /dev/null @@ -1,158 +0,0 @@ -../../../bin/py.test,sha256=YrujSlw57P6lYgfcoF2OIoA2hiYZx1GE8qoTsEll7pM,233 -../../../bin/pytest,sha256=YrujSlw57P6lYgfcoF2OIoA2hiYZx1GE8qoTsEll7pM,233 -__pycache__/py.cpython-312.pyc,, -_pytest/__init__.py,sha256=4IdRJhnW5XG2KlaJkOxn5_TC9WeQ5tXDSF7tbb4vEso,391 -_pytest/__pycache__/__init__.cpython-312.pyc,, -_pytest/__pycache__/_argcomplete.cpython-312.pyc,, -_pytest/__pycache__/_version.cpython-312.pyc,, -_pytest/__pycache__/cacheprovider.cpython-312.pyc,, -_pytest/__pycache__/capture.cpython-312.pyc,, -_pytest/__pycache__/compat.cpython-312.pyc,, -_pytest/__pycache__/debugging.cpython-312.pyc,, -_pytest/__pycache__/deprecated.cpython-312.pyc,, -_pytest/__pycache__/doctest.cpython-312.pyc,, -_pytest/__pycache__/faulthandler.cpython-312.pyc,, -_pytest/__pycache__/fixtures.cpython-312.pyc,, -_pytest/__pycache__/freeze_support.cpython-312.pyc,, -_pytest/__pycache__/helpconfig.cpython-312.pyc,, -_pytest/__pycache__/hookspec.cpython-312.pyc,, -_pytest/__pycache__/junitxml.cpython-312.pyc,, -_pytest/__pycache__/legacypath.cpython-312.pyc,, -_pytest/__pycache__/logging.cpython-312.pyc,, -_pytest/__pycache__/main.cpython-312.pyc,, -_pytest/__pycache__/monkeypatch.cpython-312.pyc,, -_pytest/__pycache__/nodes.cpython-312.pyc,, -_pytest/__pycache__/outcomes.cpython-312.pyc,, -_pytest/__pycache__/pastebin.cpython-312.pyc,, -_pytest/__pycache__/pathlib.cpython-312.pyc,, -_pytest/__pycache__/pytester.cpython-312.pyc,, -_pytest/__pycache__/pytester_assertions.cpython-312.pyc,, -_pytest/__pycache__/python.cpython-312.pyc,, -_pytest/__pycache__/python_api.cpython-312.pyc,, -_pytest/__pycache__/raises.cpython-312.pyc,, -_pytest/__pycache__/recwarn.cpython-312.pyc,, -_pytest/__pycache__/reports.cpython-312.pyc,, -_pytest/__pycache__/runner.cpython-312.pyc,, -_pytest/__pycache__/scope.cpython-312.pyc,, -_pytest/__pycache__/setuponly.cpython-312.pyc,, -_pytest/__pycache__/setupplan.cpython-312.pyc,, -_pytest/__pycache__/skipping.cpython-312.pyc,, -_pytest/__pycache__/stash.cpython-312.pyc,, -_pytest/__pycache__/stepwise.cpython-312.pyc,, -_pytest/__pycache__/subtests.cpython-312.pyc,, -_pytest/__pycache__/terminal.cpython-312.pyc,, -_pytest/__pycache__/threadexception.cpython-312.pyc,, -_pytest/__pycache__/timing.cpython-312.pyc,, -_pytest/__pycache__/tmpdir.cpython-312.pyc,, -_pytest/__pycache__/tracemalloc.cpython-312.pyc,, -_pytest/__pycache__/unittest.cpython-312.pyc,, -_pytest/__pycache__/unraisableexception.cpython-312.pyc,, -_pytest/__pycache__/warning_types.cpython-312.pyc,, -_pytest/__pycache__/warnings.cpython-312.pyc,, -_pytest/_argcomplete.py,sha256=gh0pna66p4LVb2D8ST4568WGxvdInGT43m6slYhqNqU,3776 -_pytest/_code/__init__.py,sha256=BKbowoYQADKjAJmTWdQ8SSQLbBBsh0-dZj3TGjtn6yM,521 -_pytest/_code/__pycache__/__init__.cpython-312.pyc,, -_pytest/_code/__pycache__/code.cpython-312.pyc,, -_pytest/_code/__pycache__/source.cpython-312.pyc,, -_pytest/_code/code.py,sha256=Hsem3E7nEdsaPnYxq26g0qXXP9PGSf4-R-1pZZmTV2w,55836 -_pytest/_code/source.py,sha256=VvNzWHYfT96SO128m7tfIdagZ35yug46bWfgXH_5Vp8,7772 -_pytest/_io/__init__.py,sha256=pkLF29VEFr6Dlr3eOtJL8sf47RLFt1Jf4X1DZBPlYmc,190 -_pytest/_io/__pycache__/__init__.cpython-312.pyc,, -_pytest/_io/__pycache__/pprint.cpython-312.pyc,, -_pytest/_io/__pycache__/saferepr.cpython-312.pyc,, -_pytest/_io/__pycache__/terminalwriter.cpython-312.pyc,, -_pytest/_io/__pycache__/wcwidth.cpython-312.pyc,, -_pytest/_io/pprint.py,sha256=GLBKL6dmnRr92GnVMkNzMkKqx08Op7tdJSeh3AewonY,19622 -_pytest/_io/saferepr.py,sha256=Hhx5F-75iz03hdk-WO86Bmy9RBuRHsuJj-YUzozfrgo,4082 -_pytest/_io/terminalwriter.py,sha256=K0pB1pfAvrKBWFARKSobQ4wWi0pdmiBaGb3a_v_AAjQ,8994 -_pytest/_io/wcwidth.py,sha256=cUEJ74UhweICwbKvU2q6noZcNgD0QlBEB9CfakGYaqA,1289 -_pytest/_py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -_pytest/_py/__pycache__/__init__.cpython-312.pyc,, -_pytest/_py/__pycache__/error.cpython-312.pyc,, -_pytest/_py/__pycache__/path.cpython-312.pyc,, -_pytest/_py/error.py,sha256=kGQ7F8_fZ6YVBhAx-u9mkTQBTx0qIxxnVMC0CgiOd70,3475 -_pytest/_py/path.py,sha256=-KeBr6FhiHZTDzI4e0ibR5VmweiOYoH8W_TkD7WvGO0,49220 -_pytest/_version.py,sha256=HcUAcCatO-VYGqVZtcyuDUHbveFtq2RFU6eL9NJOT2g,704 -_pytest/assertion/__init__.py,sha256=OjnJm4j6VHgwYjKvW8d-KFefjEdOSONFF4z10o9r7eg,7120 -_pytest/assertion/__pycache__/__init__.cpython-312.pyc,, -_pytest/assertion/__pycache__/rewrite.cpython-312.pyc,, -_pytest/assertion/__pycache__/truncate.cpython-312.pyc,, -_pytest/assertion/__pycache__/util.cpython-312.pyc,, -_pytest/assertion/rewrite.py,sha256=N2rauTZyAmVShY_6ygCcvvVT8ILy5N7oes5155ZtSxA,48206 -_pytest/assertion/truncate.py,sha256=2vOU3kS4hUZhDDL0Xaz1pM7oW_10wMc0J3b_lEbSzQI,5438 -_pytest/assertion/util.py,sha256=YQLyQ7nYHQu0ui5SeB62KB0KjuueFyHwghGT_p1dfmM,20561 -_pytest/cacheprovider.py,sha256=QCKdiepUSkuPNEx5DlhIQHbA413MwNUld4LIpV16FW8,23149 -_pytest/capture.py,sha256=kulumJdRdHu7zoosOr4lfHR0ce6LsOthau9Byrw8xV4,36829 -_pytest/compat.py,sha256=qcHcZHQyEA6TO3C0qGEgO0b_YxNu23ok0mTZ0sah6kk,10254 -_pytest/config/__init__.py,sha256=Z1aTUicjQ3RIs8h-A4j6m5C-IG1qGNuP-1_g1NK_hko,78099 -_pytest/config/__pycache__/__init__.cpython-312.pyc,, -_pytest/config/__pycache__/argparsing.cpython-312.pyc,, -_pytest/config/__pycache__/compat.cpython-312.pyc,, -_pytest/config/__pycache__/exceptions.cpython-312.pyc,, -_pytest/config/__pycache__/findpaths.cpython-312.pyc,, -_pytest/config/argparsing.py,sha256=dDjwtY9nhTQ9Q14ZbdU-EGwoQKZFuVZB1Ps0wclg5_o,20439 -_pytest/config/compat.py,sha256=djDt_XTPwXDIgnnopti2ZVrqtwzO5hFWiMhgU5dgIM4,2947 -_pytest/config/exceptions.py,sha256=6Vm9yzwUOxJGm17ZMGbw69v_i07mT1KmBwENM2scKhk,315 -_pytest/config/findpaths.py,sha256=0iq92sR6c6gVBFacwMNW-gFFzlfvWNO46dX9x39gnIg,12878 -_pytest/debugging.py,sha256=JkV7Ob7wQ53TFGkQ0Ta96jAMYGubgdXiEs39T7FPzHQ,13947 -_pytest/deprecated.py,sha256=EikoYjqdlLTZgbBNTmFdPqiP5DdZpzAyKwRbO2hA0VE,3611 -_pytest/doctest.py,sha256=GgjdWOxH-fV9eQoWQ7puKejcFdA1HPtG-Y7qbp-5L8c,25478 -_pytest/faulthandler.py,sha256=1c7DpRtP0_C3zpFUSw1BORpAG8GsiDtJ9zCuWdEiLc0,4250 -_pytest/fixtures.py,sha256=7_gKoVUCpfo4sCLy3T07p2Dv_xa5x2HnKDKZu884DeY,78681 -_pytest/freeze_support.py,sha256=X94IxipqebeA_HgzJh8dbjqGnrtEQFuMIC5hK7SGWXw,1300 -_pytest/helpconfig.py,sha256=6iy21oUDURGedZpB3emSzaFCxeNT_ERSkSIZLIxMJvU,10019 -_pytest/hookspec.py,sha256=uxxEMrVqSf5P4ycwj7TEVOffVWZtFwhUjN05LFEC_8E,43019 -_pytest/junitxml.py,sha256=1xGYlQzzO9HH3HghZ5hu4TBCf8V-IhBG5kVdx6W2PVo,25522 -_pytest/legacypath.py,sha256=_l6v8akNMfTc5TAjvbc6M-_t157p9QE6-118WM0DRt8,16588 -_pytest/logging.py,sha256=TZ67JQP_3Ylt0p11D2J68L_os9glsuggMvec0Hljtb8,35234 -_pytest/main.py,sha256=fKKyDMQfv8J2G8sjHuyyaWfVU2UQNcQAAPRNcOF0tpM,42435 -_pytest/mark/__init__.py,sha256=38H7wh2k3SZMZNZwAhPbHBxpPxYbn-wzmVtGUYigvzc,9870 -_pytest/mark/__pycache__/__init__.cpython-312.pyc,, -_pytest/mark/__pycache__/expression.cpython-312.pyc,, -_pytest/mark/__pycache__/structures.cpython-312.pyc,, -_pytest/mark/expression.py,sha256=30rS65yMCdfn1UWiCP_4imfzSeWEtP_qz4_2JV3Y1-U,11244 -_pytest/mark/structures.py,sha256=kY88t9EXrKmT2UnIhztQoCEZKUSTwDfj2TInQCtZw5I,23074 -_pytest/monkeypatch.py,sha256=WX3_6czULhvc3wSF-0N8Vesbv9wGN3dwliYcDvT8Efs,15500 -_pytest/nodes.py,sha256=aTsDhbLEVkZ2cgC8UXQW53bBDD5Y7l7ZNwB0kb5Nho4,26540 -_pytest/outcomes.py,sha256=qbgHRCERDRK8W8ZAfanQf4Dp4_W7vNoc2vv3pm3HtKo,10108 -_pytest/pastebin.py,sha256=p92zJtSNz9-xDEFzqQ3zemYggXRaDnxD6X4IyitevbA,4155 -_pytest/pathlib.py,sha256=xIOYa8ElyypD2BTuS2_8bh6OyD4DjPfBOulkFxQ5b1Q,37879 -_pytest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -_pytest/pytester.py,sha256=x0am9bXHz3QPBTPXPylcYmfP8sGhVi5RC_KPj6g3Hn4,62389 -_pytest/pytester_assertions.py,sha256=xX_HbFPB-Rz_NNDttTY39ft7_wZLvPgQQBVevSCeVmA,2253 -_pytest/python.py,sha256=_SgxiiPnr7jPgXlKs8sCjdsBSN-AoUZmzIvJMgMkVh4,68754 -_pytest/python_api.py,sha256=p40UzxqyZ2pm6Ioy1dBmJ7UiaO7Vmx7zhyZza_F5VJA,31744 -_pytest/raises.py,sha256=86V-hoY5hYba69npyrH7r4kQNb8QjouYb8SydGLmjVc,60082 -_pytest/recwarn.py,sha256=4a6s8WWqn-yCCHyqgnB_ffc_zkwXBGPCuvC-03L65c4,13386 -_pytest/reports.py,sha256=LpJypnzeOLkl66JKKIsu9390HA2qItpvvOU-5uWPpB0,23230 -_pytest/runner.py,sha256=jXhLKz4jsF9MTt0_UmPrEZZOhrbhbSyBTF_LISXcTqQ,19785 -_pytest/scope.py,sha256=pB7jsiisth16PBFacV1Yxd3Pj3YAx2dmlSmGbG4mw6A,2738 -_pytest/setuponly.py,sha256=BsRrC4ERDVr42-2G_L0AxhNU4XVwbMsy5S0lOvKr8wA,3167 -_pytest/setupplan.py,sha256=l-ycFNxDZPyY52wh4f7yaqhzZ7SW1ijSKnQLmqzDZWA,1184 -_pytest/skipping.py,sha256=WCRzHVoxF4D0GOrJoJTnzx_WOoLTsM1enlSYzG7NEnw,10810 -_pytest/stash.py,sha256=5pE3kDx4q855TW9aVvYTdrkkKlMDU6-xiX4luKpJEgI,3090 -_pytest/stepwise.py,sha256=kD81DrnhnclKBmMfauwQmbeMbYUvuw07w5WnNkmIdEQ,7689 -_pytest/subtests.py,sha256=-OzOpE0j98vDWMAsI-xMJ0FcYb6inLq6YPH_0tEyU44,13241 -_pytest/terminal.py,sha256=vCSgSHXwRTPrBHkSklZLCzDY1mIcidMscxiu10WXQnI,64734 -_pytest/threadexception.py,sha256=hTccpzZUrrQkDROVFAqHgXwAU481ca4Mq4CA4YB7my4,4953 -_pytest/timing.py,sha256=lPcKHaM1eKHv1_2ZgUaPK9h3FhtqGvZAYucx_LSdZw0,3108 -_pytest/tmpdir.py,sha256=I2kYwJAWDB9rk14WL_RKsnOnACIdX0CsFYkr515FA-4,11263 -_pytest/tracemalloc.py,sha256=lCUB_YUAb6R1vqq_b-LSYSXy-Tidbn2m7tfzmWAUrjk,778 -_pytest/unittest.py,sha256=FvXeCrTzm2lHkQD5PHN1THbgeundkmYrqY481ChacBM,23411 -_pytest/unraisableexception.py,sha256=dNaBpBHkOB4pOISoaMdau2ojrGoc_i4ux76DVXLLT-w,5179 -_pytest/warning_types.py,sha256=Vm6nG0sPUCyC3MBqHHroZXDlkU-Vvi0QTJQaaMryGJA,4398 -_pytest/warnings.py,sha256=yJMDr7vSeij-nyTuldE7wL25ZL_LumQGY6PJZpXiQ-I,5194 -py.py,sha256=txZ1tdmEW6CBTp6Idn-I2sOzzA0xKNoCi9Re27Uj6HE,329 -pytest-9.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pytest-9.0.1.dist-info/METADATA,sha256=GQA8fZ6TL_0gsol98tM6f09VjKNgrahIbtjKERFJRis,7558 -pytest-9.0.1.dist-info/RECORD,, -pytest-9.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pytest-9.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 -pytest-9.0.1.dist-info/entry_points.txt,sha256=8IPrHPH3LNZQ7v5tNEOcNTZYk_SheNg64jsTM9erqL4,77 -pytest-9.0.1.dist-info/licenses/LICENSE,sha256=yoNqX57Mo7LzUCMPqiCkj7ixRWU7VWjXhIYt-GRwa5s,1091 -pytest-9.0.1.dist-info/top_level.txt,sha256=yyhjvmXH7-JOaoQIdmNQHPuoBCxOyXS3jIths_6C8A4,18 -pytest/__init__.py,sha256=e-eh4iGNxZoZ0a0THkq-IRcqKVCH78comJOCSHguh2Y,5582 -pytest/__main__.py,sha256=oVDrGGo7N0TNyzXntUblcgTKbhHGWtivcX5TC7tEcKo,154 -pytest/__pycache__/__init__.cpython-312.pyc,, -pytest/__pycache__/__main__.cpython-312.pyc,, -pytest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-cov.pth b/Backend/venv/lib/python3.12/site-packages/pytest-cov.pth new file mode 100644 index 00000000..8ed1a516 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest-cov.pth @@ -0,0 +1 @@ +import os, sys;exec('if \'COV_CORE_SOURCE\' in os.environ:\n try:\n from pytest_cov.embed import init\n init()\n except Exception as exc:\n sys.stderr.write(\n "pytest-cov: Failed to setup subprocess coverage. "\n "Environ: {0!r} "\n "Exception: {1!r}\\n".format(\n dict((k, v) for k, v in os.environ.items() if k.startswith(\'COV_CORE\')),\n exc\n )\n )\n') diff --git a/Backend/venv/lib/python3.12/site-packages/pytest/__init__.py b/Backend/venv/lib/python3.12/site-packages/pytest/__init__.py index 3e6281ac..9c1d5d20 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest/__init__.py @@ -1,8 +1,5 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" - -from __future__ import annotations - from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo @@ -23,7 +20,6 @@ from _pytest.config.argparsing import Parser from _pytest.debugging import pytestPDB as __pytestPDB from _pytest.doctest import DoctestItem from _pytest.fixtures import fixture -from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureLookupError from _pytest.fixtures import FixtureRequest from _pytest.fixtures import yield_fixture @@ -31,9 +27,7 @@ from _pytest.freeze_support import freeze_includes from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir from _pytest.logging import LogCaptureFixture -from _pytest.main import Dir from _pytest.main import Session -from _pytest.mark import HIDDEN_PARAM from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark from _pytest.mark import MarkDecorator @@ -41,7 +35,6 @@ from _pytest.mark import MarkGenerator from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector -from _pytest.nodes import Directory from _pytest.nodes import File from _pytest.nodes import Item from _pytest.outcomes import exit @@ -60,9 +53,7 @@ from _pytest.python import Metafunc from _pytest.python import Module from _pytest.python import Package from _pytest.python_api import approx -from _pytest.raises import raises -from _pytest.raises import RaisesExc -from _pytest.raises import RaisesGroup +from _pytest.python_api import raises from _pytest.recwarn import deprecated_call from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import warns @@ -71,9 +62,6 @@ from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.stash import Stash from _pytest.stash import StashKey -from _pytest.subtests import SubtestReport -from _pytest.subtests import Subtests -from _pytest.terminal import TerminalReporter from _pytest.terminal import TestShortLogReport from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestAssertRewriteWarning @@ -82,42 +70,50 @@ from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning -from _pytest.warning_types import PytestFDWarning -from _pytest.warning_types import PytestRemovedIn9Warning -from _pytest.warning_types import PytestRemovedIn10Warning +from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import PytestReturnNotNoneWarning +from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnknownMarkWarning from _pytest.warning_types import PytestUnraisableExceptionWarning from _pytest.warning_types import PytestWarning - set_trace = __pytestPDB.set_trace __all__ = [ - "HIDDEN_PARAM", + "__version__", + "approx", "Cache", "CallInfo", "CaptureFixture", "Class", - "CollectReport", + "cmdline", "Collector", + "CollectReport", "Config", - "Dir", - "Directory", + "console_main", + "deprecated_call", "DoctestItem", + "exit", "ExceptionInfo", "ExitCode", + "fail", "File", - "FixtureDef", + "fixture", "FixtureLookupError", "FixtureRequest", + "freeze_includes", "Function", + "hookimpl", "HookRecorder", + "hookspec", + "importorskip", "Item", "LineMatcher", "LogCaptureFixture", + "main", + "mark", "Mark", "MarkDecorator", "MarkGenerator", @@ -126,6 +122,7 @@ __all__ = [ "MonkeyPatch", "OptionGroup", "Package", + "param", "Parser", "PytestAssertRewriteWarning", "PytestCacheWarning", @@ -133,54 +130,42 @@ __all__ = [ "PytestConfigWarning", "PytestDeprecationWarning", "PytestExperimentalApiWarning", - "PytestFDWarning", - "PytestPluginManager", - "PytestRemovedIn9Warning", - "PytestRemovedIn10Warning", + "PytestRemovedIn8Warning", "PytestReturnNotNoneWarning", + "Pytester", + "PytestPluginManager", + "PytestUnhandledCoroutineWarning", "PytestUnhandledThreadExceptionWarning", "PytestUnknownMarkWarning", "PytestUnraisableExceptionWarning", "PytestWarning", - "Pytester", - "RaisesExc", - "RaisesGroup", + "raises", "RecordedHookCall", + "register_assert_rewrite", "RunResult", "Session", - "Stash", - "StashKey", - "SubtestReport", - "Subtests", - "TempPathFactory", - "TempdirFactory", - "TerminalReporter", - "TestReport", - "TestShortLogReport", - "Testdir", - "UsageError", - "WarningsRecorder", - "__version__", - "approx", - "cmdline", - "console_main", - "deprecated_call", - "exit", - "fail", - "fixture", - "freeze_includes", - "hookimpl", - "hookspec", - "importorskip", - "main", - "mark", - "param", - "raises", - "register_assert_rewrite", "set_trace", "skip", + "Stash", + "StashKey", "version_tuple", + "TempdirFactory", + "TempPathFactory", + "Testdir", + "TestReport", + "TestShortLogReport", + "UsageError", + "WarningsRecorder", "warns", "xfail", "yield_fixture", ] + + +def __getattr__(name: str) -> object: + if name == "Instance": + # The import emits a deprecation warning. + from _pytest.python import Instance + + return Instance + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/Backend/venv/lib/python3.12/site-packages/pytest/__main__.py b/Backend/venv/lib/python3.12/site-packages/pytest/__main__.py index cccab5d5..b1701529 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest/__main__.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest/__main__.py @@ -1,9 +1,5 @@ """The pytest entry point.""" - -from __future__ import annotations - import pytest - if __name__ == "__main__": raise SystemExit(pytest.console_main()) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4e795f05 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__main__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 00000000..6396b963 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest/__pycache__/__main__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/licenses/LICENSE rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/METADATA similarity index 72% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/METADATA rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/METADATA index b6e06552..c73b027b 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/METADATA +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/METADATA @@ -1,40 +1,41 @@ -Metadata-Version: 2.4 +Metadata-Version: 2.1 Name: pytest-asyncio -Version: 1.3.0 +Version: 0.21.1 Summary: Pytest support for asyncio -Author-email: Tin Tvrtković -Maintainer-email: Michael Seifert -License-Expression: Apache-2.0 -Project-URL: Bug Tracker, https://github.com/pytest-dev/pytest-asyncio/issues -Project-URL: Changelog, https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html +Home-page: https://github.com/pytest-dev/pytest-asyncio +Author: Tin Tvrtković +Author-email: tinchester@gmail.com +License: Apache 2.0 Project-URL: Documentation, https://pytest-asyncio.readthedocs.io -Project-URL: Homepage, https://github.com/pytest-dev/pytest-asyncio +Project-URL: Changelog, https://pytest-asyncio.readthedocs.io/en/latest/reference/changelog.html Project-URL: Source Code, https://github.com/pytest-dev/pytest-asyncio -Classifier: Development Status :: 5 - Production/Stable -Classifier: Framework :: AsyncIO -Classifier: Framework :: Pytest +Project-URL: Bug Tracker, https://github.com/pytest-dev/pytest-asyncio/issues +Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3 :: Only +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: AsyncIO +Classifier: Framework :: Pytest Classifier: Typing :: Typed -Requires-Python: >=3.10 +Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE -Requires-Dist: backports-asyncio-runner<2,>=1.1; python_version < "3.11" -Requires-Dist: pytest<10,>=8.2 -Requires-Dist: typing-extensions>=4.12; python_version < "3.13" +Requires-Dist: pytest (>=7.0.0) +Requires-Dist: typing-extensions (>=3.7.2) ; python_version < "3.8" Provides-Extra: docs -Requires-Dist: sphinx>=5.3; extra == "docs" -Requires-Dist: sphinx-rtd-theme>=1; extra == "docs" +Requires-Dist: sphinx (>=5.3) ; extra == 'docs' +Requires-Dist: sphinx-rtd-theme (>=1.0) ; extra == 'docs' Provides-Extra: testing -Requires-Dist: coverage>=6.2; extra == "testing" -Requires-Dist: hypothesis>=5.7.1; extra == "testing" -Dynamic: license-file +Requires-Dist: coverage (>=6.2) ; extra == 'testing' +Requires-Dist: hypothesis (>=5.7.1) ; extra == 'testing' +Requires-Dist: flaky (>=3.5.0) ; extra == 'testing' +Requires-Dist: mypy (>=0.931) ; extra == 'testing' +Requires-Dist: pytest-trio (>=0.7.0) ; extra == 'testing' pytest-asyncio ============== @@ -48,9 +49,8 @@ pytest-asyncio .. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg :target: https://github.com/pytest-dev/pytest-asyncio :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Matrix-%23pytest--asyncio-brightgreen - :alt: Matrix chat room: #pytest-asyncio - :target: https://matrix.to/#/#pytest-asyncio:matrix.org +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black `pytest-asyncio `_ is a `pytest `_ plugin. It facilitates testing of code that uses the `asyncio `_ library. diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/RECORD new file mode 100644 index 00000000..5d7ed04e --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/RECORD @@ -0,0 +1,15 @@ +pytest_asyncio-0.21.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pytest_asyncio-0.21.1.dist-info/LICENSE,sha256=qK0xscP0DcpahBGTUbj6jdyGjt13-tio6_bY8tFvpK4,11324 +pytest_asyncio-0.21.1.dist-info/METADATA,sha256=0Zy0_pagtR9L_VCS3foQYsxI9aS7Kn5vKm1paLJ9DOc,4037 +pytest_asyncio-0.21.1.dist-info/RECORD,, +pytest_asyncio-0.21.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pytest_asyncio-0.21.1.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92 +pytest_asyncio-0.21.1.dist-info/entry_points.txt,sha256=_5TsciE-7mIopUy1NrPCEjHVTDIwhNbyXvP-su1-O7w,43 +pytest_asyncio-0.21.1.dist-info/top_level.txt,sha256=J4BTi9IZbfghCsiVybot1y0AaLUgxp3NMaNpH9fghNI,15 +pytest_asyncio/__init__.py,sha256=2J7tryFeuZBUKF_miJXNNZvNm2MCcGaj1_zq8baQm9w,162 +pytest_asyncio/__pycache__/__init__.cpython-312.pyc,, +pytest_asyncio/__pycache__/_version.cpython-312.pyc,, +pytest_asyncio/__pycache__/plugin.cpython-312.pyc,, +pytest_asyncio/_version.py,sha256=Z6-ugEKMtn6oRV3bVcSxQWqIiNYmNwBbpsLFkH3Zm8g,162 +pytest_asyncio/plugin.py,sha256=2-N-ejUbeGlpKxB58mafdHooUoSwf71IhhDi6cIezeY,19233 +pytest_asyncio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/REQUESTED similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/REQUESTED rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/REQUESTED diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/WHEEL similarity index 65% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/WHEEL index e7fa31b6..1f37c02f 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: setuptools (80.9.0) +Generator: bdist_wheel (0.40.0) Root-Is-Purelib: true Tag: py3-none-any diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/entry_points.txt b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/entry_points.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/entry_points.txt rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/entry_points.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/top_level.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/top_level.txt rename to Backend/venv/lib/python3.12/site-packages/pytest_asyncio-0.21.1.dist-info/top_level.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/RECORD deleted file mode 100644 index d646eae9..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio-1.3.0.dist-info/RECORD +++ /dev/null @@ -1,13 +0,0 @@ -pytest_asyncio-1.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pytest_asyncio-1.3.0.dist-info/METADATA,sha256=aNclD7fY8M3otX7WeXxOud50Ml2Aw2L8z-VeV5Zpgbk,4088 -pytest_asyncio-1.3.0.dist-info/RECORD,, -pytest_asyncio-1.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pytest_asyncio-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 -pytest_asyncio-1.3.0.dist-info/entry_points.txt,sha256=_5TsciE-7mIopUy1NrPCEjHVTDIwhNbyXvP-su1-O7w,43 -pytest_asyncio-1.3.0.dist-info/licenses/LICENSE,sha256=qK0xscP0DcpahBGTUbj6jdyGjt13-tio6_bY8tFvpK4,11324 -pytest_asyncio-1.3.0.dist-info/top_level.txt,sha256=J4BTi9IZbfghCsiVybot1y0AaLUgxp3NMaNpH9fghNI,15 -pytest_asyncio/__init__.py,sha256=kyqYDFkRmSN14_6v_128g4jqaO9JrhMnfMDMiwkyZ8Q,250 -pytest_asyncio/__pycache__/__init__.cpython-312.pyc,, -pytest_asyncio/__pycache__/plugin.cpython-312.pyc,, -pytest_asyncio/plugin.py,sha256=Ehy5jXRZap5dsyaaS-cTG67uqzdUtZlqmybCKJEIY6o,29856 -pytest_asyncio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__init__.py b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__init__.py index abd62e15..1bc2811d 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__init__.py @@ -1,11 +1,5 @@ """The main point for importing pytest-asyncio items.""" +from ._version import version as __version__ # noqa +from .plugin import fixture -from __future__ import annotations - -from importlib.metadata import version - -from .plugin import fixture, is_async_test - -__version__ = version(__name__) - -__all__ = ("fixture", "is_async_test") +__all__ = ("fixture",) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..19e23df0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/_version.cpython-312.pyc new file mode 100644 index 00000000..673643b4 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/plugin.cpython-312.pyc new file mode 100644 index 00000000..7dd89fbe Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/__pycache__/plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/_version.py b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/_version.py new file mode 100644 index 00000000..11f23015 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '0.21.1' +__version_tuple__ = version_tuple = (0, 21, 1) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py index fccb6cff..12669791 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_asyncio/plugin.py @@ -1,72 +1,63 @@ """pytest-asyncio implementation.""" - -from __future__ import annotations - import asyncio import contextlib -import contextvars import enum import functools import inspect import socket import sys -import traceback import warnings -from asyncio import AbstractEventLoop, AbstractEventLoopPolicy -from collections.abc import ( +from textwrap import dedent +from typing import ( + Any, AsyncIterator, Awaitable, Callable, - Generator, + Dict, Iterable, Iterator, - Sequence, -) -from types import AsyncGeneratorType, CoroutineType -from typing import ( - Any, - Literal, - ParamSpec, + List, + Optional, + Set, TypeVar, + Union, + cast, overload, ) -import pluggy import pytest -from _pytest.fixtures import resolve_fixture_function -from _pytest.scope import Scope from pytest import ( Config, - FixtureDef, FixtureRequest, Function, Item, - Mark, - MonkeyPatch, Parser, - PytestCollectionWarning, - PytestDeprecationWarning, PytestPluginManager, + Session, ) -if sys.version_info >= (3, 11): - from asyncio import Runner +if sys.version_info >= (3, 8): + from typing import Literal else: - from backports.asyncio.runner import Runner + from typing_extensions import Literal -if sys.version_info >= (3, 13): - from typing import TypeIs -else: - from typing_extensions import TypeIs +_R = TypeVar("_R") _ScopeName = Literal["session", "package", "module", "class", "function"] -_R = TypeVar("_R", bound=Awaitable[Any] | AsyncIterator[Any]) -_P = ParamSpec("_P") -FixtureFunction = Callable[_P, _R] +_T = TypeVar("_T") +SimpleFixtureFunction = TypeVar( + "SimpleFixtureFunction", bound=Callable[..., Awaitable[_R]] +) +FactoryFixtureFunction = TypeVar( + "FactoryFixtureFunction", bound=Callable[..., AsyncIterator[_R]] +) +FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction] +FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction] -class PytestAsyncioError(Exception): - """Base class for exceptions raised by pytest-asyncio""" +# https://github.com/pytest-dev/pytest/pull/9510 +FixtureDef = Any +SubRequest = Any class Mode(str, enum.Enum): @@ -91,89 +82,59 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None metavar="MODE", help=ASYNCIO_MODE_HELP, ) - group.addoption( - "--asyncio-debug", - dest="asyncio_debug", - action="store_true", - default=None, - help="enable asyncio debug mode for the default event loop", - ) parser.addini( "asyncio_mode", help="default value for --asyncio-mode", default="strict", ) - parser.addini( - "asyncio_debug", - help="enable asyncio debug mode for the default event loop", - type="bool", - default="false", - ) - parser.addini( - "asyncio_default_fixture_loop_scope", - type="string", - help="default scope of the asyncio event loop used to execute async fixtures", - default=None, - ) - parser.addini( - "asyncio_default_test_loop_scope", - type="string", - help="default scope of the asyncio event loop used to execute tests", - default="function", - ) @overload def fixture( - fixture_function: FixtureFunction[_P, _R], + fixture_function: FixtureFunction, *, - scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., - loop_scope: _ScopeName | None = ..., - params: Iterable[object] | None = ..., + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., autouse: bool = ..., - ids: ( - Iterable[str | float | int | bool | None] - | Callable[[Any], object | None] - | None - ) = ..., - name: str | None = ..., -) -> FixtureFunction[_P, _R]: ... + ids: Union[ + Iterable[Union[str, float, int, bool, None]], + Callable[[Any], Optional[object]], + None, + ] = ..., + name: Optional[str] = ..., +) -> FixtureFunction: + ... @overload def fixture( fixture_function: None = ..., *, - scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., - loop_scope: _ScopeName | None = ..., - params: Iterable[object] | None = ..., + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., autouse: bool = ..., - ids: ( - Iterable[str | float | int | bool | None] - | Callable[[Any], object | None] - | None - ) = ..., - name: str | None = None, -) -> Callable[[FixtureFunction[_P, _R]], FixtureFunction[_P, _R]]: ... + ids: Union[ + Iterable[Union[str, float, int, bool, None]], + Callable[[Any], Optional[object]], + None, + ] = ..., + name: Optional[str] = None, +) -> FixtureFunctionMarker: + ... def fixture( - fixture_function: FixtureFunction[_P, _R] | None = None, - loop_scope: _ScopeName | None = None, - **kwargs: Any, -) -> ( - FixtureFunction[_P, _R] - | Callable[[FixtureFunction[_P, _R]], FixtureFunction[_P, _R]] -): + fixture_function: Optional[FixtureFunction] = None, **kwargs: Any +) -> Union[FixtureFunction, FixtureFunctionMarker]: if fixture_function is not None: - _make_asyncio_fixture_function(fixture_function, loop_scope) + _make_asyncio_fixture_function(fixture_function) return pytest.fixture(fixture_function, **kwargs) else: @functools.wraps(fixture) - def inner(fixture_function: FixtureFunction[_P, _R]) -> FixtureFunction[_P, _R]: - return fixture(fixture_function, loop_scope=loop_scope, **kwargs) + def inner(fixture_function: FixtureFunction) -> FixtureFunction: + return fixture(fixture_function, **kwargs) return inner @@ -183,16 +144,20 @@ def _is_asyncio_fixture_function(obj: Any) -> bool: return getattr(obj, "_force_asyncio_fixture", False) -def _make_asyncio_fixture_function(obj: Any, loop_scope: _ScopeName | None) -> None: +def _make_asyncio_fixture_function(obj: Any) -> None: if hasattr(obj, "__func__"): # instance method, check the function object obj = obj.__func__ obj._force_asyncio_fixture = True - obj._loop_scope = loop_scope + + +def _is_coroutine(obj: Any) -> bool: + """Check to see if an object is really an asyncio coroutine.""" + return asyncio.iscoroutinefunction(obj) def _is_coroutine_or_asyncgen(obj: Any) -> bool: - return inspect.iscoroutinefunction(obj) or inspect.isasyncgenfunction(obj) + return _is_coroutine(obj) or inspect.isasyncgenfunction(obj) def _get_asyncio_mode(config: Config) -> Mode: @@ -201,53 +166,15 @@ def _get_asyncio_mode(config: Config) -> Mode: val = config.getini("asyncio_mode") try: return Mode(val) - except ValueError as e: + except ValueError: modes = ", ".join(m.value for m in Mode) raise pytest.UsageError( f"{val!r} is not a valid asyncio_mode. Valid modes: {modes}." - ) from e - - -def _get_asyncio_debug(config: Config) -> bool: - val = config.getoption("asyncio_debug") - if val is None: - val = config.getini("asyncio_debug") - - if isinstance(val, bool): - return val - else: - return val == "true" - - -_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET = """\ -The configuration option "asyncio_default_fixture_loop_scope" is unset. -The event loop scope for asynchronous fixtures will default to the fixture caching \ -scope. Future versions of pytest-asyncio will default the loop scope for asynchronous \ -fixtures to function scope. Set the default fixture loop scope explicitly in order to \ -avoid unexpected behavior in the future. Valid fixture loop scopes are: \ -"function", "class", "module", "package", "session" -""" - - -def _validate_scope(scope: str | None, option_name: str) -> None: - if scope is None: - return - valid_scopes = [s.value for s in Scope] - if scope not in valid_scopes: - raise pytest.UsageError( - f"{scope!r} is not a valid {option_name}. " - f"Valid scopes are: {', '.join(valid_scopes)}." ) def pytest_configure(config: Config) -> None: - default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope") - _validate_scope(default_fixture_loop_scope, "asyncio_default_fixture_loop_scope") - if not default_fixture_loop_scope: - warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) - - default_test_loop_scope = config.getini("asyncio_default_test_loop_scope") - _validate_scope(default_test_loop_scope, "asyncio_default_test_loop_scope") + """Inject documentation.""" config.addinivalue_line( "markers", "asyncio: " @@ -257,63 +184,108 @@ def pytest_configure(config: Config) -> None: @pytest.hookimpl(tryfirst=True) -def pytest_report_header(config: Config) -> list[str]: +def pytest_report_header(config: Config) -> List[str]: """Add asyncio config to pytest header.""" mode = _get_asyncio_mode(config) - debug = _get_asyncio_debug(config) - default_fixture_loop_scope = config.getini("asyncio_default_fixture_loop_scope") - default_test_loop_scope = _get_default_test_loop_scope(config) - header = [ - f"mode={mode}", - f"debug={debug}", - f"asyncio_default_fixture_loop_scope={default_fixture_loop_scope}", - f"asyncio_default_test_loop_scope={default_test_loop_scope}", - ] - return [ - "asyncio: " + ", ".join(header), - ] + return [f"asyncio: mode={mode}"] -def _fixture_synchronizer( - fixturedef: FixtureDef, runner: Runner, request: FixtureRequest -) -> Callable: - """Returns a synchronous function evaluating the specified fixture.""" - fixture_function = resolve_fixture_function(fixturedef, request) +def _preprocess_async_fixtures( + config: Config, + processed_fixturedefs: Set[FixtureDef], +) -> None: + asyncio_mode = _get_asyncio_mode(config) + fixturemanager = config.pluginmanager.get_plugin("funcmanage") + for fixtures in fixturemanager._arg2fixturedefs.values(): + for fixturedef in fixtures: + func = fixturedef.func + if fixturedef in processed_fixturedefs or not _is_coroutine_or_asyncgen( + func + ): + continue + if not _is_asyncio_fixture_function(func) and asyncio_mode == Mode.STRICT: + # Ignore async fixtures without explicit asyncio mark in strict mode + # This applies to pytest_trio fixtures, for example + continue + _make_asyncio_fixture_function(func) + _inject_fixture_argnames(fixturedef) + _synchronize_async_fixture(fixturedef) + assert _is_asyncio_fixture_function(fixturedef.func) + processed_fixturedefs.add(fixturedef) + + +def _inject_fixture_argnames(fixturedef: FixtureDef) -> None: + """ + Ensures that `request` and `event_loop` are arguments of the specified fixture. + """ + to_add = [] + for name in ("request", "event_loop"): + if name not in fixturedef.argnames: + to_add.append(name) + if to_add: + fixturedef.argnames += tuple(to_add) + + +def _synchronize_async_fixture(fixturedef: FixtureDef) -> None: + """ + Wraps the fixture function of an async fixture in a synchronous function. + """ if inspect.isasyncgenfunction(fixturedef.func): - return _wrap_asyncgen_fixture(fixture_function, runner, request) # type: ignore[arg-type] + _wrap_asyncgen_fixture(fixturedef) elif inspect.iscoroutinefunction(fixturedef.func): - return _wrap_async_fixture(fixture_function, runner, request) # type: ignore[arg-type] - else: - return fixturedef.func + _wrap_async_fixture(fixturedef) -AsyncGenFixtureParams = ParamSpec("AsyncGenFixtureParams") -AsyncGenFixtureYieldType = TypeVar("AsyncGenFixtureYieldType") +def _add_kwargs( + func: Callable[..., Any], + kwargs: Dict[str, Any], + event_loop: asyncio.AbstractEventLoop, + request: SubRequest, +) -> Dict[str, Any]: + sig = inspect.signature(func) + ret = kwargs.copy() + if "request" in sig.parameters: + ret["request"] = request + if "event_loop" in sig.parameters: + ret["event_loop"] = event_loop + return ret -def _wrap_asyncgen_fixture( - fixture_function: Callable[ - AsyncGenFixtureParams, AsyncGeneratorType[AsyncGenFixtureYieldType, Any] - ], - runner: Runner, - request: FixtureRequest, -) -> Callable[AsyncGenFixtureParams, AsyncGenFixtureYieldType]: - @functools.wraps(fixture_function) +def _perhaps_rebind_fixture_func( + func: _T, instance: Optional[Any], unittest: bool +) -> _T: + if instance is not None: + # The fixture needs to be bound to the actual request.instance + # so it is bound to the same object as the test method. + unbound, cls = func, None + try: + unbound, cls = func.__func__, type(func.__self__) # type: ignore + except AttributeError: + pass + # If unittest is true, the fixture is bound unconditionally. + # otherwise, only if the fixture was bound before to an instance of + # the same type. + if unittest or (cls is not None and isinstance(instance, cls)): + func = unbound.__get__(instance) # type: ignore + return func + + +def _wrap_asyncgen_fixture(fixturedef: FixtureDef) -> None: + fixture = fixturedef.func + + @functools.wraps(fixture) def _asyncgen_fixture_wrapper( - *args: AsyncGenFixtureParams.args, - **kwargs: AsyncGenFixtureParams.kwargs, + event_loop: asyncio.AbstractEventLoop, request: SubRequest, **kwargs: Any ): - gen_obj = fixture_function(*args, **kwargs) + func = _perhaps_rebind_fixture_func( + fixture, request.instance, fixturedef.unittest + ) + gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request)) async def setup(): res = await gen_obj.__anext__() return res - context = contextvars.copy_context() - result = runner.run(setup(), context=context) - - reset_contextvars = _apply_contextvar_changes(context) - def finalizer() -> None: """Yield again, to finalize.""" @@ -327,518 +299,274 @@ def _wrap_asyncgen_fixture( msg += "Yield only once." raise ValueError(msg) - runner.run(async_finalizer(), context=context) - if reset_contextvars is not None: - reset_contextvars() + event_loop.run_until_complete(async_finalizer()) + result = event_loop.run_until_complete(setup()) request.addfinalizer(finalizer) return result - return _asyncgen_fixture_wrapper + fixturedef.func = _asyncgen_fixture_wrapper -AsyncFixtureParams = ParamSpec("AsyncFixtureParams") -AsyncFixtureReturnType = TypeVar("AsyncFixtureReturnType") +def _wrap_async_fixture(fixturedef: FixtureDef) -> None: + fixture = fixturedef.func - -def _wrap_async_fixture( - fixture_function: Callable[ - AsyncFixtureParams, CoroutineType[Any, Any, AsyncFixtureReturnType] - ], - runner: Runner, - request: FixtureRequest, -) -> Callable[AsyncFixtureParams, AsyncFixtureReturnType]: - @functools.wraps(fixture_function) + @functools.wraps(fixture) def _async_fixture_wrapper( - *args: AsyncFixtureParams.args, - **kwargs: AsyncFixtureParams.kwargs, + event_loop: asyncio.AbstractEventLoop, request: SubRequest, **kwargs: Any ): + func = _perhaps_rebind_fixture_func( + fixture, request.instance, fixturedef.unittest + ) + async def setup(): - res = await fixture_function(*args, **kwargs) + res = await func(**_add_kwargs(func, kwargs, event_loop, request)) return res - context = contextvars.copy_context() - result = runner.run(setup(), context=context) + return event_loop.run_until_complete(setup()) - # Copy the context vars modified by the setup task into the current - # context, and (if needed) add a finalizer to reset them. - # - # Note that this is slightly different from the behavior of a non-async - # fixture, which would rely on the fixture author to add a finalizer - # to reset the variables. In this case, the author of the fixture can't - # write such a finalizer because they have no way to capture the Context - # in which the setup function was run, so we need to do it for them. - reset_contextvars = _apply_contextvar_changes(context) - if reset_contextvars is not None: - request.addfinalizer(reset_contextvars) - - return result - - return _async_fixture_wrapper + fixturedef.func = _async_fixture_wrapper -def _apply_contextvar_changes( - context: contextvars.Context, -) -> Callable[[], None] | None: - """ - Copy contextvar changes from the given context to the current context. +_HOLDER: Set[FixtureDef] = set() - If any contextvars were modified by the fixture, return a finalizer that - will restore them. - """ - context_tokens = [] - for var in context: - try: - if var.get() is context.get(var): - # This variable is not modified, so leave it as-is. - continue - except LookupError: - # This variable isn't yet set in the current context at all. - pass - token = var.set(context.get(var)) - context_tokens.append((var, token)) - if not context_tokens: +@pytest.hookimpl(tryfirst=True) +def pytest_pycollect_makeitem( + collector: Union[pytest.Module, pytest.Class], name: str, obj: object +) -> Union[ + pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None +]: + """A pytest hook to collect asyncio coroutines.""" + if not collector.funcnamefilter(name): return None - - def restore_contextvars(): - while context_tokens: - (var, token) = context_tokens.pop() - var.reset(token) - - return restore_contextvars - - -class PytestAsyncioFunction(Function): - """Base class for all test functions managed by pytest-asyncio.""" - - @classmethod - def item_subclass_for(cls, item: Function, /) -> type[PytestAsyncioFunction] | None: - """ - Returns a subclass of PytestAsyncioFunction if there is a specialized subclass - for the specified function item. - - Return None if no specialized subclass exists for the specified item. - """ - for subclass in cls.__subclasses__(): - if subclass._can_substitute(item): - return subclass - return None - - @classmethod - def _from_function(cls, function: Function, /) -> Function: - """ - Instantiates this specific PytestAsyncioFunction type from the specified - Function item. - """ - assert function.get_closest_marker("asyncio") - assert function.parent is not None - subclass_instance = cls.from_parent( - function.parent, - name=function.name, - callspec=getattr(function, "callspec", None), - callobj=function.obj, - fixtureinfo=function._fixtureinfo, - keywords=function.keywords, - originalname=function.originalname, - ) - subclass_instance.own_markers = function.own_markers - assert subclass_instance.own_markers == function.own_markers - return subclass_instance - - @staticmethod - def _can_substitute(item: Function) -> bool: - """Returns whether the specified function can be replaced by this class""" - raise NotImplementedError() - - def setup(self) -> None: - runner_fixture_id = f"_{self._loop_scope}_scoped_runner" - if runner_fixture_id not in self.fixturenames: - self.fixturenames.append(runner_fixture_id) - return super().setup() - - def runtest(self) -> None: - runner_fixture_id = f"_{self._loop_scope}_scoped_runner" - runner = self._request.getfixturevalue(runner_fixture_id) - context = contextvars.copy_context() - synchronized_obj = _synchronize_coroutine( - getattr(*self._synchronization_target_attr), runner, context - ) - with MonkeyPatch.context() as c: - c.setattr(*self._synchronization_target_attr, synchronized_obj) - super().runtest() - - @functools.cached_property - def _loop_scope(self) -> _ScopeName: - """ - Return the scope of the asyncio event loop this item is run in. - - The effective scope is determined lazily. It is identical to to the - `loop_scope` value of the closest `asyncio` pytest marker. If no such - marker is present, the the loop scope is determined by the configuration - value of `asyncio_default_test_loop_scope`, instead. - """ - marker = self.get_closest_marker("asyncio") - assert marker is not None - default_loop_scope = _get_default_test_loop_scope(self.config) - return _get_marked_loop_scope(marker, default_loop_scope) - - @property - def _synchronization_target_attr(self) -> tuple[object, str]: - """ - Return the coroutine that needs to be synchronized during the test run. - - This method is inteded to be overwritten by subclasses when they need to apply - the coroutine synchronizer to a value that's different from self.obj - e.g. the AsyncHypothesisTest subclass. - """ - return self, "obj" - - -class Coroutine(PytestAsyncioFunction): - """Pytest item created by a coroutine""" - - @staticmethod - def _can_substitute(item: Function) -> bool: - func = item.obj - return inspect.iscoroutinefunction(func) - - -class AsyncGenerator(PytestAsyncioFunction): - """Pytest item created by an asynchronous generator""" - - @staticmethod - def _can_substitute(item: Function) -> bool: - func = item.obj - return inspect.isasyncgenfunction(func) - - @classmethod - def _from_function(cls, function: Function, /) -> Function: - async_gen_item = super()._from_function(function) - unsupported_item_type_message = ( - f"Tests based on asynchronous generators are not supported. " - f"{function.name} will be ignored." - ) - async_gen_item.warn(PytestCollectionWarning(unsupported_item_type_message)) - async_gen_item.add_marker( - pytest.mark.xfail(run=False, reason=unsupported_item_type_message) - ) - return async_gen_item - - -class AsyncStaticMethod(PytestAsyncioFunction): - """ - Pytest item that is a coroutine or an asynchronous generator - decorated with staticmethod - """ - - @staticmethod - def _can_substitute(item: Function) -> bool: - func = item.obj - return isinstance(func, staticmethod) and _is_coroutine_or_asyncgen( - func.__func__ - ) - - -class AsyncHypothesisTest(PytestAsyncioFunction): - """ - Pytest item that is coroutine or an asynchronous generator decorated by - @hypothesis.given. - """ - - def setup(self) -> None: - if not getattr(self.obj, "hypothesis", False) and getattr( - self.obj, "is_hypothesis_test", False - ): - pytest.fail( - f"test function `{self!r}` is using Hypothesis, but pytest-asyncio " - "only works with Hypothesis 3.64.0 or later." - ) - return super().setup() - - @staticmethod - def _can_substitute(item: Function) -> bool: - func = item.obj - return ( - getattr(func, "is_hypothesis_test", False) # type: ignore[return-value] - and getattr(func, "hypothesis", None) - and inspect.iscoroutinefunction(func.hypothesis.inner_test) - ) - - @property - def _synchronization_target_attr(self) -> tuple[object, str]: - return self.obj.hypothesis, "inner_test" - - -# The function name needs to start with "pytest_" -# see https://github.com/pytest-dev/pytest/issues/11307 -@pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True) -def pytest_pycollect_makeitem_convert_async_functions_to_subclass( - collector: pytest.Module | pytest.Class, name: str, obj: object -) -> Generator[None, pluggy.Result, None]: - """ - Converts coroutines and async generators collected as pytest.Functions - to AsyncFunction items. - """ - hook_result = yield - try: - node_or_list_of_nodes: ( - pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None - ) = hook_result.get_result() - except BaseException as e: - hook_result.force_exception(e) - return - if not node_or_list_of_nodes: - return - if isinstance(node_or_list_of_nodes, Sequence): - node_iterator = iter(node_or_list_of_nodes) - else: - # Treat single node as a single-element iterable - node_iterator = iter((node_or_list_of_nodes,)) - updated_node_collection = [] - for node in node_iterator: - updated_item = node - if isinstance(node, Function): - specialized_item_class = PytestAsyncioFunction.item_subclass_for(node) - if specialized_item_class: - if _get_asyncio_mode( - node.config - ) == Mode.AUTO and not node.get_closest_marker("asyncio"): - node.add_marker("asyncio") - if node.get_closest_marker("asyncio"): - updated_item = specialized_item_class._from_function(node) - updated_node_collection.append(updated_item) - hook_result.force_result(updated_node_collection) - - -@contextlib.contextmanager -def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]: - old_loop_policy = _get_event_loop_policy() - try: - old_loop = _get_event_loop_no_warn() - except RuntimeError: - old_loop = None - _set_event_loop_policy(policy) - try: - yield - finally: - _set_event_loop_policy(old_loop_policy) - _set_event_loop(old_loop) - - -def _get_event_loop_policy() -> AbstractEventLoopPolicy: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - return asyncio.get_event_loop_policy() - - -def _set_event_loop_policy(policy: AbstractEventLoopPolicy) -> None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - asyncio.set_event_loop_policy(policy) - - -def _get_event_loop_no_warn( - policy: AbstractEventLoopPolicy | None = None, -) -> asyncio.AbstractEventLoop: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - if policy is not None: - return policy.get_event_loop() - else: - return asyncio.get_event_loop() - - -def _set_event_loop(loop: AbstractEventLoop | None) -> None: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - asyncio.set_event_loop(loop) - - -@pytest.hookimpl(tryfirst=True, hookwrapper=True) -def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: - """Pytest hook called before a test case is run.""" - if pyfuncitem.get_closest_marker("asyncio") is not None: - if is_async_test(pyfuncitem): - asyncio_mode = _get_asyncio_mode(pyfuncitem.config) - for fixname, fixtures in pyfuncitem._fixtureinfo.name2fixturedefs.items(): - # name2fixturedefs is a dict between fixture name and a list of matching - # fixturedefs. The last entry in the list is closest and the one used. - func = fixtures[-1].func - if ( - asyncio_mode == Mode.STRICT - and _is_coroutine_or_asyncgen(func) - and not _is_asyncio_fixture_function(func) - ): - warnings.warn( - PytestDeprecationWarning( - f"asyncio test {pyfuncitem.name!r} requested async " - "@pytest.fixture " - f"{fixname!r} in strict mode. " - "You might want to use @pytest_asyncio.fixture or switch " - "to auto mode. " - "This will become an error in future versions of " - "pytest-asyncio." - ), - stacklevel=1, - ) - # no stacklevel points at the users code, so we set stacklevel=1 - # so it at least indicates that it's the plugin complaining. - # Pytest gives the test file & name in the warnings summary at least - - else: - pyfuncitem.warn( - pytest.PytestWarning( - f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' " - "but it is not an async function. " - "Please remove the asyncio mark. " - "If the test is not marked explicitly, " - "check for global marks applied via 'pytestmark'." - ) - ) - yield + _preprocess_async_fixtures(collector.config, _HOLDER) return None -def _synchronize_coroutine( - func: Callable[..., CoroutineType], - runner: asyncio.Runner, - context: contextvars.Context, +def pytest_collection_modifyitems( + session: Session, config: Config, items: List[Item] +) -> None: + """ + Marks collected async test items as `asyncio` tests. + + The mark is only applied in `AUTO` mode. It is applied to: + + - coroutines + - staticmethods wrapping coroutines + - Hypothesis tests wrapping coroutines + + """ + if _get_asyncio_mode(config) != Mode.AUTO: + return + function_items = (item for item in items if isinstance(item, Function)) + for function_item in function_items: + function = function_item.obj + if isinstance(function, staticmethod): + # staticmethods need to be unwrapped. + function = function.__func__ + if ( + _is_coroutine(function) + or _is_hypothesis_test(function) + and _hypothesis_test_wraps_coroutine(function) + ): + function_item.add_marker("asyncio") + + +def _hypothesis_test_wraps_coroutine(function: Any) -> bool: + return _is_coroutine(function.hypothesis.inner_test) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup( + fixturedef: FixtureDef, request: SubRequest +) -> Optional[object]: + """Adjust the event loop policy when an event loop is produced.""" + if fixturedef.argname == "event_loop": + # The use of a fixture finalizer is preferred over the + # pytest_fixture_post_finalizer hook. The fixture finalizer is invoked once + # for each fixture, whereas the hook may be invoked multiple times for + # any specific fixture. + # see https://github.com/pytest-dev/pytest/issues/5848 + _add_finalizers( + fixturedef, + _close_event_loop, + _provide_clean_event_loop, + ) + outcome = yield + loop = outcome.get_result() + policy = asyncio.get_event_loop_policy() + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + old_loop = policy.get_event_loop() + if old_loop is not loop: + old_loop.close() + except RuntimeError: + # Either the current event loop has been set to None + # or the loop policy doesn't specify to create new loops + # or we're not in the main thread + pass + policy.set_event_loop(loop) + return + + yield + + +def _add_finalizers(fixturedef: FixtureDef, *finalizers: Callable[[], object]) -> None: + """ + Regsiters the specified fixture finalizers in the fixture. + + Finalizers need to specified in the exact order in which they should be invoked. + + :param fixturedef: Fixture definition which finalizers should be added to + :param finalizers: Finalizers to be added + """ + for finalizer in reversed(finalizers): + fixturedef.addfinalizer(finalizer) + + +_UNCLOSED_EVENT_LOOP_WARNING = dedent( + """\ + pytest-asyncio detected an unclosed event loop when tearing down the event_loop + fixture: %r + pytest-asyncio will close the event loop for you, but future versions of the + library will no longer do so. In order to ensure compatibility with future + versions, please make sure that: + 1. Any custom "event_loop" fixture properly closes the loop after yielding it + 2. The scopes of your custom "event_loop" fixtures do not overlap + 3. Your code does not modify the event loop in async fixtures or tests + """ +) + + +def _close_event_loop() -> None: + policy = asyncio.get_event_loop_policy() + try: + loop = policy.get_event_loop() + except RuntimeError: + loop = None + if loop is not None: + if not loop.is_closed(): + warnings.warn( + _UNCLOSED_EVENT_LOOP_WARNING % loop, + DeprecationWarning, + ) + loop.close() + + +def _provide_clean_event_loop() -> None: + # At this point, the event loop for the current thread is closed. + # When a user calls asyncio.get_event_loop(), they will get a closed loop. + # In order to avoid this side effect from pytest-asyncio, we need to replace + # the current loop with a fresh one. + # Note that we cannot set the loop to None, because get_event_loop only creates + # a new loop, when set_event_loop has not been called. + policy = asyncio.get_event_loop_policy() + new_loop = policy.new_event_loop() + policy.set_event_loop(new_loop) + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Optional[object]: + """ + Pytest hook called before a test case is run. + + Wraps marked tests in a synchronous function + where the wrapped test coroutine is executed in an event loop. + """ + marker = pyfuncitem.get_closest_marker("asyncio") + if marker is not None: + funcargs: Dict[str, object] = pyfuncitem.funcargs # type: ignore[name-defined] + loop = cast(asyncio.AbstractEventLoop, funcargs["event_loop"]) + if _is_hypothesis_test(pyfuncitem.obj): + pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( + pyfuncitem, + pyfuncitem.obj.hypothesis.inner_test, + _loop=loop, + ) + else: + pyfuncitem.obj = wrap_in_sync( + pyfuncitem, + pyfuncitem.obj, + _loop=loop, + ) + yield + + +def _is_hypothesis_test(function: Any) -> bool: + return getattr(function, "is_hypothesis_test", False) + + +def wrap_in_sync( + pyfuncitem: pytest.Function, + func: Callable[..., Awaitable[Any]], + _loop: asyncio.AbstractEventLoop, ): - """ - Return a sync wrapper around a coroutine executing it in the - specified runner and context. - """ + """Return a sync wrapper around an async function executing it in the + current event loop.""" + + # if the function is already wrapped, we rewrap using the original one + # not using __wrapped__ because the original function may already be + # a wrapped one + raw_func = getattr(func, "_raw_test_func", None) + if raw_func is not None: + func = raw_func @functools.wraps(func) def inner(*args, **kwargs): coro = func(*args, **kwargs) - runner.run(coro, context=context) + if not inspect.isawaitable(coro): + pyfuncitem.warn( + pytest.PytestWarning( + f"The test {pyfuncitem} is marked with '@pytest.mark.asyncio' " + "but it is not an async function. " + "Please remove asyncio marker. " + "If the test is not marked explicitly, " + "check for global markers applied via 'pytestmark'." + ) + ) + return + task = asyncio.ensure_future(coro, loop=_loop) + try: + _loop.run_until_complete(task) + except BaseException: + # run_until_complete doesn't get the result from exceptions + # that are not subclasses of `Exception`. Consume all + # exceptions to prevent asyncio's warning from logging. + if task.done() and not task.cancelled(): + task.exception() + raise + inner._raw_test_func = func # type: ignore[attr-defined] return inner -@pytest.hookimpl(wrapper=True) -def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None: - asyncio_mode = _get_asyncio_mode(request.config) - if not _is_asyncio_fixture_function(fixturedef.func): - if asyncio_mode == Mode.STRICT: - # Ignore async fixtures without explicit asyncio mark in strict mode - # This applies to pytest_trio fixtures, for example - return (yield) - if not _is_coroutine_or_asyncgen(fixturedef.func): - return (yield) - default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope") - loop_scope = ( - getattr(fixturedef.func, "_loop_scope", None) - or default_loop_scope - or fixturedef.scope - ) - runner_fixture_id = f"_{loop_scope}_scoped_runner" - runner = request.getfixturevalue(runner_fixture_id) - synchronizer = _fixture_synchronizer(fixturedef, runner, request) - _make_asyncio_fixture_function(synchronizer, loop_scope) - with MonkeyPatch.context() as c: - c.setattr(fixturedef, "func", synchronizer) - hook_result = yield - return hook_result - - -_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR = """\ -An asyncio pytest marker defines both "scope" and "loop_scope", \ -but it should only use "loop_scope". -""" - -_MARKER_SCOPE_KWARG_DEPRECATION_WARNING = """\ -The "scope" keyword argument to the asyncio marker has been deprecated. \ -Please use the "loop_scope" argument instead. -""" - - -def _get_marked_loop_scope( - asyncio_marker: Mark, default_loop_scope: _ScopeName -) -> _ScopeName: - assert asyncio_marker.name == "asyncio" - if asyncio_marker.args or ( - asyncio_marker.kwargs and set(asyncio_marker.kwargs) - {"loop_scope", "scope"} +def pytest_runtest_setup(item: pytest.Item) -> None: + marker = item.get_closest_marker("asyncio") + if marker is None: + return + fixturenames = item.fixturenames # type: ignore[attr-defined] + # inject an event loop fixture for all async tests + if "event_loop" in fixturenames: + fixturenames.remove("event_loop") + fixturenames.insert(0, "event_loop") + obj = getattr(item, "obj", None) + if not getattr(obj, "hypothesis", False) and getattr( + obj, "is_hypothesis_test", False ): - raise ValueError("mark.asyncio accepts only a keyword argument 'loop_scope'.") - if "scope" in asyncio_marker.kwargs: - if "loop_scope" in asyncio_marker.kwargs: - raise pytest.UsageError(_DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR) - warnings.warn(PytestDeprecationWarning(_MARKER_SCOPE_KWARG_DEPRECATION_WARNING)) - scope = asyncio_marker.kwargs.get("loop_scope") or asyncio_marker.kwargs.get( - "scope" - ) - if scope is None: - scope = default_loop_scope - assert scope in {"function", "class", "module", "package", "session"} - return scope + pytest.fail( + "test function `%r` is using Hypothesis, but pytest-asyncio " + "only works with Hypothesis 3.64.0 or later." % item + ) -def _get_default_test_loop_scope(config: Config) -> Any: - return config.getini("asyncio_default_test_loop_scope") - - -_RUNNER_TEARDOWN_WARNING = """\ -An exception occurred during teardown of an asyncio.Runner. \ -The reason is likely that you closed the underlying event loop in a test, \ -which prevents the cleanup of asynchronous generators by the runner. -This warning will become an error in future versions of pytest-asyncio. \ -Please ensure that your tests don't close the event loop. \ -Here is the traceback of the exception triggered during teardown: -%s -""" - - -def _create_scoped_runner_fixture(scope: _ScopeName) -> Callable: - @pytest.fixture( - scope=scope, - name=f"_{scope}_scoped_runner", - ) - def _scoped_runner( - event_loop_policy, - request: FixtureRequest, - ) -> Iterator[Runner]: - new_loop_policy = event_loop_policy - debug_mode = _get_asyncio_debug(request.config) - with _temporary_event_loop_policy(new_loop_policy): - runner = Runner(debug=debug_mode).__enter__() - try: - yield runner - except Exception as e: - runner.__exit__(type(e), e, e.__traceback__) - else: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", ".*BaseEventLoop.shutdown_asyncgens.*", RuntimeWarning - ) - try: - runner.__exit__(None, None, None) - except RuntimeError: - warnings.warn( - _RUNNER_TEARDOWN_WARNING % traceback.format_exc(), - RuntimeWarning, - ) - - return _scoped_runner - - -for scope in Scope: - globals()[f"_{scope.value}_scoped_runner"] = _create_scoped_runner_fixture( - scope.value - ) - - -@pytest.fixture(scope="session", autouse=True) -def event_loop_policy() -> AbstractEventLoopPolicy: - """Return an instance of the policy used to create asyncio event loops.""" - return _get_event_loop_policy() - - -def is_async_test(item: Item) -> TypeIs[PytestAsyncioFunction]: - """Returns whether a test item is a pytest-asyncio test""" - return isinstance(item, PytestAsyncioFunction) +@pytest.fixture +def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: + """Create an instance of the default event loop for each test case.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() def _unused_port(socket_type: int) -> int: diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/licenses/AUTHORS.rst b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/AUTHORS.rst similarity index 83% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/licenses/AUTHORS.rst rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/AUTHORS.rst index 42933ffa..61e7915c 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/licenses/AUTHORS.rst +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/AUTHORS.rst @@ -1,16 +1,15 @@ - Authors ======= -* Marc Schlaich - https://github.com/schlamar (\http://www.schlamar.org) +* Marc Schlaich - http://www.schlamar.org * Rick van Hattem - http://wol.ph * Buck Evan - https://github.com/bukzor * Eric Larson - http://larsoner.com -* Marc Abramowitz - \http://marc-abramowitz.com +* Marc Abramowitz - http://marc-abramowitz.com * Thomas Kluyver - https://github.com/takluyver * Guillaume Ayoub - http://www.yabz.fr -* Federico Ceratto - \http://firelet.net -* Josh Kalderimis - \http://blog.cookiestack.com +* Federico Ceratto - http://firelet.net +* Josh Kalderimis - http://blog.cookiestack.com * Ionel Cristian Mărieș - https://blog.ionelmc.ro * Christian Ledermann - https://github.com/cleder * Alec Nikolas Reiter - https://github.com/justanr @@ -19,7 +18,7 @@ Authors * Michael Elovskikh - https://github.com/wronglink * Saurabh Kumar - https://github.com/theskumar * Michael Elovskikh - https://github.com/wronglink -* Daniel Hahler - https://github.com/blueyed (\https://daniel.hahler.de) +* Daniel Hahler - https://daniel.hahler.de * Florian Bruhin - http://www.the-compiler.org * Zoltan Kozma - https://github.com/kozmaz87 * Francis Niu - https://flniu.github.io @@ -60,8 +59,3 @@ Authors * Christian Fetzer - https://github.com/fetzerch * Jonathan Stewmon - https://github.com/jstewmon * Matthew Gamble - https://github.com/mwgamble -* Christian Clauss - https://github.com/cclauss -* Dawn James - https://github.com/dawngerpony -* Tsvika Shapira - https://github.com/tsvikas -* Marcos Boger - https://github.com/marcosboger -* Ofek Lev - https://github.com/ofek diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/INSTALLER b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/INSTALLER similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/INSTALLER rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/INSTALLER diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/licenses/LICENSE b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/LICENSE similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/licenses/LICENSE rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/LICENSE diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/METADATA b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/METADATA similarity index 76% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/METADATA rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/METADATA index 84ef0a68..c39eabd7 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/METADATA +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/METADATA @@ -1,17 +1,15 @@ -Metadata-Version: 2.4 +Metadata-Version: 2.1 Name: pytest-cov -Version: 7.0.0 +Version: 4.1.0 Summary: Pytest plugin for measuring coverage. -Project-URL: Sources, https://github.com/pytest-dev/pytest-cov +Home-page: https://github.com/pytest-dev/pytest-cov +Author: Marc Schlaich +Author-email: marc.schlaich@gmail.com +License: MIT Project-URL: Documentation, https://pytest-cov.readthedocs.io/ Project-URL: Changelog, https://pytest-cov.readthedocs.io/en/latest/changelog.html Project-URL: Issue Tracker, https://github.com/pytest-dev/pytest-cov/issues -Author-email: Marc Schlaich -Maintainer-email: Ionel Cristian Mărieș -License-Expression: MIT -License-File: AUTHORS.rst -License-File: LICENSE -Keywords: cover,coverage,distributed,parallel,py.test,pytest +Keywords: cover,coverage,pytest,py.test,distributed,parallel Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers @@ -22,24 +20,27 @@ Classifier: Operating System :: Unix Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Utilities -Requires-Python: >=3.9 -Requires-Dist: coverage[toml]>=7.10.6 -Requires-Dist: pluggy>=1.2 -Requires-Dist: pytest>=7 +Requires-Python: >=3.7 +License-File: LICENSE +License-File: AUTHORS.rst +Requires-Dist: pytest (>=4.6) +Requires-Dist: coverage[toml] (>=5.2.1) Provides-Extra: testing -Requires-Dist: process-tests; extra == 'testing' -Requires-Dist: pytest-xdist; extra == 'testing' -Requires-Dist: virtualenv; extra == 'testing' -Description-Content-Type: text/x-rst +Requires-Dist: fields ; extra == 'testing' +Requires-Dist: hunter ; extra == 'testing' +Requires-Dist: process-tests ; extra == 'testing' +Requires-Dist: six ; extra == 'testing' +Requires-Dist: pytest-xdist ; extra == 'testing' +Requires-Dist: virtualenv ; extra == 'testing' ======== Overview @@ -53,24 +54,39 @@ Overview * - docs - |docs| * - tests - - |github-actions| + - | |github-actions| |requires| + | * - package - - |version| |conda-forge| |wheel| |supported-versions| |supported-implementations| |commits-since| + - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations| + | |commits-since| .. |docs| image:: https://readthedocs.org/projects/pytest-cov/badge/?style=flat - :target: https://readthedocs.org/projects/pytest-cov/ + :target: https://readthedocs.org/projects/pytest-cov :alt: Documentation Status .. |github-actions| image:: https://github.com/pytest-dev/pytest-cov/actions/workflows/test.yml/badge.svg :alt: GitHub Actions Status :target: https://github.com/pytest-dev/pytest-cov/actions +.. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true + :alt: AppVeyor Build Status + :target: https://ci.appveyor.com/project/pytestbot/pytest-cov + +.. |requires| image:: https://requires.io/github/pytest-dev/pytest-cov/requirements.svg?branch=master + :alt: Requirements Status + :target: https://requires.io/github/pytest-dev/pytest-cov/requirements/?branch=master + .. |version| image:: https://img.shields.io/pypi/v/pytest-cov.svg :alt: PyPI Package latest release :target: https://pypi.org/project/pytest-cov .. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pytest-cov.svg :target: https://anaconda.org/conda-forge/pytest-cov + +.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v4.1.0.svg + :alt: Commits since latest release + :target: https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...master + .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg :alt: PyPI Wheel :target: https://pypi.org/project/pytest-cov @@ -83,17 +99,12 @@ Overview :alt: Supported implementations :target: https://pypi.org/project/pytest-cov -.. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v7.0.0.svg - :alt: Commits since latest release - :target: https://github.com/pytest-dev/pytest-cov/compare/v7.0.0...master - .. end-badges -This plugin provides coverage functionality as a pytest plugin. Compared to just using ``coverage run`` this plugin does some extras: +This plugin produces coverage reports. Compared to just using ``coverage run`` this plugin does some extras: -* Automatic erasing and combination of .coverage files and default reporting. -* Support for detailed coverage contexts (add ``--cov-context=test`` to have the full test name including parametrization as the context). -* Xdist support: you can use all of pytest-xdist's features including remote interpreters and still get coverage. +* Subprocess support: you can fork or run stuff in a subprocess and will get covered without any fuss. +* Xdist support: you can use all of pytest-xdist's features and still get coverage. * Consistent pytest behavior. If you run ``coverage run -m pytest`` you will have slightly different ``sys.path`` (CWD will be in it, unlike when running ``pytest``). @@ -113,10 +124,11 @@ For distributed testing support install pytest-xdist:: pip install pytest-xdist -Upgrading from pytest-cov 6.3 ------------------------------ +Upgrading from ancient pytest-cov +--------------------------------- -`pytest-cov 6.3` and older were using a ``.pth`` file to enable coverage measurements in subprocesses. This was removed in `pytest-cov 7` - use `coverage's patch options `_ to enable subprocess measurements. +`pytest-cov 2.0` is using a new ``.pth`` file (``pytest-cov.pth``). You may want to manually remove the older +``init_cov_core.pth`` from site-packages as it's not automatically removed. Uninstalling ------------ @@ -152,7 +164,11 @@ Would produce a report like:: Documentation ============= -https://pytest-cov.readthedocs.io/en/latest/ + http://pytest-cov.rtfd.org/ + + + + Coverage Data File @@ -168,15 +184,15 @@ examine it. Limitations =========== -For distributed testing the workers must have the pytest-cov package installed. This is needed since +For distributed testing the workers must have the pytest-cov package installed. This is needed since the plugin must be registered through setuptools for pytest to start the plugin on the worker. -Security -======== +For subprocess measurement environment variables must make it from the main process to the +subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must +do normal site initialisation so that the environment variables can be detected and coverage +started. -To report a security vulnerability please use the `Tidelift security contact `_. -Tidelift will coordinate the fix and disclosure. Acknowledgements ================ @@ -198,115 +214,6 @@ No doubt others have contributed to these tools as well. Changelog ========= -7.0.0 (2025-09-09) ------------------- - -* Dropped support for subprocesses measurement. - - It was a feature added long time ago when coverage lacked a nice way to measure subprocesses created in tests. - It relied on a ``.pth`` file, there was no way to opt-out and it created bad interations - with `coverage's new patch system `_ added - in `7.10 `_. - - To migrate to this release you might need to enable the suprocess patch, example for ``.coveragerc``: - - .. code-block:: ini - - [run] - patch = subprocess - - This release also requires at least coverage 7.10.6. -* Switched packaging to have metadata completely in ``pyproject.toml`` and use `hatchling `_ for - building. - Contributed by Ofek Lev in `#551 `_ - with some extras in `#716 `_. -* Removed some not really necessary testing deps like ``six``. - -6.3.0 (2025-09-06) ------------------- - -* Added support for markdown reports. - Contributed by Marcos Boger in `#712 `_ - and `#714 `_. -* Fixed some formatting issues in docs. - Anonymous contribution in `#706 `_. - -6.2.1 (2025-06-12) ------------------- - -* Added a version requirement for pytest's pluggy dependency (1.2.0, released 2023-06-21) that has the required new-style hookwrapper API. -* Removed deprecated license classifier (packaging). -* Disabled coverage warnings in two more situations where they have no value: - - * "module-not-measured" in workers - * "already-imported" in subprocesses - -6.2.0 (2025-06-11) ------------------- - -* The plugin now adds 3 rules in the filter warnings configuration to prevent common coverage warnings being raised as obscure errors:: - - default:unclosed database in `_. -* Removed unnecessary CovFailUnderWarning. Fixes `#675 `_. -* Fixed the term report not using the precision specified via ``--cov-precision``. - - -6.0.0 (2024-10-29) ------------------- - -* Updated various documentation inaccuracies, especially on subprocess handling. -* Changed fail under checks to use the precision set in the coverage configuration. - Now it will perform the check just like ``coverage report`` would. -* Added a ``--cov-precision`` cli option that can override the value set in your coverage configuration. -* Dropped support for now EOL Python 3.8. - -5.0.0 (2024-03-24) ------------------- - -* Removed support for xdist rsync (now deprecated). - Contributed by Matthias Reichenbach in `#623 `_. -* Switched docs theme to Furo. -* Various legacy Python cleanup and CI improvements. - Contributed by Christian Clauss and Hugo van Kemenade in - `#630 `_, - `#631 `_, - `#632 `_ and - `#633 `_. -* Added a ``pyproject.toml`` example in the docs. - Contributed by Dawn James in `#626 `_. -* Modernized project's pre-commit hooks to use ruff. Initial POC contributed by - Christian Clauss in `#584 `_. -* Dropped support for Python 3.7. - 4.1.0 (2023-05-24) ------------------ @@ -322,7 +229,6 @@ Changelog Contributed by Mark Mayo in `#572 `_. * Fixed a skip in the test suite for some old xdist. Contributed by a bunch of people in `#565 `_. -* Dropped support for Python 3.6. 4.0.0 (2022-09-28) @@ -351,7 +257,7 @@ Changelog Contributed by Andre Brisco in `#543 `_ and Colin O'Dell in `#525 `_. * Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+. - Contributed by Christian Fetzer in `#536 `_. + Contributed by Christian Fetzer in `#536 `_. * Modernized pytest hook implementation. Contributed by Bruno Oliveira in `#549 `_ and Ronny Pfannschmidt in `#550 `_. @@ -422,7 +328,7 @@ Changelog * Removed the empty `console_scripts` entrypoint that confused some Gentoo build script. I didn't ask why it was so broken cause I didn't want to ruin my day. Contributed by Michał Górny in `#434 `_. -* Fixed the missing `coverage context `_ +* Fixed the missing `coverage context `_ when using subprocesses. Contributed by Bernát Gábor in `#443 `_. * Updated the config section in the docs. @@ -451,7 +357,7 @@ Changelog * Made pytest startup faster when plugin not active by lazy-importing. Contributed by Anders Hovmöller in `#339 `_. * Various CI improvements. - Contributed by Daniel Hahler in `#363 `_ and + Contributed by Daniel Hahler in `#363 `_ and `#364 `_. * Various Python support updates (drop EOL 3.4, test against 3.8 final). Contributed by Hugo van Kemenade in @@ -478,8 +384,8 @@ Changelog ------------------ * Fixed ``RecursionError`` that can occur when using - `cleanup_on_signal `__ or - `cleanup_on_sigterm `__. + `cleanup_on_signal `__ or + `cleanup_on_sigterm `__. See: `#294 `_. The 2.7.x releases of pytest-cov should be considered broken regarding aforementioned cleanup API. * Added compatibility with future xdist release that deprecates some internals diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/RECORD new file mode 100644 index 00000000..ead5ed16 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/RECORD @@ -0,0 +1,20 @@ +pytest-cov.pth,sha256=9HRGpg_fWQXoTn18iSuvkvjVoyJtDaFZm5wBTqtsfds,377 +pytest_cov-4.1.0.dist-info/AUTHORS.rst,sha256=-Uhe-93ZhCyiK1Dc_a9S2GD0ILdw5XYnBq76OeEulkE,2742 +pytest_cov-4.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pytest_cov-4.1.0.dist-info/LICENSE,sha256=g1WGrhVnZqJOPBA_vFXZr2saFt9XypMsl0gqJzf9g9U,1071 +pytest_cov-4.1.0.dist-info/METADATA,sha256=C6z3RsxyhDvKqw0smWc6D2kkqYY7nBqMRLlokx96MJU,26710 +pytest_cov-4.1.0.dist-info/RECORD,, +pytest_cov-4.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pytest_cov-4.1.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +pytest_cov-4.1.0.dist-info/entry_points.txt,sha256=1Wx3pjYCY2v4ATD5dhlQN6Ta-C4LKmBa1fXhiEX6C8A,42 +pytest_cov-4.1.0.dist-info/top_level.txt,sha256=HvYHsAFV4MeTUNUwhawY_DKvrpE2lYratTHX_U45oBU,11 +pytest_cov/__init__.py,sha256=or_DHkDGoBkb3MEivnwTc1Xoqx3-4aYbDjx26zvGuc4,93 +pytest_cov/__pycache__/__init__.cpython-312.pyc,, +pytest_cov/__pycache__/compat.cpython-312.pyc,, +pytest_cov/__pycache__/embed.cpython-312.pyc,, +pytest_cov/__pycache__/engine.cpython-312.pyc,, +pytest_cov/__pycache__/plugin.cpython-312.pyc,, +pytest_cov/compat.py,sha256=s1mdF7htBSQdsLry2Z8N8m8vpVGIU9apAJOcj-5Iz48,560 +pytest_cov/embed.py,sha256=sUyiA6eJplIQ7RIH_fDlcwDC-vP7cMDcnz9hWChpyeM,3556 +pytest_cov/engine.py,sha256=WcZHftFONt9usODxyhDufcTCGdgerzuuzF5KBQK2yB8,15880 +pytest_cov/plugin.py,sha256=ojjZaYqgmNy2jHFnyS-SWbfYcjKGVFsa7CNCPicUsNQ,15268 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/REQUESTED similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/REQUESTED rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/REQUESTED diff --git a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/WHEEL similarity index 65% rename from Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/WHEEL index e7fa31b6..becc9a66 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest-9.0.1.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: setuptools (80.9.0) +Generator: bdist_wheel (0.37.1) Root-Is-Purelib: true Tag: py3-none-any diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/entry_points.txt b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/entry_points.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/entry_points.txt rename to Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/entry_points.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/top_level.txt new file mode 100644 index 00000000..a2fe2811 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov-4.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +pytest_cov diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/RECORD deleted file mode 100644 index c3e93b79..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/RECORD +++ /dev/null @@ -1,14 +0,0 @@ -pytest_cov-7.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pytest_cov-7.0.0.dist-info/METADATA,sha256=XwG_4psf73MF1IuSOpbk3Bkfd_uHKY2yB8t2hejOXjs,31158 -pytest_cov-7.0.0.dist-info/RECORD,, -pytest_cov-7.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pytest_cov-7.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 -pytest_cov-7.0.0.dist-info/entry_points.txt,sha256=1Wx3pjYCY2v4ATD5dhlQN6Ta-C4LKmBa1fXhiEX6C8A,42 -pytest_cov-7.0.0.dist-info/licenses/AUTHORS.rst,sha256=o1bLAu5ccigi9VR3NRNqGofxlLqHmVLqKBKxfiKY0YI,3032 -pytest_cov-7.0.0.dist-info/licenses/LICENSE,sha256=g1WGrhVnZqJOPBA_vFXZr2saFt9XypMsl0gqJzf9g9U,1071 -pytest_cov/__init__.py,sha256=-4_bNtyFDPe7lbiB7b4QUVyj92LbXcV4bJtMH6BLblE,908 -pytest_cov/__pycache__/__init__.cpython-312.pyc,, -pytest_cov/__pycache__/engine.cpython-312.pyc,, -pytest_cov/__pycache__/plugin.cpython-312.pyc,, -pytest_cov/engine.py,sha256=lcYQQZygQtTnJOd8yjLGpmL2fmN7Mdpx7zKnhxiIW5g,17161 -pytest_cov/plugin.py,sha256=BoORWcw9ns3YTmMyDWT75GTnekYH21WK4vUC7nDocv4,17084 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__init__.py b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__init__.py index 62d553a4..8839990b 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__init__.py @@ -1,41 +1,2 @@ """pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE.""" - -__version__ = '7.0.0' - -import pytest - - -class CoverageError(Exception): - """Indicates that our coverage is too low""" - - -class PytestCovWarning(pytest.PytestWarning): - """ - The base for all pytest-cov warnings, never raised directly. - """ - - -class CovDisabledWarning(PytestCovWarning): - """ - Indicates that Coverage was manually disabled. - """ - - -class CovReportWarning(PytestCovWarning): - """ - Indicates that we failed to generate a report. - """ - - -class CentralCovContextWarning(PytestCovWarning): - """ - Indicates that dynamic_context was set to test_function instead of using the builtin --cov-context. - """ - - -class DistCovError(Exception): - """ - Raised when dynamic_context is set to test_function and xdist is also used. - - See: https://github.com/pytest-dev/pytest-cov/issues/604 - """ +__version__ = '4.1.0' diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e03c7169 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..838c36d0 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/embed.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/embed.cpython-312.pyc new file mode 100644 index 00000000..58843b11 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/embed.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/engine.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/engine.cpython-312.pyc new file mode 100644 index 00000000..4c18785c Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/engine.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/plugin.cpython-312.pyc new file mode 100644 index 00000000..be04affa Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_cov/__pycache__/plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/compat.py b/Backend/venv/lib/python3.12/site-packages/pytest_cov/compat.py new file mode 100644 index 00000000..614419cb --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov/compat.py @@ -0,0 +1,24 @@ +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + + +StringIO # pyflakes, this is for re-export + + +class SessionWrapper: + def __init__(self, session): + self._session = session + if hasattr(session, 'testsfailed'): + self._attr = 'testsfailed' + else: + self._attr = '_testsfailed' + + @property + def testsfailed(self): + return getattr(self._session, self._attr) + + @testsfailed.setter + def testsfailed(self, value): + setattr(self._session, self._attr, value) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/embed.py b/Backend/venv/lib/python3.12/site-packages/pytest_cov/embed.py new file mode 100644 index 00000000..f8a2749f --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov/embed.py @@ -0,0 +1,122 @@ +"""Activate coverage at python startup if appropriate. + +The python site initialisation will ensure that anything we import +will be removed and not visible at the end of python startup. However +we minimise all work by putting these init actions in this separate +module and only importing what is needed when needed. + +For normal python startup when coverage should not be activated the pth +file checks a single env var and does not import or call the init fn +here. + +For python startup when an ancestor process has set the env indicating +that code coverage is being collected we activate coverage based on +info passed via env vars. +""" +import atexit +import os +import signal + +_active_cov = None + + +def init(): + # Only continue if ancestor process has set everything needed in + # the env. + global _active_cov + + cov_source = os.environ.get('COV_CORE_SOURCE') + cov_config = os.environ.get('COV_CORE_CONFIG') + cov_datafile = os.environ.get('COV_CORE_DATAFILE') + cov_branch = True if os.environ.get('COV_CORE_BRANCH') == 'enabled' else None + cov_context = os.environ.get('COV_CORE_CONTEXT') + + if cov_datafile: + if _active_cov: + cleanup() + # Import what we need to activate coverage. + import coverage + + # Determine all source roots. + if cov_source in os.pathsep: + cov_source = None + else: + cov_source = cov_source.split(os.pathsep) + if cov_config == os.pathsep: + cov_config = True + + # Activate coverage for this process. + cov = _active_cov = coverage.Coverage( + source=cov_source, + branch=cov_branch, + data_suffix=True, + config_file=cov_config, + auto_data=True, + data_file=cov_datafile + ) + cov.load() + cov.start() + if cov_context: + cov.switch_context(cov_context) + cov._warn_no_data = False + cov._warn_unimported_source = False + return cov + + +def _cleanup(cov): + if cov is not None: + cov.stop() + cov.save() + cov._auto_save = False # prevent autosaving from cov._atexit in case the interpreter lacks atexit.unregister + try: + atexit.unregister(cov._atexit) + except Exception: + pass + + +def cleanup(): + global _active_cov + global _cleanup_in_progress + global _pending_signal + + _cleanup_in_progress = True + _cleanup(_active_cov) + _active_cov = None + _cleanup_in_progress = False + if _pending_signal: + pending_signal = _pending_signal + _pending_signal = None + _signal_cleanup_handler(*pending_signal) + + +_previous_handlers = {} +_pending_signal = None +_cleanup_in_progress = False + + +def _signal_cleanup_handler(signum, frame): + global _pending_signal + if _cleanup_in_progress: + _pending_signal = signum, frame + return + cleanup() + _previous_handler = _previous_handlers.get(signum) + if _previous_handler == signal.SIG_IGN: + return + elif _previous_handler and _previous_handler is not _signal_cleanup_handler: + _previous_handler(signum, frame) + elif signum == signal.SIGTERM: + os._exit(128 + signum) + elif signum == signal.SIGINT: + raise KeyboardInterrupt() + + +def cleanup_on_signal(signum): + previous = signal.getsignal(signum) + if previous is not _signal_cleanup_handler: + _previous_handlers[signum] = previous + signal.signal(signum, _signal_cleanup_handler) + + +def cleanup_on_sigterm(): + cleanup_on_signal(signal.SIGTERM) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/engine.py b/Backend/venv/lib/python3.12/site-packages/pytest_cov/engine.py index ca631272..97d4d017 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov/engine.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov/engine.py @@ -1,28 +1,17 @@ """Coverage controllers for use by pytest-cov and nose-cov.""" - -import argparse import contextlib import copy import functools import os import random -import shutil import socket import sys -import warnings -from pathlib import Path -from typing import Union import coverage from coverage.data import CoverageData -from coverage.sqldata import filename_suffix -from . import CentralCovContextWarning -from . import DistCovError - - -class BrokenCovConfigError(Exception): - pass +from .compat import StringIO +from .embed import cleanup class _NullFile: @@ -45,7 +34,7 @@ def _ensure_topdir(meth): @functools.wraps(meth) def ensure_topdir_wrapper(self, *args, **kwargs): try: - original_cwd = Path.cwd() + original_cwd = os.getcwd() except OSError: # Looks like it's gone, this is non-ideal because a side-effect will # be introduced in the tests here but we can't do anything about it. @@ -63,14 +52,13 @@ def _ensure_topdir(meth): class CovController: """Base class for different plugin implementations.""" - def __init__(self, options: argparse.Namespace, config: Union[None, object], nodeid: Union[None, str]): + def __init__(self, cov_source, cov_report, cov_config, cov_append, cov_branch, config=None, nodeid=None): """Get some common config used by multiple derived classes.""" - self.cov_source = options.cov_source - self.cov_report = options.cov_report - self.cov_config = options.cov_config - self.cov_append = options.cov_append - self.cov_branch = options.cov_branch - self.cov_precision = options.cov_precision + self.cov_source = cov_source + self.cov_report = cov_report + self.cov_config = cov_config + self.cov_append = cov_append + self.cov_branch = cov_branch self.config = config self.nodeid = nodeid @@ -79,73 +67,67 @@ class CovController: self.data_file = None self.node_descs = set() self.failed_workers = [] - self.topdir = os.fspath(Path.cwd()) + self.topdir = os.getcwd() self.is_collocated = None - self.started = False @contextlib.contextmanager def ensure_topdir(self): - original_cwd = Path.cwd() + original_cwd = os.getcwd() os.chdir(self.topdir) yield os.chdir(original_cwd) @_ensure_topdir def pause(self): - self.started = False self.cov.stop() + self.unset_env() @_ensure_topdir def resume(self): self.cov.start() - self.started = True + self.set_env() - def start(self): - self.started = True + @_ensure_topdir + def set_env(self): + """Put info about coverage into the env so that subprocesses can activate coverage.""" + if self.cov_source is None: + os.environ['COV_CORE_SOURCE'] = os.pathsep + else: + os.environ['COV_CORE_SOURCE'] = os.pathsep.join(self.cov_source) + config_file = os.path.abspath(self.cov_config) + if os.path.exists(config_file): + os.environ['COV_CORE_CONFIG'] = config_file + else: + os.environ['COV_CORE_CONFIG'] = os.pathsep + os.environ['COV_CORE_DATAFILE'] = os.path.abspath(self.cov.config.data_file) + if self.cov_branch: + os.environ['COV_CORE_BRANCH'] = 'enabled' - def finish(self): - self.started = False + @staticmethod + def unset_env(): + """Remove coverage info from env.""" + os.environ.pop('COV_CORE_SOURCE', None) + os.environ.pop('COV_CORE_CONFIG', None) + os.environ.pop('COV_CORE_DATAFILE', None) + os.environ.pop('COV_CORE_BRANCH', None) + os.environ.pop('COV_CORE_CONTEXT', None) @staticmethod def get_node_desc(platform, version_info): """Return a description of this node.""" - return 'platform {}, python {}'.format(platform, '{}.{}.{}-{}-{}'.format(*version_info[:5])) + return 'platform {}, python {}'.format(platform, '%s.%s.%s-%s-%s' % version_info[:5]) @staticmethod - def get_width(): - # taken from https://github.com/pytest-dev/pytest/blob/33c7b05a/src/_pytest/_io/terminalwriter.py#L26 - width, _ = shutil.get_terminal_size(fallback=(80, 24)) - # The Windows get_terminal_size may be bogus, let's sanify a bit. - if width < 40: - width = 80 - return width - - def sep(self, stream, s, txt): + def sep(stream, s, txt): if hasattr(stream, 'sep'): stream.sep(s, txt) else: - fullwidth = self.get_width() - # taken from https://github.com/pytest-dev/pytest/blob/33c7b05a/src/_pytest/_io/terminalwriter.py#L126 - # The goal is to have the line be as long as possible - # under the condition that len(line) <= fullwidth. - if sys.platform == 'win32': - # If we print in the last column on windows we are on a - # new line but there is no way to verify/neutralize this - # (we may not know the exact line width). - # So let's be defensive to avoid empty lines in the output. - fullwidth -= 1 - N = max((fullwidth - len(txt) - 2) // (2 * len(s)), 1) - fill = s * N - line = f'{fill} {txt} {fill}' - # In some situations there is room for an extra sepchar at the right, - # in particular if we consider that with a sepchar like "_ " the - # trailing space is not important at the end of the line. - if len(line) + len(s.rstrip()) <= fullwidth: - line += s.rstrip() - # (end of terminalwriter borrowed code) - line += '\n\n' - stream.write(line) + sep_total = max((70 - 2 - len(txt)), 2) + sep_len = sep_total // 2 + sep_extra = sep_total % 2 + out = f'{s * sep_len} {txt} {s * (sep_len + sep_extra)}\n' + stream.write(out) @_ensure_topdir def summary(self, stream): @@ -153,21 +135,22 @@ class CovController: total = None if not self.cov_report: - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): return self.cov.report(show_missing=True, ignore_errors=True, file=_NullFile) # Output coverage section header. if len(self.node_descs) == 1: - self.sep(stream, '_', f'coverage: {"".join(self.node_descs)}') + self.sep(stream, '-', f"coverage: {''.join(self.node_descs)}") else: - self.sep(stream, '_', 'coverage') + self.sep(stream, '-', 'coverage') for node_desc in sorted(self.node_descs): self.sep(stream, ' ', f'{node_desc}') # Report on any failed workers. if self.failed_workers: - self.sep(stream, '_', 'coverage: failed workers') - stream.write('The following workers failed to return coverage data, ensure that pytest-cov is installed on these workers.\n') + self.sep(stream, '-', 'coverage: failed workers') + stream.write('The following workers failed to return coverage data, ' + 'ensure that pytest-cov is installed on these workers.\n') for node in self.failed_workers: stream.write(f'{node.gateway.id}\n') @@ -177,23 +160,22 @@ class CovController: 'show_missing': ('term-missing' in self.cov_report) or None, 'ignore_errors': True, 'file': stream, - 'precision': self.cov_precision, } skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values() options.update({'skip_covered': skip_covered or None}) - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): total = self.cov.report(**options) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: annotate_dir = self.cov_report['annotate'] - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): self.cov.annotate(ignore_errors=True, directory=annotate_dir) # We need to call Coverage.report here, just to get the total # Coverage.annotate don't return any total and we need it for --cov-fail-under. - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): total = self.cov.report(ignore_errors=True, file=_NullFile) if annotate_dir: stream.write(f'Coverage annotated source written to dir {annotate_dir}\n') @@ -203,44 +185,28 @@ class CovController: # Produce html report if wanted. if 'html' in self.cov_report: output = self.cov_report['html'] - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): total = self.cov.html_report(ignore_errors=True, directory=output) stream.write(f'Coverage HTML written to dir {self.cov.config.html_dir if output is None else output}\n') # Produce xml report if wanted. if 'xml' in self.cov_report: output = self.cov_report['xml'] - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): total = self.cov.xml_report(ignore_errors=True, outfile=output) stream.write(f'Coverage XML written to file {self.cov.config.xml_output if output is None else output}\n') # Produce json report if wanted if 'json' in self.cov_report: output = self.cov_report['json'] - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): total = self.cov.json_report(ignore_errors=True, outfile=output) stream.write('Coverage JSON written to file %s\n' % (self.cov.config.json_output if output is None else output)) - # Produce Markdown report if wanted. - if 'markdown' in self.cov_report: - output = self.cov_report['markdown'] - with _backup(self.cov, 'config'): - with Path(output).open('w') as output_file: - total = self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') - stream.write(f'Coverage Markdown information written to file {output}\n') - - # Produce Markdown report if wanted, appending to output file - if 'markdown-append' in self.cov_report: - output = self.cov_report['markdown-append'] - with _backup(self.cov, 'config'): - with Path(output).open('a') as output_file: - total = self.cov.report(ignore_errors=True, file=output_file, output_format='markdown') - stream.write(f'Coverage Markdown information appended to file {output}\n') - # Produce lcov report if wanted. if 'lcov' in self.cov_report: output = self.cov_report['lcov'] - with _backup(self.cov, 'config'): + with _backup(self.cov, "config"): self.cov.lcov_report(ignore_errors=True, outfile=output) # We need to call Coverage.report here, just to get the total @@ -257,39 +223,29 @@ class Central(CovController): @_ensure_topdir def start(self): - self.cov = coverage.Coverage( - source=self.cov_source, - branch=self.cov_branch, - data_suffix=True, - config_file=self.cov_config, - ) - if self.cov.config.dynamic_context == 'test_function': - message = ( - 'Detected dynamic_context=test_function in coverage configuration. ' - 'This is unnecessary as this plugin provides the more complete --cov-context option.' - ) - warnings.warn(CentralCovContextWarning(message), stacklevel=1) + cleanup() - self.combining_cov = coverage.Coverage( - source=self.cov_source, - branch=self.cov_branch, - data_suffix=f'{filename_suffix(True)}.combine', - data_file=os.path.abspath(self.cov.config.data_file), # noqa: PTH100 - config_file=self.cov_config, - ) + self.cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=True, + config_file=self.cov_config) + self.combining_cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=True, + data_file=os.path.abspath(self.cov.config.data_file), + config_file=self.cov_config) # Erase or load any previous coverage data and start coverage. if not self.cov_append: self.cov.erase() self.cov.start() - - super().start() + self.set_env() @_ensure_topdir def finish(self): """Stop coverage, save data to file and set the list of coverage objects to report on.""" - super().finish() + self.unset_env() self.cov.stop() self.cov.save() @@ -307,28 +263,26 @@ class DistMaster(CovController): @_ensure_topdir def start(self): - self.cov = coverage.Coverage( - source=self.cov_source, - branch=self.cov_branch, - data_suffix=True, - config_file=self.cov_config, - ) - if self.cov.config.dynamic_context == 'test_function': - raise DistCovError( - 'Detected dynamic_context=test_function in coverage configuration. ' - 'This is known to cause issues when using xdist, see: https://github.com/pytest-dev/pytest-cov/issues/604\n' - 'It is recommended to use --cov-context instead.' - ) + cleanup() + + # Ensure coverage rc file rsynced if appropriate. + if self.cov_config and os.path.exists(self.cov_config): + # rsyncdir is going away in pytest-xdist 4.0, already deprecated + if hasattr(self.config.option, 'rsyncdir'): + self.config.option.rsyncdir.append(self.cov_config) + + self.cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=True, + config_file=self.cov_config) self.cov._warn_no_data = False self.cov._warn_unimported_source = False self.cov._warn_preimported_source = False - self.combining_cov = coverage.Coverage( - source=self.cov_source, - branch=self.cov_branch, - data_suffix=f'{filename_suffix(True)}.combine', - data_file=os.path.abspath(self.cov.config.data_file), # noqa: PTH100 - config_file=self.cov_config, - ) + self.combining_cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=True, + data_file=os.path.abspath(self.cov.config.data_file), + config_file=self.cov_config) if not self.cov_append: self.cov.erase() self.cov.start() @@ -337,13 +291,11 @@ class DistMaster(CovController): def configure_node(self, node): """Workers need to know if they are collocated and what files have moved.""" - node.workerinput.update( - { - 'cov_master_host': socket.gethostname(), - 'cov_master_topdir': self.topdir, - 'cov_master_rsync_roots': [str(root) for root in node.nodemanager.roots], - } - ) + node.workerinput.update({ + 'cov_master_host': socket.gethostname(), + 'cov_master_topdir': self.topdir, + 'cov_master_rsync_roots': [str(root) for root in node.nodemanager.roots], + }) def testnodedown(self, node, error): """Collect data file name from worker.""" @@ -358,17 +310,27 @@ class DistMaster(CovController): # If worker is not collocated then we must save the data file # that it returns to us. if 'cov_worker_data' in output: - data_suffix = '%s.%s.%06d.%s' % ( # noqa: UP031 - socket.gethostname(), - os.getpid(), - random.randint(0, 999999), # noqa: S311 - output['cov_worker_node_id'], + data_suffix = '%s.%s.%06d.%s' % ( + socket.gethostname(), os.getpid(), + random.randint(0, 999999), + output['cov_worker_node_id'] ) - cov_data = CoverageData( - suffix=data_suffix, - ) - cov_data.loads(output['cov_worker_data']) + cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=data_suffix, + config_file=self.cov_config) + cov.start() + if coverage.version_info < (5, 0): + data = CoverageData() + data.read_fileobj(StringIO(output['cov_worker_data'])) + cov.data.update(data) + else: + data = CoverageData(no_disk=True) + data.loads(output['cov_worker_data']) + cov.get_data().update(data) + cov.stop() + cov.save() path = output['cov_worker_path'] self.cov.config.paths['source'].append(path) @@ -395,38 +357,34 @@ class DistWorker(CovController): @_ensure_topdir def start(self): - # Determine whether we are collocated with master. - self.is_collocated = ( - socket.gethostname() == self.config.workerinput['cov_master_host'] - and self.topdir == self.config.workerinput['cov_master_topdir'] - ) - # If we are not collocated, then rewrite master paths to worker paths. + cleanup() + + # Determine whether we are collocated with master. + self.is_collocated = (socket.gethostname() == self.config.workerinput['cov_master_host'] and + self.topdir == self.config.workerinput['cov_master_topdir']) + + # If we are not collocated then rewrite master paths to worker paths. if not self.is_collocated: master_topdir = self.config.workerinput['cov_master_topdir'] worker_topdir = self.topdir if self.cov_source is not None: - self.cov_source = [source.replace(master_topdir, worker_topdir) for source in self.cov_source] + self.cov_source = [source.replace(master_topdir, worker_topdir) + for source in self.cov_source] self.cov_config = self.cov_config.replace(master_topdir, worker_topdir) # Erase any previous data and start coverage. - self.cov = coverage.Coverage( - source=self.cov_source, - branch=self.cov_branch, - data_suffix=True, - config_file=self.cov_config, - ) - # Prevent workers from issuing module-not-measured type of warnings (expected for a workers to not have coverage in all the files). - self.cov._warn_unimported_source = False + self.cov = coverage.Coverage(source=self.cov_source, + branch=self.cov_branch, + data_suffix=True, + config_file=self.cov_config) self.cov.start() - - super().start() + self.set_env() @_ensure_topdir def finish(self): """Stop coverage and send relevant info back to the master.""" - super().finish() - + self.unset_env() self.cov.stop() if self.is_collocated: @@ -446,15 +404,20 @@ class DistWorker(CovController): # it on the master node. # Send all the data to the master over the channel. - data = self.cov.get_data().dumps() + if coverage.version_info < (5, 0): + buff = StringIO() + self.cov.data.write_fileobj(buff) + data = buff.getvalue() + else: + data = self.cov.get_data().dumps() - self.config.workeroutput.update( - { - 'cov_worker_path': self.topdir, - 'cov_worker_node_id': self.nodeid, - 'cov_worker_data': data, - } - ) + self.config.workeroutput.update({ + 'cov_worker_path': self.topdir, + 'cov_worker_node_id': self.nodeid, + 'cov_worker_data': data, + }) def summary(self, stream): """Only the master reports so do nothing.""" + + pass diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov/plugin.py b/Backend/venv/lib/python3.12/site-packages/pytest_cov/plugin.py index 553a9203..2a1544a6 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov/plugin.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_cov/plugin.py @@ -1,36 +1,47 @@ """Coverage plugin for pytest.""" - import argparse import os -import re import warnings -from io import StringIO -from pathlib import Path -from typing import TYPE_CHECKING +import coverage import pytest -from . import CovDisabledWarning -from . import CovReportWarning -from . import PytestCovWarning +from . import compat +from . import embed -if TYPE_CHECKING: - from .engine import CovController -COVERAGE_SQLITE_WARNING_RE = re.compile('unclosed database in = 6.3') + if len(values) == 1: return report_type, None @@ -39,7 +50,8 @@ def validate_report(arg): return report_type, report_modifier if report_type not in file_choices: - msg = f'output specifier not supported for: "{arg}" (choose from "{file_choices}")' + msg = 'output specifier not supported for: "{}" (choose from "{}")'.format(arg, + file_choices) raise argparse.ArgumentTypeError(msg) return values @@ -52,16 +64,17 @@ def validate_fail_under(num_str): try: value = float(num_str) except ValueError: - raise argparse.ArgumentTypeError('An integer or float value is required.') from None + raise argparse.ArgumentTypeError('An integer or float value is required.') if value > 100: - raise argparse.ArgumentTypeError( - 'Your desire for over-achievement is admirable but misplaced. The maximum value is 100. Perhaps write more integration tests?' - ) + raise argparse.ArgumentTypeError('Your desire for over-achievement is admirable but misplaced. ' + 'The maximum value is 100. Perhaps write more integration tests?') return value def validate_context(arg): - if arg != 'test': + if coverage.version_info <= (5, 0): + raise argparse.ArgumentTypeError('Contexts are only supported with coverage.py >= 5.x') + if arg != "test": raise argparse.ArgumentTypeError('The only supported value is "test".') return arg @@ -71,107 +84,46 @@ class StoreReport(argparse.Action): report_type, file = values namespace.cov_report[report_type] = file - # coverage.py doesn't set a default file for markdown output_format - if report_type in ['markdown', 'markdown-append'] and file is None: - namespace.cov_report[report_type] = 'coverage.md' - if all(x in namespace.cov_report for x in ['markdown', 'markdown-append']): - self._validate_markdown_dest_files(namespace.cov_report, parser) - - def _validate_markdown_dest_files(self, cov_report_options, parser): - markdown_file = cov_report_options['markdown'] - markdown_append_file = cov_report_options['markdown-append'] - if markdown_file == markdown_append_file: - error_message = f"markdown and markdown-append options cannot point to the same file: '{markdown_file}'." - error_message += ' Please redirect one of them using :DEST (e.g. --cov-report=markdown:dest_file.md)' - parser.error(error_message) - def pytest_addoption(parser): """Add options to control coverage.""" - group = parser.getgroup('cov', 'coverage reporting with distributed testing support') - group.addoption( - '--cov', - action='append', - default=[], - metavar='SOURCE', - nargs='?', - const=True, - dest='cov_source', - help='Path or package name to measure during execution (multi-allowed). ' - 'Use --cov= to not do any source filtering and record everything.', - ) - group.addoption( - '--cov-reset', - action='store_const', - const=[], - dest='cov_source', - help='Reset cov sources accumulated in options so far. ', - ) - group.addoption( - '--cov-report', - action=StoreReport, - default={}, - metavar='TYPE', - type=validate_report, - help='Type of report to generate: term, term-missing, ' - 'annotate, html, xml, json, markdown, markdown-append, lcov (multi-allowed). ' - 'term, term-missing may be followed by ":skip-covered". ' - 'annotate, html, xml, json, markdown, markdown-append and lcov may be followed by ":DEST" ' - 'where DEST specifies the output location. ' - 'Use --cov-report= to not generate any output.', - ) - group.addoption( - '--cov-config', - action='store', - default='.coveragerc', - metavar='PATH', - help='Config file for coverage. Default: .coveragerc', - ) - group.addoption( - '--no-cov-on-fail', - action='store_true', - default=False, - help='Do not report coverage if test run fails. Default: False', - ) - group.addoption( - '--no-cov', - action='store_true', - default=False, - help='Disable coverage report completely (useful for debuggers). Default: False', - ) - group.addoption( - '--cov-fail-under', - action='store', - metavar='MIN', - type=validate_fail_under, - help='Fail if the total coverage is less than MIN.', - ) - group.addoption( - '--cov-append', - action='store_true', - default=False, - help='Do not delete coverage but append to current. Default: False', - ) - group.addoption( - '--cov-branch', - action='store_true', - default=None, - help='Enable branch coverage.', - ) - group.addoption( - '--cov-precision', - type=int, - default=None, - help='Override the reporting precision.', - ) - group.addoption( - '--cov-context', - action='store', - metavar='CONTEXT', - type=validate_context, - help='Dynamic contexts to use. "test" for now.', - ) + group = parser.getgroup( + 'cov', 'coverage reporting with distributed testing support') + group.addoption('--cov', action='append', default=[], metavar='SOURCE', + nargs='?', const=True, dest='cov_source', + help='Path or package name to measure during execution (multi-allowed). ' + 'Use --cov= to not do any source filtering and record everything.') + group.addoption('--cov-reset', action='store_const', const=[], dest='cov_source', + help='Reset cov sources accumulated in options so far. ') + group.addoption('--cov-report', action=StoreReport, default={}, + metavar='TYPE', type=validate_report, + help='Type of report to generate: term, term-missing, ' + 'annotate, html, xml, json, lcov (multi-allowed). ' + 'term, term-missing may be followed by ":skip-covered". ' + 'annotate, html, xml, json and lcov may be followed by ":DEST" ' + 'where DEST specifies the output location. ' + 'Use --cov-report= to not generate any output.') + group.addoption('--cov-config', action='store', default='.coveragerc', + metavar='PATH', + help='Config file for coverage. Default: .coveragerc') + group.addoption('--no-cov-on-fail', action='store_true', default=False, + help='Do not report coverage if test run fails. ' + 'Default: False') + group.addoption('--no-cov', action='store_true', default=False, + help='Disable coverage report completely (useful for debuggers). ' + 'Default: False') + group.addoption('--cov-fail-under', action='store', metavar='MIN', + type=validate_fail_under, + help='Fail if the total coverage is less than MIN.') + group.addoption('--cov-append', action='store_true', default=False, + help='Do not delete coverage but append to current. ' + 'Default: False') + group.addoption('--cov-branch', action='store_true', default=None, + help='Enable branch coverage.') + group.addoption('--cov-context', action='store', metavar='CONTEXT', + type=validate_context, + help='Dynamic contexts to use. "test" for now.') def _prepare_cov_source(cov_source): @@ -209,7 +161,7 @@ class CovPlugin: distributed worker. """ - def __init__(self, options: argparse.Namespace, pluginmanager, start=True, no_cov_should_warn=False): + def __init__(self, options, pluginmanager, start=True, no_cov_should_warn=False): """Creates a coverage pytest plugin. We read the rc file that coverage uses to get the data file @@ -220,16 +172,17 @@ class CovPlugin: # Our implementation is unknown at this time. self.pid = None self.cov_controller = None - self.cov_report = StringIO() + self.cov_report = compat.StringIO() self.cov_total = None self.failed = False self._started = False self._start_path = None self._disabled = False self.options = options - self._wrote_heading = False - is_dist = getattr(options, 'numprocesses', False) or getattr(options, 'distload', False) or getattr(options, 'dist', 'no') != 'no' + is_dist = (getattr(options, 'numprocesses', False) or + getattr(options, 'distload', False) or + getattr(options, 'dist', 'no') != 'no') if getattr(options, 'no_cov', False): self._disabled = True return @@ -251,7 +204,8 @@ class CovPlugin: # worker is started in pytest hook - def start(self, controller_cls: type['CovController'], config=None, nodeid=None): + def start(self, controller_cls, config=None, nodeid=None): + if config is None: # fake config option for engine class Config: @@ -259,15 +213,21 @@ class CovPlugin: config = Config() - self.cov_controller = controller_cls(self.options, config, nodeid) + self.cov_controller = controller_cls( + self.options.cov_source, + self.options.cov_report, + self.options.cov_config, + self.options.cov_append, + self.options.cov_branch, + config, + nodeid + ) self.cov_controller.start() self._started = True - self._start_path = Path.cwd() + self._start_path = os.getcwd() cov_config = self.cov_controller.cov.config if self.options.cov_fail_under is None and hasattr(cov_config, 'fail_under'): self.options.cov_fail_under = cov_config.fail_under - if self.options.cov_precision is None: - self.options.cov_precision = getattr(cov_config, 'precision', 0) def _is_worker(self, session): return getattr(session.config, 'workerinput', None) is not None @@ -286,13 +246,15 @@ class CovPlugin: self.pid = os.getpid() if self._is_worker(session): - nodeid = session.config.workerinput.get('workerid', session.nodeid) + nodeid = ( + session.config.workerinput.get('workerid', getattr(session, 'nodeid')) + ) self.start(engine.DistWorker, session.config, nodeid) elif not self._started: self.start(engine.Central) if self.options.cov_context == 'test': - session.config.pluginmanager.register(TestContextPlugin(self.cov_controller), '_cov_contexts') + session.config.pluginmanager.register(TestContextPlugin(self.cov_controller.cov), '_cov_contexts') @pytest.hookimpl(optionalhook=True) def pytest_configure_node(self, node): @@ -316,81 +278,50 @@ class CovPlugin: needed = self.options.cov_report or self.options.cov_fail_under return needed and not (self.failed and self.options.no_cov_on_fail) + def _failed_cov_total(self): + cov_fail_under = self.options.cov_fail_under + return cov_fail_under is not None and self.cov_total < cov_fail_under + # we need to wrap pytest_runtestloop. by the time pytest_sessionfinish # runs, it's too late to set testsfailed - @pytest.hookimpl(wrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtestloop(self, session): + yield + if self._disabled: - return (yield) + return - # we add default warning configuration to prevent certain warnings to bubble up as errors due to rigid filterwarnings configuration - for _, message, category, _, _ in warnings.filters: - if category is ResourceWarning and message == COVERAGE_SQLITE_WARNING_RE: - break - else: - warnings.filterwarnings('default', 'unclosed database in +Home-page: https://github.com/pytest-dev/pytest-mock/ +Author: Bruno Oliveira +Author-email: nicoddemus@gmail.com License: MIT -Project-URL: Homepage, https://github.com/pytest-dev/pytest-mock/ Project-URL: Documentation, https://pytest-mock.readthedocs.io/en/latest/ Project-URL: Changelog, https://pytest-mock.readthedocs.io/en/latest/changelog.html Project-URL: Source, https://github.com/pytest-dev/pytest-mock/ Project-URL: Tracker, https://github.com/pytest-dev/pytest-mock/issues -Keywords: pytest,mock +Keywords: pytest mock +Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: Software Development :: Testing -Requires-Python: >=3.9 +Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE -Requires-Dist: pytest>=6.2.5 +Requires-Dist: pytest >=5.0 Provides-Extra: dev -Requires-Dist: pre-commit; extra == "dev" -Requires-Dist: pytest-asyncio; extra == "dev" -Requires-Dist: tox; extra == "dev" -Dynamic: license-file +Requires-Dist: pre-commit ; extra == 'dev' +Requires-Dist: tox ; extra == 'dev' +Requires-Dist: pytest-asyncio ; extra == 'dev' =========== pytest-mock diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/RECORD new file mode 100644 index 00000000..b4534729 --- /dev/null +++ b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/RECORD @@ -0,0 +1,17 @@ +pytest_mock-3.12.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +pytest_mock-3.12.0.dist-info/LICENSE,sha256=tc35IhAzpjNmUVvePoW1LB-y2yk8HZhXeExVY6VEGcM,1075 +pytest_mock-3.12.0.dist-info/METADATA,sha256=lGVoZEoQLkPPanrQ_koZnbnCqd2PrF3NVW4RfB4ddvY,3835 +pytest_mock-3.12.0.dist-info/RECORD,, +pytest_mock-3.12.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pytest_mock-3.12.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92 +pytest_mock-3.12.0.dist-info/entry_points.txt,sha256=kLSeM0ZCJRR1PqlUzALpxe52ui1Ag7ica4mrqmCX2aU,37 +pytest_mock-3.12.0.dist-info/top_level.txt,sha256=g25fQWB0jTCpAGM1n3t-0RlLvKJIM-t7-WwhbeYf0OU,12 +pytest_mock/__init__.py,sha256=zoj3CFm4AdW66AErIscSl6KngImqDa84-AEy2oY4S5E,703 +pytest_mock/__pycache__/__init__.cpython-312.pyc,, +pytest_mock/__pycache__/_util.cpython-312.pyc,, +pytest_mock/__pycache__/_version.cpython-312.pyc,, +pytest_mock/__pycache__/plugin.cpython-312.pyc,, +pytest_mock/_util.py,sha256=IPXeNO9fOOf7wth_CCkUA_O8yrBAQDwMuFB2eUNSWsA,930 +pytest_mock/_version.py,sha256=6iU97ZNjFyWo5y612HWPvq4RAAHGjmzNdNrNPdD1CeM,413 +pytest_mock/plugin.py,sha256=JWnJbA49pMjFwI4594aQCHlfcA0o3qJq3ZMgZDmd9-k,24336 +pytest_mock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/REQUESTED b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/WHEEL b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/WHEEL similarity index 64% rename from Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/WHEEL rename to Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/WHEEL index 12228d41..7e688737 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_cov-7.0.0.dist-info/WHEEL +++ b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/WHEEL @@ -1,4 +1,5 @@ Wheel-Version: 1.0 -Generator: hatchling 1.27.0 +Generator: bdist_wheel (0.41.2) Root-Is-Purelib: true Tag: py3-none-any + diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/entry_points.txt b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/entry_points.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/entry_points.txt rename to Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/entry_points.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/top_level.txt b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/top_level.txt similarity index 100% rename from Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/top_level.txt rename to Backend/venv/lib/python3.12/site-packages/pytest_mock-3.12.0.dist-info/top_level.txt diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/RECORD b/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/RECORD deleted file mode 100644 index 31328e18..00000000 --- a/Backend/venv/lib/python3.12/site-packages/pytest_mock-3.15.1.dist-info/RECORD +++ /dev/null @@ -1,17 +0,0 @@ -pytest_mock-3.15.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -pytest_mock-3.15.1.dist-info/METADATA,sha256=BNY0E-9dChaU-8kKqIpN-nyL_WG1KOD7S-5YkvIZ0IY,3899 -pytest_mock-3.15.1.dist-info/RECORD,, -pytest_mock-3.15.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -pytest_mock-3.15.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 -pytest_mock-3.15.1.dist-info/entry_points.txt,sha256=kLSeM0ZCJRR1PqlUzALpxe52ui1Ag7ica4mrqmCX2aU,37 -pytest_mock-3.15.1.dist-info/licenses/LICENSE,sha256=tc35IhAzpjNmUVvePoW1LB-y2yk8HZhXeExVY6VEGcM,1075 -pytest_mock-3.15.1.dist-info/top_level.txt,sha256=g25fQWB0jTCpAGM1n3t-0RlLvKJIM-t7-WwhbeYf0OU,12 -pytest_mock/__init__.py,sha256=oh9IvRFf4He9YjPHxttOSfpgIpOydOg2bs2tSVgkgYA,825 -pytest_mock/__pycache__/__init__.cpython-312.pyc,, -pytest_mock/__pycache__/_util.cpython-312.pyc,, -pytest_mock/__pycache__/_version.cpython-312.pyc,, -pytest_mock/__pycache__/plugin.cpython-312.pyc,, -pytest_mock/_util.py,sha256=IPXeNO9fOOf7wth_CCkUA_O8yrBAQDwMuFB2eUNSWsA,930 -pytest_mock/_version.py,sha256=303j3tKYk28DKGdLIfzneWfc-qSQv8QTVjNPpLWi-nM,706 -pytest_mock/plugin.py,sha256=NIWHMy0_1z836FSGOewtFMjYINsvPGtw5MC9kNIwJpA,25297 -pytest_mock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__init__.py b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__init__.py index 75fd27af..0afa47c0 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__init__.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__init__.py @@ -1,22 +1,18 @@ -from pytest_mock.plugin import AsyncMockType -from pytest_mock.plugin import MockerFixture -from pytest_mock.plugin import MockType -from pytest_mock.plugin import PytestMockWarning from pytest_mock.plugin import class_mocker from pytest_mock.plugin import mocker +from pytest_mock.plugin import MockerFixture from pytest_mock.plugin import module_mocker from pytest_mock.plugin import package_mocker from pytest_mock.plugin import pytest_addoption from pytest_mock.plugin import pytest_configure +from pytest_mock.plugin import PytestMockWarning from pytest_mock.plugin import session_mocker MockFixture = MockerFixture # backward-compatibility only (#204) __all__ = [ - "AsyncMockType", "MockerFixture", "MockFixture", - "MockType", "PytestMockWarning", "pytest_addoption", "pytest_configure", diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..da572908 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_util.cpython-312.pyc new file mode 100644 index 00000000..f5cb5f66 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_version.cpython-312.pyc new file mode 100644 index 00000000..0110a6d1 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/plugin.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/plugin.cpython-312.pyc new file mode 100644 index 00000000..59dff387 Binary files /dev/null and b/Backend/venv/lib/python3.12/site-packages/pytest_mock/__pycache__/plugin.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/_version.py b/Backend/venv/lib/python3.12/site-packages/pytest_mock/_version.py index 7b769ace..9e1a8995 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_mock/_version.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_mock/_version.py @@ -1,34 +1,16 @@ -# file generated by setuptools-scm +# file generated by setuptools_scm # don't change, don't track in version control - -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", - "__commit_id__", - "commit_id", -] - TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Tuple - from typing import Union - + from typing import Tuple, Union VERSION_TUPLE = Tuple[Union[int, str], ...] - COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object - COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -commit_id: COMMIT_ID -__commit_id__: COMMIT_ID -__version__ = version = '3.15.1' -__version_tuple__ = version_tuple = (3, 15, 1) - -__commit_id__ = commit_id = None +__version__ = version = '3.12.0' +__version_tuple__ = version_tuple = (3, 12, 0) diff --git a/Backend/venv/lib/python3.12/site-packages/pytest_mock/plugin.py b/Backend/venv/lib/python3.12/site-packages/pytest_mock/plugin.py index ef996121..673f98bf 100644 --- a/Backend/venv/lib/python3.12/site-packages/pytest_mock/plugin.py +++ b/Backend/venv/lib/python3.12/site-packages/pytest_mock/plugin.py @@ -1,22 +1,24 @@ +import asyncio import builtins import functools import inspect -import itertools +import sys import unittest.mock import warnings -from collections.abc import Generator -from collections.abc import Iterable -from collections.abc import Iterator -from collections.abc import Mapping -from dataclasses import dataclass -from dataclasses import field from typing import Any from typing import Callable +from typing import cast +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Mapping from typing import Optional +from typing import overload +from typing import Tuple +from typing import Type from typing import TypeVar from typing import Union -from typing import cast -from typing import overload import pytest @@ -25,58 +27,22 @@ from ._util import parse_ini_boolean _T = TypeVar("_T") -AsyncMockType = unittest.mock.AsyncMock -MockType = Union[ - unittest.mock.MagicMock, - unittest.mock.AsyncMock, - unittest.mock.NonCallableMagicMock, -] +if sys.version_info >= (3, 8): + AsyncMockType = unittest.mock.AsyncMock + MockType = Union[ + unittest.mock.MagicMock, + unittest.mock.AsyncMock, + unittest.mock.NonCallableMagicMock, + ] +else: + AsyncMockType = Any + MockType = Union[unittest.mock.MagicMock, unittest.mock.NonCallableMagicMock] class PytestMockWarning(UserWarning): """Base class for all warnings emitted by pytest-mock.""" -@dataclass -class MockCacheItem: - mock: MockType - patch: Optional[Any] = None - - -@dataclass -class MockCache: - """ - Cache MagicMock and Patcher instances so we can undo them later. - """ - - cache: list[MockCacheItem] = field(default_factory=list) - - def _find(self, mock: MockType) -> MockCacheItem: - for mock_item in self.cache: - if mock_item.mock is mock: - return mock_item - raise ValueError("This mock object is not registered") - - def add(self, mock: MockType, **kwargs: Any) -> MockCacheItem: - self.cache.append(MockCacheItem(mock=mock, **kwargs)) - return self.cache[-1] - - def remove(self, mock: MockType) -> None: - mock_item = self._find(mock) - if mock_item.patch: - mock_item.patch.stop() - self.cache.remove(mock_item) - - def clear(self) -> None: - for mock_item in reversed(self.cache): - if mock_item.patch is not None: - mock_item.patch.stop() - self.cache.clear() - - def __iter__(self) -> Iterator[MockCacheItem]: - return iter(self.cache) - - class MockerFixture: """ Fixture that provides the same interface to functions in the mock module, @@ -84,9 +50,11 @@ class MockerFixture: """ def __init__(self, config: Any) -> None: - self._mock_cache: MockCache = MockCache() + self._patches_and_mocks: List[Tuple[Any, unittest.mock.MagicMock]] = [] self.mock_module = mock_module = get_mock_module(config) - self.patch = self._Patcher(self._mock_cache, mock_module) # type: MockerFixture._Patcher + self.patch = self._Patcher( + self._patches_and_mocks, mock_module + ) # type: MockerFixture._Patcher # aliases for convenience self.Mock = mock_module.Mock self.MagicMock = mock_module.MagicMock @@ -109,7 +77,7 @@ class MockerFixture: m: MockType = self.mock_module.create_autospec( spec, spec_set, instance, **kwargs ) - self._mock_cache.add(m) + self._patches_and_mocks.append((None, m)) return m def resetall( @@ -121,55 +89,62 @@ class MockerFixture: :param bool return_value: Reset the return_value of mocks. :param bool side_effect: Reset the side_effect of mocks. """ - supports_reset_mock_with_args: tuple[type[Any], ...] + supports_reset_mock_with_args: Tuple[Type[Any], ...] if hasattr(self, "AsyncMock"): supports_reset_mock_with_args = (self.Mock, self.AsyncMock) else: supports_reset_mock_with_args = (self.Mock,) - for mock_item in self._mock_cache: + for p, m in self._patches_and_mocks: # See issue #237. - if not hasattr(mock_item.mock, "reset_mock"): + if not hasattr(m, "reset_mock"): continue - # NOTE: The mock may be a dictionary - if hasattr(mock_item.mock, "spy_return_list"): - mock_item.mock.spy_return_list = [] - if hasattr(mock_item.mock, "spy_return_iter"): - mock_item.mock.spy_return_iter = None - if isinstance(mock_item.mock, supports_reset_mock_with_args): - mock_item.mock.reset_mock( - return_value=return_value, side_effect=side_effect - ) + if isinstance(m, supports_reset_mock_with_args): + m.reset_mock(return_value=return_value, side_effect=side_effect) else: - mock_item.mock.reset_mock() + m.reset_mock() def stopall(self) -> None: """ Stop all patchers started by this fixture. Can be safely called multiple times. """ - self._mock_cache.clear() + for p, m in reversed(self._patches_and_mocks): + if p is not None: + p.stop() + self._patches_and_mocks.clear() def stop(self, mock: unittest.mock.MagicMock) -> None: """ Stops a previous patch or spy call by passing the ``MagicMock`` object returned by it. """ - self._mock_cache.remove(mock) + for index, (p, m) in enumerate(self._patches_and_mocks): + if mock is m: + p.stop() + del self._patches_and_mocks[index] + break + else: + raise ValueError("This mock object is not registered") - def spy( - self, obj: object, name: str, duplicate_iterators: bool = False - ) -> MockType: + def spy(self, obj: object, name: str) -> MockType: """ Create a spy of method. It will run method normally, but it is now possible to use `mock` call features with it, like call count. :param obj: An object. :param name: A method in object. - :param duplicate_iterators: Whether to keep a copy of the returned iterator in `spy_return_iter`. :return: Spy object. """ method = getattr(obj, name) + if inspect.isclass(obj) and isinstance( + inspect.getattr_static(obj, name), (classmethod, staticmethod) + ): + # Can't use autospec classmethod or staticmethod objects before 3.7 + # see: https://bugs.python.org/issue23078 + autospec = False + else: + autospec = inspect.ismethod(method) or inspect.isfunction(method) def wrapper(*args, **kwargs): spy_obj.spy_return = None @@ -180,14 +155,7 @@ class MockerFixture: spy_obj.spy_exception = e raise else: - if duplicate_iterators and isinstance(r, Iterator): - r, duplicated_iterator = itertools.tee(r, 2) - spy_obj.spy_return_iter = duplicated_iterator - else: - spy_obj.spy_return_iter = None - spy_obj.spy_return = r - spy_obj.spy_return_list.append(r) return r async def async_wrapper(*args, **kwargs): @@ -200,20 +168,15 @@ class MockerFixture: raise else: spy_obj.spy_return = r - spy_obj.spy_return_list.append(r) return r - if inspect.iscoroutinefunction(method): + if asyncio.iscoroutinefunction(method): wrapped = functools.update_wrapper(async_wrapper, method) else: wrapped = functools.update_wrapper(wrapper, method) - autospec = inspect.ismethod(method) or inspect.isfunction(method) - spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec) spy_obj.spy_return = None - spy_obj.spy_return_iter = None - spy_obj.spy_return_list = [] spy_obj.spy_exception = None return spy_obj @@ -251,8 +214,8 @@ class MockerFixture: DEFAULT = object() - def __init__(self, mock_cache, mock_module): - self.__mock_cache = mock_cache + def __init__(self, patches_and_mocks, mock_module): + self.__patches_and_mocks = patches_and_mocks self.mock_module = mock_module def _start_patch( @@ -264,18 +227,22 @@ class MockerFixture: """ p = mock_func(*args, **kwargs) mocked: MockType = p.start() - self.__mock_cache.add(mock=mocked, patch=p) + self.__patches_and_mocks.append((p, mocked)) if hasattr(mocked, "reset_mock"): # check if `mocked` is actually a mock object, as depending on autospec or target # parameters `mocked` can be anything if hasattr(mocked, "__enter__") and warn_on_mock_enter: + if sys.version_info >= (3, 8): + depth = 5 + else: + depth = 4 mocked.__enter__.side_effect = lambda: warnings.warn( "Mocks returned by pytest-mock do not need to be used as context managers. " "The mocker fixture automatically undoes mocking at the end of a test. " "This warning can be ignored if it was triggered by mocking a context manager. " - "https://pytest-mock.readthedocs.io/en/latest/usage.html#usage-as-context-manager", + "https://pytest-mock.readthedocs.io/en/latest/remarks.html#usage-as-context-manager", PytestMockWarning, - stacklevel=5, + stacklevel=depth, ) return mocked @@ -289,7 +256,7 @@ class MockerFixture: spec_set: Optional[object] = None, autospec: Optional[object] = None, new_callable: object = None, - **kwargs: Any, + **kwargs: Any ) -> MockType: """API to mock.patch.object""" if new is self.DEFAULT: @@ -305,7 +272,7 @@ class MockerFixture: spec_set=spec_set, autospec=autospec, new_callable=new_callable, - **kwargs, + **kwargs ) def context_manager( @@ -318,7 +285,7 @@ class MockerFixture: spec_set: Optional[builtins.object] = None, autospec: Optional[builtins.object] = None, new_callable: builtins.object = None, - **kwargs: Any, + **kwargs: Any ) -> MockType: """This is equivalent to mock.patch.object except that the returned mock does not issue a warning when used as a context manager.""" @@ -335,7 +302,7 @@ class MockerFixture: spec_set=spec_set, autospec=autospec, new_callable=new_callable, - **kwargs, + **kwargs ) def multiple( @@ -346,8 +313,8 @@ class MockerFixture: spec_set: Optional[builtins.object] = None, autospec: Optional[builtins.object] = None, new_callable: Optional[builtins.object] = None, - **kwargs: Any, - ) -> dict[str, MockType]: + **kwargs: Any + ) -> Dict[str, MockType]: """API to mock.patch.multiple""" return self._start_patch( self.mock_module.patch.multiple, @@ -358,15 +325,15 @@ class MockerFixture: spec_set=spec_set, autospec=autospec, new_callable=new_callable, - **kwargs, + **kwargs ) def dict( self, in_dict: Union[Mapping[Any, Any], str], - values: Union[Mapping[Any, Any], Iterable[tuple[Any, Any]]] = (), + values: Union[Mapping[Any, Any], Iterable[Tuple[Any, Any]]] = (), clear: bool = False, - **kwargs: Any, + **kwargs: Any ) -> Any: """API to mock.patch.dict""" return self._start_patch( @@ -375,7 +342,7 @@ class MockerFixture: in_dict, values=values, clear=clear, - **kwargs, + **kwargs ) @overload @@ -388,8 +355,9 @@ class MockerFixture: spec_set: Optional[builtins.object] = ..., autospec: Optional[builtins.object] = ..., new_callable: None = ..., - **kwargs: Any, - ) -> MockType: ... + **kwargs: Any + ) -> MockType: + ... @overload def __call__( @@ -401,8 +369,9 @@ class MockerFixture: spec_set: Optional[builtins.object] = ..., autospec: Optional[builtins.object] = ..., new_callable: None = ..., - **kwargs: Any, - ) -> _T: ... + **kwargs: Any + ) -> _T: + ... @overload def __call__( @@ -414,8 +383,9 @@ class MockerFixture: spec_set: Optional[builtins.object], autospec: Optional[builtins.object], new_callable: Callable[[], _T], - **kwargs: Any, - ) -> _T: ... + **kwargs: Any + ) -> _T: + ... @overload def __call__( @@ -428,8 +398,9 @@ class MockerFixture: autospec: Optional[builtins.object] = ..., *, new_callable: Callable[[], _T], - **kwargs: Any, - ) -> _T: ... + **kwargs: Any + ) -> _T: + ... def __call__( self, @@ -440,7 +411,7 @@ class MockerFixture: spec_set: Optional[builtins.object] = None, autospec: Optional[builtins.object] = None, new_callable: Optional[Callable[[], Any]] = None, - **kwargs: Any, + **kwargs: Any ) -> Any: """API to mock.patch""" if new is self.DEFAULT: @@ -455,7 +426,7 @@ class MockerFixture: spec_set=spec_set, autospec=autospec, new_callable=new_callable, - **kwargs, + **kwargs ) @@ -476,8 +447,8 @@ package_mocker = pytest.fixture(scope="package")(_mocker) session_mocker = pytest.fixture(scope="session")(_mocker) -_mock_module_patches: list[Any] = [] -_mock_module_originals: dict[str, Any] = {} +_mock_module_patches = [] # type: List[Any] +_mock_module_originals = {} # type: Dict[str, Any] def assert_wrapper( diff --git a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc index 9647ae4e..8f633161 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc index 01321fca..6d99fb38 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc index 4fa2f233..c190b07b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/multipart.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/multipart.cpython-312.pyc index a1a82237..3b399921 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/multipart.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/python_multipart/__pycache__/multipart.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc index 83a218b6..def88d8f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/LUT.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc index c55d3a3a..4a7bcfac 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc index 909ffaaf..29ef3bdc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc index 6a531cb3..ce289d8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/constants.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc index 58ac6c54..6c75c0d9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc index 1cac8e7d..28ff185e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc index 7f871d8b..b937ac74 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc index 3e3c199b..d60b8ecc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc index 7cbcb6ed..8013e2ee 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/compat/__pycache__/pil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc index 890ab716..fbf7afc8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc index 8a97b02b..122d54ac 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc index 10d95aa2..177f7cb4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/__pycache__/pure.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc index 2cb03e5d..c7505f9b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc index 97b64867..5214ca28 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc index 9cb98b87..3f7247d9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc index 9f2ca9dc..c6517cfa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/qrcode/image/styles/moduledrawers/__pycache__/pil.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__init__.cpython-312.pyc index f203940a..7dce98c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__version__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__version__.cpython-312.pyc index f70e7098..a2b6c444 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__version__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/__version__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc index 3c53496f..c5872fbc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/adapters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/adapters.cpython-312.pyc index b2e03b3c..5f642d79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/adapters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/adapters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/api.cpython-312.pyc index d55b49e8..4efb3799 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/auth.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/auth.cpython-312.pyc index 6f1412fa..737033ed 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/auth.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/auth.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/certs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/certs.cpython-312.pyc index 0909afb6..f9b2e738 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/certs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/certs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/compat.cpython-312.pyc index da2c2ad2..67630d7f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/cookies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/cookies.cpython-312.pyc index 59df7285..0c1f16a3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/cookies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/cookies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/exceptions.cpython-312.pyc index 44fc5e35..5e005fab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/hooks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/hooks.cpython-312.pyc index 0a8b9cbf..9be381c7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/hooks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/hooks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/models.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/models.cpython-312.pyc index 03fbd802..6ad16bab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/models.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/models.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/packages.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/packages.cpython-312.pyc index 41fca48e..7ce9d663 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/packages.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/packages.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/sessions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/sessions.cpython-312.pyc index f8335783..4674a178 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/sessions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/sessions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/status_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/status_codes.cpython-312.pyc index 2ff50836..cf66a697 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/status_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/status_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/structures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/structures.cpython-312.pyc index 092d11cc..e8286e3c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/structures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/structures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/utils.cpython-312.pyc index dd06de27..efc0b4a7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/requests/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/__init__.cpython-312.pyc index e18db011..9534ae9e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_cell_widths.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_cell_widths.cpython-312.pyc index 21ee4df0..e2602d62 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_cell_widths.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_cell_widths.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_codes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_codes.cpython-312.pyc index db9cbb86..b94f7290 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_codes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_codes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_replace.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_replace.cpython-312.pyc index de2a2998..418618e8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_replace.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_emoji_replace.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_export_format.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_export_format.cpython-312.pyc index 30309675..77885162 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_export_format.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_export_format.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_extension.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_extension.cpython-312.pyc index f7a5c05b..b143d8ba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_extension.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_extension.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_fileno.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_fileno.cpython-312.pyc index 8349fa52..64f64be3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_fileno.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_fileno.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_log_render.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_log_render.cpython-312.pyc index 80ecf314..f3fb4237 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_log_render.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_log_render.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_loop.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_loop.cpython-312.pyc index ef114d08..d3ea6893 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_loop.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_loop.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_null_file.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_null_file.cpython-312.pyc index fe9f1720..63d60650 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_null_file.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_null_file.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_palettes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_palettes.cpython-312.pyc index 2ce84ee3..c8fdb367 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_palettes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_palettes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_pick.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_pick.cpython-312.pyc index e5929c51..10df9da0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_pick.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_pick.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_ratio.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_ratio.cpython-312.pyc index a6913321..27dbf51d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_ratio.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_ratio.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_spinners.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_spinners.cpython-312.pyc index 692deeb0..d99944ec 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_spinners.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_spinners.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_wrap.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_wrap.cpython-312.pyc index 03518c20..f4c5bac1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_wrap.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/_wrap.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/abc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/abc.cpython-312.pyc index 861bd9ae..0ad4d18e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/abc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/abc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/align.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/align.cpython-312.pyc index f91ee70c..883f3fef 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/align.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/align.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/ansi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/ansi.cpython-312.pyc index 927cc535..cb27fcc3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/ansi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/ansi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/box.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/box.cpython-312.pyc index 701308d8..5ade5d1f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/box.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/box.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/cells.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/cells.cpython-312.pyc index 8b0704b1..b5acd136 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/cells.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/cells.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color.cpython-312.pyc index ac34c92b..20c31fa6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color_triplet.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color_triplet.cpython-312.pyc index cdacf700..1d5a0f07 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color_triplet.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/color_triplet.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/console.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/console.cpython-312.pyc index a16e7b28..bdbfb51a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/console.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/console.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/constrain.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/constrain.cpython-312.pyc index 0db98c5c..5da91ed2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/constrain.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/constrain.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/containers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/containers.cpython-312.pyc index 76e7cfc0..1e3a5fd0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/containers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/containers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/control.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/control.cpython-312.pyc index 3620b751..09f63349 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/control.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/control.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/default_styles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/default_styles.cpython-312.pyc index 82f9791e..400bf3f5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/default_styles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/default_styles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/emoji.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/emoji.cpython-312.pyc index 6b597b60..95f98c09 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/emoji.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/emoji.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/errors.cpython-312.pyc index 9c0ac576..50fbbf86 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/file_proxy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/file_proxy.cpython-312.pyc index f745bd52..6654c5ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/file_proxy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/file_proxy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/filesize.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/filesize.cpython-312.pyc index 3b081546..4b342b41 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/filesize.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/filesize.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/highlighter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/highlighter.cpython-312.pyc index aeece26f..8be53abc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/highlighter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/highlighter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/jupyter.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/jupyter.cpython-312.pyc index c0fdddfa..f9cb5adc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/jupyter.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/jupyter.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live.cpython-312.pyc index d186368e..b6f03592 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live_render.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live_render.cpython-312.pyc index 3261c54f..2741f17e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live_render.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/live_render.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/markup.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/markup.cpython-312.pyc index 7519a175..3014a17f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/markup.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/markup.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/measure.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/measure.cpython-312.pyc index ebd1bf1a..9ac8d49a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/measure.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/measure.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/padding.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/padding.cpython-312.pyc index 5ea4cd99..f52d0ba5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/padding.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/padding.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pager.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pager.cpython-312.pyc index 52f84841..a8505bcc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pager.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pager.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/palette.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/palette.cpython-312.pyc index 4860d7f7..ccfc69ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/palette.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/palette.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/panel.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/panel.cpython-312.pyc index e9ce2fcf..8e7657d0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/panel.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/panel.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pretty.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pretty.cpython-312.pyc index bf03c08a..1d1e6232 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pretty.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/pretty.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress.cpython-312.pyc index 07733cde..84e360de 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress_bar.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress_bar.cpython-312.pyc index e208d5dd..f414a3b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress_bar.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/progress_bar.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/protocol.cpython-312.pyc index 39669e9e..8d906574 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/region.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/region.cpython-312.pyc index ebf474e1..171bca4d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/region.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/region.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/repr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/repr.cpython-312.pyc index 395685a0..f9aa1f92 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/repr.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/repr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/scope.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/scope.cpython-312.pyc index bfdb198e..12389fe2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/scope.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/scope.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/screen.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/screen.cpython-312.pyc index 7d7cfc66..83a78860 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/screen.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/screen.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/segment.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/segment.cpython-312.pyc index 43767c85..dabaee81 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/segment.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/segment.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/spinner.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/spinner.cpython-312.pyc index dfb8de88..8eb98713 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/spinner.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/spinner.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/style.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/style.cpython-312.pyc index a894a139..b8e6f15b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/style.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/style.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/styled.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/styled.cpython-312.pyc index 40998e80..0a08e547 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/styled.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/styled.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/syntax.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/syntax.cpython-312.pyc index c9687ad8..8f82cd7f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/syntax.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/syntax.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/table.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/table.cpython-312.pyc index faa9441a..4d95c2eb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/table.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/table.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/terminal_theme.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/terminal_theme.cpython-312.pyc index 5841eb66..a1ba03fc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/terminal_theme.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/terminal_theme.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/text.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/text.cpython-312.pyc index 84440591..e855055d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/text.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/text.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/theme.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/theme.cpython-312.pyc index 13a661be..40a96ffa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/theme.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/theme.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/themes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/themes.cpython-312.pyc index 773fc3dd..d6a84a93 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/themes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/rich/__pycache__/themes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/__init__.cpython-312.pyc index bc13765e..95c97dd2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/errors.cpython-312.pyc index f50c1631..34039e01 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/extension.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/extension.cpython-312.pyc index 651ddcb9..06408bb5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/extension.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/extension.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/util.cpython-312.pyc index 9006d7e6..5d791600 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/wrappers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/wrappers.cpython-312.pyc index bb9bd85c..f77497f8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/wrappers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/slowapi/__pycache__/wrappers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/__init__.cpython-312.pyc index 7313a9f6..e6ff8580 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_impl.cpython-312.pyc index bd2a4773..69589360 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_version.cpython-312.pyc index 7c26fce2..644411f7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sniffio/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/__init__.cpython-312.pyc index 25c8f518..de1b1906 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/events.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/events.cpython-312.pyc index 9c205c78..0d8edfed 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/events.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/events.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/exc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/exc.cpython-312.pyc index 80f0719a..42db41a5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/exc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/exc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/inspection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/inspection.cpython-312.pyc index 35f85a3a..6d87f55f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/inspection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/inspection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/log.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/log.cpython-312.pyc index ee8b8708..7b8e120c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/log.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/log.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/schema.cpython-312.pyc index c0e0daff..2ae2a7c1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/types.cpython-312.pyc index d7b63867..ebb07117 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc index fc86b14d..e4d40992 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc index 1b5c088a..392be9fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc index a41a7de1..60f3df3a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc index aa022c52..7c5df1c3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc index f2b25dbf..407f3391 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc index e4f1bf29..0b0ec81c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc index a0ad5680..ffa0c566 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc index 826a7834..f4712113 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc index 59dd7383..1e6ef809 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc index 55a421f1..aa83ee8c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc index 6643f6b7..36d7614e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc index c3410ad0..0b14c21c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc index 1cddc07d..427131d5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc index c3b552cd..012590e0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc index 964cb2a2..6a71a86c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc index 6d23c1cb..29713afb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc index 9a948292..2c7ac269 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc index 71641cc1..9e2200df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc index 87fbe3f3..a28cc921 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc index 688f8caa..7c4453a2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc index 06618b9f..7fdd661f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc index a9982b2a..81e2192d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc index 21727b7d..ebf56429 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc index f99bf7f3..849e8e5d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc index abe336da..88bb797c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc index d9f850ec..55be58b6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc index a20e8ccf..5e38c0d6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc index 33cbb614..8e36b01b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc index ec06bd93..e2130e0e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc index c1cdec7b..6d3f741a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc index 4e4040d3..0af6eb1e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc index d58ccdf8..cbd59a19 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc index 12f75f8f..68b13613 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc index 11eadd37..fc2e9f81 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc index eae8d3aa..0bbd1477 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc index 8e22fc2c..7c3af354 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc index c1316fe8..39d4f5bc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc index 4a14ae8c..3b63e967 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc index 3920e119..b80886fa 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc index f35e1613..0a146a61 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc index 5f0b7dd8..3021b5ad 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc index 043bc1ce..0407ee79 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/base.cpython-312.pyc index 22cbb639..7c37ff5b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc index e20559c6..8e73b286 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/create.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/create.cpython-312.pyc index 78dfa066..15ef4e9a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/create.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/create.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc index e6d82271..6adb06b5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/default.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/default.cpython-312.pyc index ac20e3fb..727dfee1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/default.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/default.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/events.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/events.cpython-312.pyc index a97e9d5d..ed0568ae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/events.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/events.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc index 675d5d86..59ed4bb2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-312.pyc index 43c96c64..b5c5aa85 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-312.pyc index f9a62ec4..a25bdae6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc index c1767305..252c7d3b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/result.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/result.cpython-312.pyc index 79ceece8..2da630f7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/result.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/result.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/row.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/row.cpython-312.pyc index d225e8d7..841f2a0b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/row.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/row.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc index 1c765f86..fb8b23d8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/url.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/url.cpython-312.pyc index 4cab5bdc..0d2df971 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/url.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/url.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/util.cpython-312.pyc index 52e34aaa..833bbef8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-312.pyc index d0288b16..cb5abf57 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/api.cpython-312.pyc index 33393d6e..5563d6b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/attr.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/attr.cpython-312.pyc index 33b9ecd4..bf9e0ffb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/attr.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/attr.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/base.cpython-312.pyc index 0ae51106..38f95f90 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-312.pyc index 7b8adcd4..b7c59842 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/registry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/registry.cpython-312.pyc index 2dc1ffd4..38edf611 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/registry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/registry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc index d51006bc..0234892f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc index 01aa3669..754f4138 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc index 915f1957..1c1132e9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc index f56242bf..55a861ca 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-312.pyc index 7a9321ac..76f206ff 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/engine.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/engine.cpython-312.pyc index 4fcb52bc..4cada516 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/engine.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/engine.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc index 58533128..c38f0a66 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc index af0b1eda..e8dd937d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc index d9d60150..0db13f60 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc index 7a82000d..9127677f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/base.cpython-312.pyc index 3764452e..7b400789 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc index b85c6cb5..00b0fdf0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc index 00b3c27d..d9c40ddf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-312.pyc index 2e3da5b0..8e0f9495 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/context.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/context.cpython-312.pyc index 668df89c..93060b9b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/context.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/context.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc index c008a423..7ed706bc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc index 2fa48d86..9af6f602 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc index 05c9a0b5..095bee7c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc index 2cc8c85f..73dd53c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc index 43ce462c..bbbc5974 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc index 7878e83b..d5ff6acf 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/events.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/events.cpython-312.pyc index b84840ad..f39b38af 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/events.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/events.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-312.pyc index 9e008261..0d0a27d9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-312.pyc index 48eac4c6..dafe77df 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc index 4290fa7e..c76b3260 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc index b08156dd..6e4f659d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-312.pyc index fd57fb5e..a620f3f3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc index fb04eb19..257f05cb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc index 958476e0..0abe6e77 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc index 57ff2e85..624ce433 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc index fb554d6f..e918a87a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-312.pyc index 6121533d..6ed03e72 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/query.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/query.cpython-312.pyc index e362b91a..092fa691 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/query.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/query.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc index 5af4483a..f8e12e73 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc index ad5b9b52..befaf00c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/session.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/session.cpython-312.pyc index fdf60702..88b76d96 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/session.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/session.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state.cpython-312.pyc index ba8c4ccf..1bffc846 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc index b55eeb09..e2305bc3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc index 9d7d7631..c6dc4d8f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc index 3b2130a7..07139a34 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-312.pyc index e3dfad1d..188aaa49 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc index feea3804..c7494746 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/util.cpython-312.pyc index 939a13c3..f3d449fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc index 36a5e20d..060a7bb6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc index 1de55a9f..23dad575 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/base.cpython-312.pyc index e61ba4b4..634b2fd4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/events.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/events.cpython-312.pyc index e1951a5f..17496b82 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/events.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/events.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-312.pyc index 509a41b0..962ed5e7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc index 1ee5ae5c..030da055 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc index eb327c67..9c93eb5d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc index 97dd5cdb..26d3a66c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc index bf1b3f29..b21fcc66 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc index a421a715..8d7e3d41 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc index 6666fe95..54830783 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc index ababa497..31fcbb0b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/base.cpython-312.pyc index 81050f3b..24186a9e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc index fe47607a..5419473b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc index 8f1e3114..f8b72b7b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc index a2d716a1..eefe869a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-312.pyc index e3a89ff1..2c04e28b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc index e334c0c4..de2be0d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc index 608341dd..b4d8c6f5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-312.pyc index 066f2c32..fb3c76dd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-312.pyc index d0048a00..aa551c64 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/events.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/events.cpython-312.pyc index 1c0d0593..f14bb6b9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/events.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/events.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-312.pyc index b37ac5f4..27a3b718 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-312.pyc index dd0b746c..9131d012 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc index 73b5a12c..59680e6e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-312.pyc index 9b476e0d..b0ad5841 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-312.pyc index 0888880d..9644b4bd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-312.pyc index 62639cac..2ddfba49 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-312.pyc index e1f02b3f..3eea8e0e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc index 0d84d0ab..f3cd62a5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc index 635fa779..69a34a4f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc index 74113b67..e290247b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc index d5031bf2..5ca52114 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/util.cpython-312.pyc index 63b37d1f..df4fc57c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc index dbf1f8d6..195bb2a6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-312.pyc index 9586dbf6..78230491 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-312.pyc index 629e708a..50980b6f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc index a2bdb2f0..75a1e719 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc index 873843d9..c8a30bf2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/compat.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/compat.cpython-312.pyc index db908b15..c8488898 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/compat.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/compat.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc index 2352c455..8999fff1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc index 887081bc..206a76ab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc index 00b62a3b..9ac87795 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc index ea90949f..674975c8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/queue.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/queue.cpython-312.pyc index a2ce8d11..d3ca39ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/queue.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/queue.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/topological.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/topological.cpython-312.pyc index 9bc44435..b6395859 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/topological.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/topological.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/typing.cpython-312.pyc index 2c63bef4..e6bdfa8a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/__init__.cpython-312.pyc index 5d961fc5..4e18b899 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc index c53bc1de..84db766f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_utils.cpython-312.pyc index d2481b47..46cf2373 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/_utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/applications.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/applications.cpython-312.pyc index a59bf636..151d8d82 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/applications.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/applications.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/background.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/background.cpython-312.pyc index 97991e2e..658a15fd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/background.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/background.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc index 3e4e4e8d..248e89e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/config.cpython-312.pyc index 3d550984..bbe72d04 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/convertors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/convertors.cpython-312.pyc index dcac37a6..881f9d99 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/convertors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/convertors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc index 14caa25a..670a3b58 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc index d6532a28..6801f65d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc index 8f8cc3a4..1e2a6a2a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/requests.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/requests.cpython-312.pyc index ecbcd3b2..f4e956a8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/requests.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/requests.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/responses.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/responses.cpython-312.pyc index 131a2a86..68c57158 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/responses.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/responses.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/routing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/routing.cpython-312.pyc index 945aab8d..469296a9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/routing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/routing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc index 4ce846f5..f58eec36 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/status.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/status.cpython-312.pyc index 99938c36..c31f56b1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/status.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/status.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/types.cpython-312.pyc index 4f805d51..0a2890f5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/websockets.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/websockets.cpython-312.pyc index a0666625..f05c9261 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/websockets.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/__pycache__/websockets.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc index a27f25e0..a3aa239c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc index a7ec3f25..b4329154 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc index c9211c36..63633406 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc index 296b40b2..286b00f0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc index ca06afcd..71521c6f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/__init__.cpython-312.pyc index 41816b2b..c44d4230 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_mode.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_mode.cpython-312.pyc index 4d55c194..a0840c7f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_mode.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_mode.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_version.cpython-312.pyc index d1d8f78e..44d08b47 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_api_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_app_info.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_app_info.cpython-312.pyc index ce4e3329..691c1a14 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_app_info.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_app_info.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_base_address.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_base_address.cpython-312.pyc index 48c12cd5..bfbfe420 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_base_address.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_base_address.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_encode.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_encode.cpython-312.pyc index bac465f3..cf23dd9e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_encode.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_encode.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error.cpython-312.pyc index 3e24de70..0c5e8798 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error_object.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error_object.cpython-312.pyc index 657965ca..c6b94527 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error_object.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_error_object.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_http_client.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_http_client.cpython-312.pyc index bf5162cf..4453e47f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_http_client.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_http_client.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_metrics.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_metrics.cpython-312.pyc index 79a21b32..380a927b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_metrics.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_metrics.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_options.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_options.cpython-312.pyc index 11b483f9..91416d75 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_options.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_request_options.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_requestor_options.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_requestor_options.cpython-312.pyc index b6d6101d..933e0582 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_requestor_options.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_requestor_options.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_object.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_object.cpython-312.pyc index 7c711cb7..e1bbd605 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_object.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_object.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_response.cpython-312.pyc index abb3c864..38f3c6f6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_stripe_response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_util.cpython-312.pyc index 280867a6..5daff8b2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_version.cpython-312.pyc index 6ac829a7..e502d8c5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/stripe/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc index ed21ef52..edd2aa0d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc index b104470d..025f7836 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc index 0b75ba52..374167e8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc index 8091b7b9..fda16da2 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc index 9a97adbe..b78c19ae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc index bf3d45fc..7b478c30 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc index 929e9d0a..30ed3cdd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc index bc4b158c..1bc2dca7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc index 2cc38246..4c492504 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc index cd352c78..db4e5779 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc index 0ec5d7a7..3ab813d6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/fields.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/fields.cpython-312.pyc index ed1b2b75..933f0cab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/fields.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/fields.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc index edc2b3ce..c0be0198 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc index b016ace9..265af588 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc index 87f22365..ca1b2416 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc index 7ee35c9f..63c8033b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc index 52910834..cfe39a5c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc index 4f5eaeb1..54195322 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc index 4265ff1e..bdbef11a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc index 54e851b4..461c0373 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc index 0f17a5a9..4de78fa6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc index c562a2ef..bbc26843 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc index a2b6e9b7..13def6de 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc index 73b2a5cf..b154e9b0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc index 408019d4..be194aa3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc index 7a1e23f8..2b9cf1b8 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc index a422f053..c09841d4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc index 97aaf157..581ff6db 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc index 6b797605..147d42db 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc index aacd18d7..4ae1d938 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc index 1ec28fc5..f8b38f8b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc index c3493052..e116ee02 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc index 3c4c691c..31135234 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc index 44a631b5..803f1fd7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc index f94fe161..3792a2d1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/config.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/config.cpython-312.pyc index 65e98b86..766d3dd3 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/config.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/config.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc index d1f0d23f..075676a0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc index 7c1dea90..7232a4b4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/main.cpython-312.pyc index 864e70a2..663bf901 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/server.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/server.cpython-312.pyc index d74c7931..bceac539 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/server.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/__pycache__/server.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc index ffaf035b..af91ebae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc index 1b74e8f6..33aa18be 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc index 7f8e7598..2c57299e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc index 9d1366a0..bd28e92f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc index bbafb54c..97c71742 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc index 21f4a33f..f1b048dd 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc index b1155b8e..51aaf4ba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc index 63d82a01..5930316c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc index 0ef96fe3..4079fa5b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc index e8129d48..56148791 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc index 900450cc..d2bcf144 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc index f3e20916..1708774f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc index 5198ca18..de620a97 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc index 6243278d..cab0d44d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc index 0b68490f..501001ce 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc index b0221a36..5b13818d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc index d9c90f62..cdee0dab 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc index 735f8bca..f5d47f95 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc index 438e8a7e..1fec6eba 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc index 9f84e614..cece4787 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc index 685ee863..c629585b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc index f8d344a9..3e35ecb6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc index 593420ca..9a189192 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/__init__.cpython-312.pyc index 89497c31..9602340b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_noop.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_noop.cpython-312.pyc index dc0f28b1..cc7ac607 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_noop.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_noop.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_version.cpython-312.pyc index f1ad73b5..551c98b5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvloop/__pycache__/_version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/uvloop/includes/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/uvloop/includes/__pycache__/__init__.cpython-312.pyc index b88bc5d9..fcbd6a4e 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/uvloop/includes/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/uvloop/includes/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/__init__.cpython-312.pyc index 49400b70..9a92977f 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/filters.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/filters.cpython-312.pyc index fa274bfd..ce115436 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/filters.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/filters.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/main.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/main.cpython-312.pyc index 20d66660..d364c0b7 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/main.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/main.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/run.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/run.cpython-312.pyc index 02169eb6..c69b5713 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/run.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/run.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/version.cpython-312.pyc index 1ce06e69..18fc1e5b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/watchfiles/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/__init__.cpython-312.pyc index 883dce51..7f7c4b8d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/labels.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/labels.cpython-312.pyc index ba735a8a..6370dbf4 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/labels.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/webencodings/__pycache__/labels.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/__init__.cpython-312.pyc index 29e352fd..16840125 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/datastructures.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/datastructures.cpython-312.pyc index dee0f5f5..2c634817 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/datastructures.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/datastructures.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/exceptions.cpython-312.pyc index a35e46cf..046d6868 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/frames.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/frames.cpython-312.pyc index c31ba29e..10003230 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/frames.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/frames.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/headers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/headers.cpython-312.pyc index 69a8525b..2ce08882 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/headers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/headers.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/http11.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/http11.cpython-312.pyc index 537a667a..4ecd0dbb 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/http11.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/http11.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/imports.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/imports.cpython-312.pyc index a96f1e43..10271789 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/imports.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/imports.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/protocol.cpython-312.pyc index 647356c9..e648e6d1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/server.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/server.cpython-312.pyc index ef43549d..47c4d526 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/server.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/server.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/streams.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/streams.cpython-312.pyc index ee430dcc..8793c155 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/streams.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/streams.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/typing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/typing.cpython-312.pyc index 19965edc..e81b9bd0 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/typing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/typing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/utils.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/utils.cpython-312.pyc index d8a87d3e..0894a43a 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/utils.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/utils.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/version.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/version.cpython-312.pyc index 0689773f..06784c4c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/version.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/__pycache__/version.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/__init__.cpython-312.pyc index 9537c252..8d59f4f9 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-312.pyc index d22ae148..e2c368ae 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/asyncio/__pycache__/compatibility.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/__init__.cpython-312.pyc index 46d6015e..7706d808 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/base.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/base.cpython-312.pyc index 51addb78..b71b448c 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/base.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/base.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-312.pyc index b1710943..ee12e4c6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/extensions/__pycache__/permessage_deflate.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/__init__.cpython-312.pyc index 8b6ec681..56c1bacc 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/exceptions.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/exceptions.cpython-312.pyc index e71f421a..6d685b18 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/exceptions.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/framing.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/framing.cpython-312.pyc index 9836535c..27eaf9ca 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/framing.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/framing.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/handshake.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/handshake.cpython-312.pyc index 78630a77..9ff10c72 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/handshake.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/handshake.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/http.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/http.cpython-312.pyc index 8cc2bf25..30d2fff6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/http.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/http.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/protocol.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/protocol.cpython-312.pyc index 9083fc80..ebd4d1a1 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/protocol.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/protocol.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/server.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/server.cpython-312.pyc index bad3507e..a8fad7d6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/server.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/websockets/legacy/__pycache__/server.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__init__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__init__.cpython-312.pyc index 2738a53a..02dcacb5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__init__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__init__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__wrapt__.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__wrapt__.cpython-312.pyc index 8378e28c..3c240f67 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__wrapt__.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/__wrapt__.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/arguments.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/arguments.cpython-312.pyc index 5449368f..98d52094 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/arguments.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/arguments.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/decorators.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/decorators.cpython-312.pyc index 570e3002..ae22388b 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/decorators.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/decorators.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/importer.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/importer.cpython-312.pyc index 80682b89..6e3450b6 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/importer.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/importer.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/patches.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/patches.cpython-312.pyc index 047f2eb6..46ac1c6d 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/patches.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/patches.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/proxies.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/proxies.cpython-312.pyc index 097c27ad..245467e5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/proxies.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/proxies.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/weakrefs.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/weakrefs.cpython-312.pyc index e98213d6..43486be5 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/weakrefs.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/weakrefs.cpython-312.pyc differ diff --git a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/wrappers.cpython-312.pyc b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/wrappers.cpython-312.pyc index 49d3e9fb..424eb591 100644 Binary files a/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/wrappers.cpython-312.pyc and b/Backend/venv/lib/python3.12/site-packages/wrapt/__pycache__/wrappers.cpython-312.pyc differ diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx index 739aff7a..8ea7e8a8 100644 --- a/Frontend/src/App.tsx +++ b/Frontend/src/App.tsx @@ -121,6 +121,7 @@ const StaffShiftManagementPage = lazy(() => import('./pages/admin/StaffShiftMana const StaffDashboardPage = lazy(() => import('./pages/staff/DashboardPage')); const StaffInventoryViewPage = lazy(() => import('./pages/staff/InventoryViewPage')); const StaffShiftViewPage = lazy(() => import('./pages/staff/ShiftViewPage')); +const StaffTeamChatPage = lazy(() => import('./pages/staff/TeamChatPage')); const StaffBookingManagementPage = lazy(() => import('./pages/staff/BookingManagementPage')); const StaffReceptionDashboardPage = lazy(() => import('./pages/staff/ReceptionDashboardPage')); const StaffPaymentManagementPage = lazy(() => import('./pages/staff/PaymentManagementPage')); @@ -132,6 +133,7 @@ const GuestRequestManagementPage = lazy(() => import('./pages/staff/GuestRequest const GuestCommunicationPage = lazy(() => import('./pages/staff/GuestCommunicationPage')); const IncidentComplaintManagementPage = lazy(() => import('./pages/staff/IncidentComplaintManagementPage')); const UpsellManagementPage = lazy(() => import('./pages/staff/UpsellManagementPage')); +const StaffLoyaltyManagementPage = lazy(() => import('./pages/staff/LoyaltyManagementPage')); const StaffLayout = lazy(() => import('./pages/StaffLayout')); const AccountantDashboardPage = lazy(() => import('./pages/accountant/DashboardPage')); @@ -149,9 +151,12 @@ const AccountantLayout = lazy(() => import('./pages/AccountantLayout')); const HousekeepingDashboardPage = lazy(() => import('./pages/housekeeping/DashboardPage')); const HousekeepingTasksPage = lazy(() => import('./pages/housekeeping/TasksPage')); const HousekeepingShiftViewPage = lazy(() => import('./pages/housekeeping/ShiftViewPage')); +const HousekeepingProfilePage = lazy(() => import('./pages/housekeeping/ProfilePage')); +const HousekeepingTeamChatPage = lazy(() => import('./pages/housekeeping/TeamChatPage')); const HousekeepingLayout = lazy(() => import('./pages/HousekeepingLayout')); const AdminProfilePage = lazy(() => import('./pages/admin/ProfilePage')); +const AdminTeamChatPage = lazy(() => import('./pages/admin/TeamChatPage')); const StaffProfilePage = lazy(() => import('./pages/staff/ProfilePage')); const AccountantProfilePage = lazy(() => import('./pages/accountant/ProfilePage')); @@ -669,6 +674,10 @@ function App() { path="email-campaigns" element={} /> + } + /> } @@ -815,10 +824,18 @@ function App() { path="inventory" element={} /> + } + /> } /> + } + /> {/* Accountant Routes */} @@ -905,7 +922,9 @@ function App() { } /> } /> } /> + } /> } /> + } /> {} diff --git a/Frontend/src/features/rooms/contexts/RoomContext.tsx b/Frontend/src/features/rooms/contexts/RoomContext.tsx index ac723d15..ad018c77 100644 --- a/Frontend/src/features/rooms/contexts/RoomContext.tsx +++ b/Frontend/src/features/rooms/contexts/RoomContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react'; +import React, { createContext, useContext, useState, useCallback, useEffect, useRef, useMemo } from 'react'; import roomService, { Room } from '../services/roomService'; import advancedRoomService, { RoomStatusBoardItem } from '../services/advancedRoomService'; import { toast } from 'react-toastify'; @@ -41,7 +41,21 @@ const RoomContext = createContext(undefined); export const useRoomContext = () => { const context = useContext(RoomContext); if (!context) { - throw new Error('useRoomContext must be used within a RoomProvider'); + // Provide a more helpful error message with debugging info + const error = new Error( + 'useRoomContext must be used within a RoomProvider. ' + + 'Make sure RoomProvider wraps your component tree in App.tsx' + ); + if (import.meta.env.DEV) { + console.error('RoomContext Error:', { + context, + stack: error.stack, + // Check if we're in a development environment + isDev: import.meta.env.DEV, + hint: 'This error usually occurs when: 1) Component is rendered outside RoomProvider, 2) Hot module reload issue, 3) Context provider not mounted yet', + }); + } + throw error; } return context; }; @@ -95,14 +109,14 @@ export const RoomProvider: React.FC = ({ children }) => { const response = await roomService.getRooms({ limit: 100, // Reasonable batch size for sync page: 1, - }); + }, roomsAbortRef.current.signal); if (response.data?.rooms) { setRooms(response.data.rooms); setLastUpdate(Date.now()); } } catch (error: any) { - if (error.name === 'AbortError') { + if (error.name === 'AbortError' || error.code === 'ERR_CANCELED' || error.isCancelled) { return; } logger.error('Error refreshing rooms', error); @@ -127,14 +141,17 @@ export const RoomProvider: React.FC = ({ children }) => { setStatusBoardLoading(true); setStatusBoardError(null); - const response = await advancedRoomService.getRoomStatusBoard(floor || statusBoardFloor || undefined); + const response = await advancedRoomService.getRoomStatusBoard( + floor || statusBoardFloor || undefined, + statusBoardAbortRef.current.signal + ); if (response.status === 'success' && response.data?.rooms) { setStatusBoardRooms(response.data.rooms); setLastUpdate(Date.now()); } } catch (error: any) { - if (error.name === 'AbortError') { + if (error.name === 'AbortError' || error.code === 'ERR_CANCELED' || error.isCancelled) { return; } @@ -228,7 +245,8 @@ export const RoomProvider: React.FC = ({ children }) => { refreshRooms(); // Don't fetch status board on initial load - it requires admin/staff role // It will be fetched when the component that needs it mounts - }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // refreshRooms is stable (useCallback with empty deps), so this is safe // Initial load and periodic refresh handled by auto-refresh interval @@ -290,7 +308,7 @@ export const RoomProvider: React.FC = ({ children }) => { }; }, []); - const value: RoomContextType = { + const value: RoomContextType = useMemo(() => ({ rooms, roomsLoading, roomsError, @@ -309,7 +327,26 @@ export const RoomProvider: React.FC = ({ children }) => { setStatusBoardFloor, statusBoardFloor, lastUpdate, - }; + }), [ + rooms, + roomsLoading, + roomsError, + statusBoardRooms, + statusBoardLoading, + statusBoardError, + refreshRooms, + refreshStatusBoard, + updateRoom, + deleteRoom, + createRoom, + setRoomFilters, + roomFilters, + setRoomPage, + roomPage, + setStatusBoardFloor, + statusBoardFloor, + lastUpdate, + ]); return {children}; }; diff --git a/Frontend/src/features/rooms/services/advancedRoomService.ts b/Frontend/src/features/rooms/services/advancedRoomService.ts index 9708d7ae..dec6b559 100644 --- a/Frontend/src/features/rooms/services/advancedRoomService.ts +++ b/Frontend/src/features/rooms/services/advancedRoomService.ts @@ -290,9 +290,10 @@ const advancedRoomService = { }, // Room Status Board - async getRoomStatusBoard(floor?: number) { + async getRoomStatusBoard(floor?: number, signal?: AbortSignal) { const response = await apiClient.get('/advanced-rooms/status-board', { - params: floor ? { floor } : {} + params: floor ? { floor } : {}, + signal, }); return response.data; }, diff --git a/Frontend/src/features/rooms/services/roomService.ts b/Frontend/src/features/rooms/services/roomService.ts index 7efae9a9..cb76261c 100644 --- a/Frontend/src/features/rooms/services/roomService.ts +++ b/Frontend/src/features/rooms/services/roomService.ts @@ -78,10 +78,12 @@ export const getFeaturedRooms = async ( }; export const getRooms = async ( - params: RoomSearchParams = {} + params: RoomSearchParams = {}, + signal?: AbortSignal ): Promise => { const response = await apiClient.get('/rooms', { params, + signal, }); const data = response.data; // Handle both 'status: success' and 'success: true' formats @@ -199,9 +201,28 @@ export const deleteAmenity = async ( export interface RoomType { id: number; name: string; - description: string; + description?: string; base_price: number; capacity: number; + amenities?: string[]; + created_at?: string; + updated_at?: string; +} + +export interface CreateRoomTypeData { + name: string; + description?: string; + base_price: number; + capacity: number; + amenities?: string[]; +} + +export interface UpdateRoomTypeData { + name?: string; + description?: string; + base_price?: number; + capacity?: number; + amenities?: string[]; } export const getRoomTypes = async (): Promise<{ @@ -218,6 +239,58 @@ export const getRoomTypes = async (): Promise<{ }; }; +export const getRoomType = async (id: number): Promise<{ + success: boolean; + status?: string; + data: { room_type: RoomType }; + message?: string; +}> => { + const response = await apiClient.get(`/rooms/room-types/${id}`); + const data = response.data; + return { + success: data.status === 'success' || data.success === true, + status: data.status, + data: data.data || {}, + message: data.message || '', + }; +}; + +export const createRoomType = async ( + data: CreateRoomTypeData +): Promise<{ success: boolean; data: { room_type: RoomType }; message: string }> => { + const response = await apiClient.post('/rooms/room-types', data); + const responseData = response.data; + return { + success: responseData.status === 'success' || responseData.success === true, + data: responseData.data || {}, + message: responseData.message || '', + }; +}; + +export const updateRoomType = async ( + id: number, + data: UpdateRoomTypeData +): Promise<{ success: boolean; data: { room_type: RoomType }; message: string }> => { + const response = await apiClient.put(`/rooms/room-types/${id}`, data); + const responseData = response.data; + return { + success: responseData.status === 'success' || responseData.success === true, + data: responseData.data || {}, + message: responseData.message || '', + }; +}; + +export const deleteRoomType = async ( + id: number +): Promise<{ success: boolean; message: string }> => { + const response = await apiClient.delete(`/rooms/room-types/${id}`); + const data = response.data; + return { + success: data.status === 'success' || data.success === true, + message: data.message || '', + }; +}; + export interface CreateRoomData { room_number: string; floor: number; @@ -294,6 +367,10 @@ export default { updateAmenity, deleteAmenity, getRoomTypes, + getRoomType, + createRoomType, + updateRoomType, + deleteRoomType, createRoom, updateRoom, deleteRoom, diff --git a/Frontend/src/features/team-chat/components/TeamChatPage.tsx b/Frontend/src/features/team-chat/components/TeamChatPage.tsx new file mode 100644 index 00000000..e3c1295b --- /dev/null +++ b/Frontend/src/features/team-chat/components/TeamChatPage.tsx @@ -0,0 +1,848 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { + MessageSquare, + Users, + Hash, + Plus, + Send, + MoreVertical, + Search, + Settings, + User, + Circle, + Bell, + BellOff, + Trash2, + Edit2, + Reply, + Megaphone, + ChevronDown, + X, + Check, + AlertCircle +} from 'lucide-react'; +import { toast } from 'react-toastify'; +import teamChatService, { + TeamChannel, + TeamMessage, + TeamUser +} from '../services/teamChatService'; +import useAuthStore from '../../../store/useAuthStore'; +import { normalizeImageUrl } from '../../../shared/utils/imageUtils'; +import { formatDistanceToNow } from 'date-fns'; + +interface TeamChatPageProps { + role: 'admin' | 'staff' | 'housekeeping'; +} + +const TeamChatPage: React.FC = ({ role }) => { + const { userInfo } = useAuthStore(); + const [channels, setChannels] = useState([]); + const [selectedChannel, setSelectedChannel] = useState(null); + const [messages, setMessages] = useState([]); + const [teamUsers, setTeamUsers] = useState([]); + const [newMessage, setNewMessage] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [isSending, setIsSending] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [showNewChannelModal, setShowNewChannelModal] = useState(false); + const [showNewDMModal, setShowNewDMModal] = useState(false); + const [editingMessage, setEditingMessage] = useState(null); + const [editContent, setEditContent] = useState(''); + const messagesEndRef = useRef(null); + const [ws, setWs] = useState(null); + + // Fetch channels + const fetchChannels = useCallback(async () => { + try { + const response = await teamChatService.getMyChannels(); + if (response.success) { + setChannels(response.data); + // Auto-select first channel if none selected + if (!selectedChannel && response.data.length > 0) { + setSelectedChannel(response.data[0]); + } + } + } catch (error) { + console.error('Error fetching channels:', error); + } + }, [selectedChannel]); + + // Fetch team users + const fetchTeamUsers = useCallback(async () => { + try { + const response = await teamChatService.getTeamUsers(); + if (response.success) { + setTeamUsers(response.data); + } + } catch (error) { + console.error('Error fetching team users:', error); + } + }, []); + + // Fetch messages for selected channel + const fetchMessages = useCallback(async (channelId: number) => { + try { + const response = await teamChatService.getChannelMessages(channelId); + if (response.success) { + setMessages(response.data); + // Scroll to bottom + setTimeout(() => messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }), 100); + } + } catch (error) { + console.error('Error fetching messages:', error); + } + }, []); + + // Initialize WebSocket + useEffect(() => { + const wsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/api/v1/team-chat/ws`; + const socket = new WebSocket(wsUrl); + + socket.onopen = () => { + // Send authentication + socket.send(JSON.stringify({ type: 'auth', user_id: userInfo?.id })); + }; + + socket.onmessage = (event) => { + const data = JSON.parse(event.data); + + if (data.type === 'new_message' && data.data.channel_id === selectedChannel?.id) { + setMessages(prev => [...prev, data.data]); + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + } else if (data.type === 'new_message_notification') { + // Show notification for messages in other channels + toast.info(`New message in ${data.data.channel_name || 'Team Chat'}`); + fetchChannels(); // Refresh unread counts + } else if (data.type === 'message_edited') { + setMessages(prev => prev.map(m => m.id === data.data.id ? data.data : m)); + } else if (data.type === 'message_deleted') { + setMessages(prev => prev.filter(m => m.id !== data.data.id)); + } + }; + + socket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + + setWs(socket); + + return () => { + socket.close(); + }; + }, [userInfo?.id, selectedChannel?.id, fetchChannels]); + + // Initial data load + useEffect(() => { + const loadData = async () => { + setIsLoading(true); + await Promise.all([fetchChannels(), fetchTeamUsers()]); + setIsLoading(false); + }; + loadData(); + }, [fetchChannels, fetchTeamUsers]); + + // Load messages when channel changes + useEffect(() => { + if (selectedChannel) { + fetchMessages(selectedChannel.id); + // Join channel in WebSocket + ws?.send(JSON.stringify({ type: 'join_channel', channel_id: selectedChannel.id })); + } + }, [selectedChannel, fetchMessages, ws]); + + // Send message + const handleSendMessage = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newMessage.trim() || !selectedChannel) return; + + setIsSending(true); + try { + const response = await teamChatService.sendMessage(selectedChannel.id, { + content: newMessage.trim() + }); + if (response.success) { + setMessages(prev => [...prev, response.data]); + setNewMessage(''); + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + } + } catch (error) { + console.error('Error sending message:', error); + toast.error('Failed to send message'); + } finally { + setIsSending(false); + } + }; + + // Edit message + const handleEditMessage = async (messageId: number) => { + if (!editContent.trim()) return; + try { + const response = await teamChatService.editMessage(messageId, editContent.trim()); + if (response.success) { + setMessages(prev => prev.map(m => m.id === messageId ? response.data : m)); + setEditingMessage(null); + setEditContent(''); + toast.success('Message edited'); + } + } catch (error) { + toast.error('Failed to edit message'); + } + }; + + // Delete message + const handleDeleteMessage = async (messageId: number) => { + if (!confirm('Delete this message?')) return; + try { + await teamChatService.deleteMessage(messageId); + setMessages(prev => prev.filter(m => m.id !== messageId)); + toast.success('Message deleted'); + } catch (error) { + toast.error('Failed to delete message'); + } + }; + + // Start DM + const handleStartDM = async (userId: number) => { + try { + const response = await teamChatService.sendDirectMessage({ + recipient_id: userId, + content: '👋 Hi!' + }); + if (response.success) { + await fetchChannels(); + const newChannel = channels.find(c => c.id === response.data.channel_id); + if (newChannel) setSelectedChannel(newChannel); + setShowNewDMModal(false); + toast.success('Conversation started!'); + } + } catch (error) { + toast.error('Failed to start conversation'); + } + }; + + // Create channel + const handleCreateChannel = async (name: string, memberIds: number[]) => { + try { + const response = await teamChatService.createChannel({ + name, + channel_type: 'group', + member_ids: memberIds + }); + if (response.success) { + await fetchChannels(); + setSelectedChannel(response.data); + setShowNewChannelModal(false); + toast.success('Channel created!'); + } + } catch (error) { + toast.error('Failed to create channel'); + } + }; + + // Get channel icon + const getChannelIcon = (channel: TeamChannel) => { + switch (channel.channel_type) { + case 'direct': + return ; + case 'announcement': + return ; + case 'department': + return ; + default: + return ; + } + }; + + // Filter channels + const filteredChannels = channels.filter(c => + c.name?.toLowerCase().includes(searchQuery.toLowerCase()) || + c.members.some(m => m.full_name.toLowerCase().includes(searchQuery.toLowerCase())) + ); + + // Get role color + const getRoleColor = (userRole: string | null) => { + switch (userRole) { + case 'admin': return 'bg-purple-100 text-purple-700'; + case 'staff': return 'bg-blue-100 text-blue-700'; + case 'housekeeping': return 'bg-amber-100 text-amber-700'; + default: return 'bg-gray-100 text-gray-700'; + } + }; + + // Get status color + const getStatusColor = (status?: string) => { + switch (status) { + case 'online': return 'bg-green-500'; + case 'away': return 'bg-yellow-500'; + case 'busy': return 'bg-red-500'; + default: return 'bg-gray-400'; + } + }; + + if (isLoading) { + return ( +
+
+
+

Loading team chat...

+
+
+ ); + } + + return ( +
+ {/* Sidebar - Channels List */} +
+ {/* Header */} +
+
+

+ + Team Chat +

+
+ + +
+
+ + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="w-full pl-9 pr-4 py-2 bg-gray-700 border border-gray-600 rounded-lg text-sm text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + {/* Channels List */} +
+ {/* Department Channels */} +
+

+ Channels +

+ {filteredChannels + .filter(c => c.channel_type !== 'direct') + .map(channel => ( + + ))} +
+ + {/* Direct Messages */} +
+

+ Direct Messages +

+ {filteredChannels + .filter(c => c.channel_type === 'direct') + .map(channel => { + const otherMember = channel.members.find(m => m.id !== userInfo?.id); + return ( + + ); + })} +
+
+ + {/* User Status */} +
+
+
+ {userInfo?.avatar ? ( + + ) : ( +
+ +
+ )} + +
+
+

{userInfo?.name}

+

{role}

+
+
+
+
+ + {/* Main Chat Area */} +
+ {selectedChannel ? ( + <> + {/* Channel Header */} +
+
+
+ {getChannelIcon(selectedChannel)} +
+
+

{selectedChannel.name}

+

+ {selectedChannel.members.length} members +

+
+
+
+ + +
+
+ + {/* Messages Area */} +
+ {messages.length === 0 ? ( +
+ +

No messages yet

+

Start the conversation!

+
+ ) : ( +
+ {messages.map((message, index) => { + const isOwn = message.sender_id === userInfo?.id; + const showAvatar = index === 0 || + messages[index - 1]?.sender_id !== message.sender_id; + + return ( +
+ {/* Avatar */} +
+ {showAvatar && ( + message.sender?.avatar_url ? ( + + ) : ( +
+ +
+ ) + )} +
+ + {/* Message Content */} +
+ {showAvatar && ( +
+ + {message.sender?.full_name || 'Unknown'} + + {message.sender?.role && ( + + {message.sender.role} + + )} + + {formatDistanceToNow(new Date(message.created_at), { addSuffix: true })} + +
+ )} + + {editingMessage === message.id ? ( +
+ setEditContent(e.target.value)} + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500" + autoFocus + /> + + +
+ ) : ( +
+

+ {message.content} +

+ {message.is_edited && ( + + (edited) + + )} + + {/* Message Actions */} + {isOwn && ( +
+ + +
+ )} +
+ )} +
+
+ ); + })} +
+
+ )} +
+ + {/* Message Input */} +
+
+ setNewMessage(e.target.value)} + placeholder={`Message ${selectedChannel.name}...`} + className="flex-1 px-4 py-3 bg-gray-100 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:bg-white transition-colors" + disabled={selectedChannel.channel_type === 'announcement' && role !== 'admin'} + /> + +
+ {selectedChannel.channel_type === 'announcement' && role !== 'admin' && ( +

+ + Only admins can post in announcement channels +

+ )} +
+ + ) : ( +
+
+ +

Welcome to Team Chat

+

+ Select a channel or start a new conversation to communicate with your team. +

+
+ + +
+
+
+ )} +
+ + {/* New Channel Modal */} + {showNewChannelModal && ( + setShowNewChannelModal(false)} + onCreate={handleCreateChannel} + /> + )} + + {/* New DM Modal */} + {showNewDMModal && ( + setShowNewDMModal(false)} + onSelect={handleStartDM} + /> + )} +
+ ); +}; + +// New Channel Modal Component +const NewChannelModal: React.FC<{ + users: TeamUser[]; + onClose: () => void; + onCreate: (name: string, memberIds: number[]) => void; +}> = ({ users, onClose, onCreate }) => { + const [name, setName] = useState(''); + const [selectedUsers, setSelectedUsers] = useState([]); + const [search, setSearch] = useState(''); + + const filteredUsers = users.filter(u => + u.full_name.toLowerCase().includes(search.toLowerCase()) || + u.email.toLowerCase().includes(search.toLowerCase()) + ); + + const toggleUser = (userId: number) => { + setSelectedUsers(prev => + prev.includes(userId) ? prev.filter(id => id !== userId) : [...prev, userId] + ); + }; + + return ( +
+
+
+

Create Channel

+ +
+
+
+ + setName(e.target.value)} + placeholder="e.g., morning-shift" + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" + /> +
+
+ + setSearch(e.target.value)} + placeholder="Search team members..." + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 mb-2" + /> +
+ {filteredUsers.map(user => ( + + ))} +
+
+
+
+ + +
+
+
+ ); +}; + +// New DM Modal Component +const NewDMModal: React.FC<{ + users: TeamUser[]; + onClose: () => void; + onSelect: (userId: number) => void; +}> = ({ users, onClose, onSelect }) => { + const [search, setSearch] = useState(''); + + const filteredUsers = users.filter(u => + u.full_name.toLowerCase().includes(search.toLowerCase()) || + u.email.toLowerCase().includes(search.toLowerCase()) + ); + + const getRoleColor = (role: string | null) => { + switch (role) { + case 'admin': return 'bg-purple-100 text-purple-700'; + case 'staff': return 'bg-blue-100 text-blue-700'; + case 'housekeeping': return 'bg-amber-100 text-amber-700'; + default: return 'bg-gray-100 text-gray-700'; + } + }; + + return ( +
+
+
+

New Message

+ +
+
+ setSearch(e.target.value)} + placeholder="Search team members..." + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 mb-4" + autoFocus + /> +
+ {filteredUsers.length === 0 ? ( +

No team members found

+ ) : ( + filteredUsers.map(user => ( + + )) + )} +
+
+
+
+ ); +}; + +export default TeamChatPage; + diff --git a/Frontend/src/features/team-chat/index.ts b/Frontend/src/features/team-chat/index.ts new file mode 100644 index 00000000..51eafe29 --- /dev/null +++ b/Frontend/src/features/team-chat/index.ts @@ -0,0 +1,12 @@ +export { default as TeamChatPage } from './components/TeamChatPage'; +export { default as teamChatService } from './services/teamChatService'; +export type { + TeamChannel, + TeamMessage, + TeamUser, + ChannelMember, + CreateChannelData, + SendMessageData, + DirectMessageData +} from './services/teamChatService'; + diff --git a/Frontend/src/features/team-chat/services/teamChatService.ts b/Frontend/src/features/team-chat/services/teamChatService.ts new file mode 100644 index 00000000..fe4fefa7 --- /dev/null +++ b/Frontend/src/features/team-chat/services/teamChatService.ts @@ -0,0 +1,167 @@ +import apiClient from '../../../shared/services/apiClient'; + +export interface TeamUser { + id: number; + full_name: string; + email: string; + avatar_url: string | null; + role: string | null; + status?: string; + last_seen?: string; +} + +export interface ChannelMember { + id: number; + full_name: string; + email: string; + avatar_url: string | null; + role: string | null; +} + +export interface TeamMessage { + id: number; + channel_id: number; + sender_id: number; + sender: TeamUser | null; + content: string; + priority: 'normal' | 'high' | 'urgent'; + reply_to_id: number | null; + reference_type: string | null; + reference_id: number | null; + is_edited: boolean; + is_deleted: boolean; + created_at: string; + edited_at: string | null; +} + +export interface TeamChannel { + id: number; + name: string; + description: string | null; + channel_type: 'direct' | 'group' | 'department' | 'announcement'; + department: string | null; + is_private: boolean; + is_active: boolean; + created_at: string; + last_message_at: string | null; + unread_count: number; + members: ChannelMember[]; + last_message: TeamMessage | null; +} + +export interface CreateChannelData { + name?: string; + description?: string; + channel_type?: 'group' | 'department' | 'announcement'; + department?: string; + is_private?: boolean; + member_ids?: number[]; +} + +export interface SendMessageData { + content: string; + priority?: 'normal' | 'high' | 'urgent'; + reply_to_id?: number; + reference_type?: string; + reference_id?: number; +} + +export interface DirectMessageData { + recipient_id: number; + content: string; + priority?: 'normal' | 'high' | 'urgent'; +} + +class TeamChatService { + // Channels + async getMyChannels(): Promise<{ success: boolean; data: TeamChannel[] }> { + const response = await apiClient.get('/team-chat/channels'); + return response.data; + } + + async createChannel(data: CreateChannelData): Promise<{ success: boolean; data: TeamChannel }> { + const response = await apiClient.post('/team-chat/channels', data); + return response.data; + } + + async getChannel(channelId: number): Promise<{ success: boolean; data: TeamChannel }> { + const response = await apiClient.get(`/team-chat/channels/${channelId}`); + return response.data; + } + + async updateChannel(channelId: number, data: Partial): Promise<{ success: boolean; data: TeamChannel }> { + const response = await apiClient.put(`/team-chat/channels/${channelId}`, data); + return response.data; + } + + async addChannelMembers(channelId: number, memberIds: number[]): Promise<{ success: boolean; message: string }> { + const response = await apiClient.post(`/team-chat/channels/${channelId}/members`, memberIds); + return response.data; + } + + async removeChannelMember(channelId: number, userId: number): Promise<{ success: boolean; message: string }> { + const response = await apiClient.delete(`/team-chat/channels/${channelId}/members/${userId}`); + return response.data; + } + + // Messages + async getChannelMessages( + channelId: number, + limit: number = 50, + beforeId?: number + ): Promise<{ success: boolean; data: TeamMessage[] }> { + const params = new URLSearchParams({ limit: limit.toString() }); + if (beforeId) params.append('before_id', beforeId.toString()); + const response = await apiClient.get(`/team-chat/channels/${channelId}/messages?${params}`); + return response.data; + } + + async sendMessage(channelId: number, data: SendMessageData): Promise<{ success: boolean; data: TeamMessage }> { + const response = await apiClient.post(`/team-chat/channels/${channelId}/messages`, data); + return response.data; + } + + async editMessage(messageId: number, content: string): Promise<{ success: boolean; data: TeamMessage }> { + const response = await apiClient.put(`/team-chat/messages/${messageId}`, { content }); + return response.data; + } + + async deleteMessage(messageId: number): Promise<{ success: boolean; message: string }> { + const response = await apiClient.delete(`/team-chat/messages/${messageId}`); + return response.data; + } + + // Direct Messages + async sendDirectMessage(data: DirectMessageData): Promise<{ success: boolean; data: { channel_id: number; message: TeamMessage } }> { + const response = await apiClient.post('/team-chat/direct', data); + return response.data; + } + + // Users + async getTeamUsers(role?: string): Promise<{ success: boolean; data: TeamUser[] }> { + const params = role ? `?role=${role}` : ''; + const response = await apiClient.get(`/team-chat/users${params}`); + return response.data; + } + + // Presence + async updatePresence(status: 'online' | 'away' | 'busy' | 'offline', customStatus?: string): Promise<{ success: boolean; message: string }> { + const response = await apiClient.put('/team-chat/presence', { status, custom_status: customStatus }); + return response.data; + } + + // Department Channels + async initializeDepartmentChannels(): Promise<{ success: boolean; message: string }> { + const response = await apiClient.post('/team-chat/departments/init'); + return response.data; + } + + async joinDepartmentChannel(department: string): Promise<{ success: boolean; data: TeamChannel }> { + const response = await apiClient.post(`/team-chat/departments/${department}/join`); + return response.data; + } +} + +export const teamChatService = new TeamChatService(); +export default teamChatService; + diff --git a/Frontend/src/pages/AccountantLayout.tsx b/Frontend/src/pages/AccountantLayout.tsx index 2279ef82..8faa1480 100644 --- a/Frontend/src/pages/AccountantLayout.tsx +++ b/Frontend/src/pages/AccountantLayout.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Outlet } from 'react-router-dom'; import SidebarAccountant from '../shared/components/SidebarAccountant'; +import InAppNotificationBell from '../features/notifications/components/InAppNotificationBell'; import { useResponsive } from '../shared/hooks/useResponsive'; const AccountantLayout: React.FC = () => { @@ -17,6 +18,11 @@ const AccountantLayout: React.FC = () => {
+ + {/* Notification bell - fixed position in top right */} +
+ +
); }; diff --git a/Frontend/src/pages/AdminLayout.tsx b/Frontend/src/pages/AdminLayout.tsx index 833992b1..74336995 100644 --- a/Frontend/src/pages/AdminLayout.tsx +++ b/Frontend/src/pages/AdminLayout.tsx @@ -4,6 +4,7 @@ import { SidebarAdmin } from '../shared/components'; import { Sparkles, Zap } from 'lucide-react'; import { useResponsive } from '../hooks'; import AIAssistantWidget from '../features/ai/components/AIAssistantWidget'; +import InAppNotificationBell from '../features/notifications/components/InAppNotificationBell'; // Luxury Loading Overlay const LuxuryLoadingOverlay: React.FC = () => { @@ -109,6 +110,11 @@ const AdminLayout: React.FC = () => { {/* AI Assistant Widget */} + + {/* Notification bell - fixed position in top right */} +
+ +
{/* Custom CSS for shimmer animation */}