Coverage for tests / test_nullable.py: 100%

49 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-11-09 00:02 +0000

1import pytest 1stuvwxyzA

2from sqlalchemy import Engine, inspect 1stuvwxyzA

3from sqlalchemy.exc import IntegrityError 1stuvwxyzA

4from sqlmodel import Field, Session, SQLModel, create_engine 1stuvwxyzA

5 

6 

7def test_nullable_fields(database_engine: Engine): 1stuvwxyzA

8 class Hero(SQLModel, table=True): 1abcdefghi

9 primary_key: int | None = Field( 1abcdefghi

10 default=None, 

11 primary_key=True, 

12 ) 

13 required_value: str 1abcdefghi

14 optional_default_ellipsis: str | None = Field(default=...) 1abcdefghi

15 optional_default_none: str | None = Field(default=None) 1abcdefghi

16 optional_non_nullable: str | None = Field( 1abcdefghi

17 nullable=False, 

18 ) 

19 optional_nullable: str | None = Field( 1abcdefghi

20 nullable=True, 

21 ) 

22 optional_default_ellipses_non_nullable: str | None = Field( 1abcdefghi

23 default=..., 

24 nullable=False, 

25 ) 

26 optional_default_ellipses_nullable: str | None = Field( 1abcdefghi

27 default=..., 

28 nullable=True, 

29 ) 

30 optional_default_none_non_nullable: str | None = Field( 1abcdefghi

31 default=None, 

32 nullable=False, 

33 ) 

34 optional_default_none_nullable: str | None = Field( 1abcdefghi

35 default=None, 

36 nullable=True, 

37 ) 

38 default_ellipses_non_nullable: str = Field(default=..., nullable=False) 1abcdefghi

39 optional_default_str: str | None = "default" 1abcdefghi

40 optional_default_str_non_nullable: str | None = Field( 1abcdefghi

41 default="default", nullable=False 

42 ) 

43 optional_default_str_nullable: str | None = Field( 1abcdefghi

44 default="default", nullable=True 

45 ) 

46 str_default_str: str = "default" 1abcdefghi

47 str_default_str_non_nullable: str = Field(default="default", nullable=False) 1abcdefghi

48 str_default_str_nullable: str = Field(default="default", nullable=True) 1abcdefghi

49 str_default_ellipsis_non_nullable: str = Field(default=..., nullable=False) 1abcdefghi

50 str_default_ellipsis_nullable: str = Field(default=..., nullable=True) 1abcdefghi

51 

52 engine = database_engine 1abcdefghi

53 SQLModel.metadata.create_all(engine) 1abcdefghi

54 

55 nullable = { 1abcdefghi

56 column["name"]: column["nullable"] 

57 for column in inspect(engine).get_columns("hero") 

58 } 

59 assert nullable == { 1abcdefghi

60 "primary_key": False, 

61 "required_value": False, 

62 "optional_default_ellipsis": True, 

63 "optional_default_none": True, 

64 "optional_non_nullable": False, 

65 "optional_nullable": True, 

66 "optional_default_ellipses_non_nullable": False, 

67 "optional_default_ellipses_nullable": True, 

68 "optional_default_none_non_nullable": False, 

69 "optional_default_none_nullable": True, 

70 "default_ellipses_non_nullable": False, 

71 "optional_default_str": True, 

72 "optional_default_str_non_nullable": False, 

73 "optional_default_str_nullable": True, 

74 "str_default_str": False, 

75 "str_default_str_non_nullable": False, 

76 "str_default_str_nullable": True, 

77 "str_default_ellipsis_non_nullable": False, 

78 "str_default_ellipsis_nullable": True, 

79 } 

80 

81 

82# Test for regression in https://github.com/tiangolo/sqlmodel/issues/420 

83def test_non_nullable_optional_field_with_no_default_set(database_engine: Engine): 1stuvwxyzA

84 class Hero(SQLModel, table=True): 1jklmnopqr

85 primary_key: int | None = Field( 1jklmnopqr

86 default=None, 

87 primary_key=True, 

88 ) 

89 

90 optional_non_nullable_no_default: str | None = Field(nullable=False) 1jklmnopqr

91 

92 engine = database_engine 1jklmnopqr

93 SQLModel.metadata.create_all(engine) 1jklmnopqr

94 

95 nullable = { 1jklmnopqr

96 column["name"]: column["nullable"] 

97 for column in inspect(engine).get_columns("hero") 

98 } 

99 assert nullable == { 1jklmnopqr

100 "primary_key": False, 

101 "optional_non_nullable_no_default": False, 

102 } 

103 

104 # We can create a hero with `None` set for the optional non-nullable field 

105 hero = Hero(primary_key=123, optional_non_nullable_no_default=None) 1jklmnopqr

106 # But we cannot commit it. 

107 with Session(engine) as session: 1jklmnopqr

108 session.add(hero) 1jklmnopqr

109 with pytest.raises(IntegrityError): 1jklmnopqr

110 session.commit() 1jklmnopqr

111 

112 

113def test_sqlite_nullable_primary_key(clear_sqlmodel, caplog): 1stuvwxyzA

114 # Probably the weirdest corner case, it shouldn't happen anywhere, but let's test it 

115 class Hero(SQLModel, table=True): 1BCDEFGHIJ

116 nullable_integer_primary_key: int | None = Field( 1BCDEFGHIJ

117 default=None, 

118 primary_key=True, 

119 nullable=True, 

120 ) 

121 

122 engine = create_engine("sqlite://", echo=True) 1BCDEFGHIJ

123 SQLModel.metadata.create_all(engine) 1BCDEFGHIJ

124 

125 create_table_log = [ 1BCDEFGHIJ

126 message for message in caplog.messages if "CREATE TABLE hero" in message 

127 ][0] 

128 assert "nullable_integer_primary_key INTEGER," in create_table_log 1BCDEFGHIJ