Coverage for tests / test_nullable.py: 100%

67 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-06-10 09:40 +0000

1import pytest 1qrstuvwx

2from sqlalchemy.exc import IntegrityError 1qrstuvwx

3from sqlmodel import Field, Session, SQLModel, create_engine 1qrstuvwx

4 

5 

6def test_nullable_fields(clear_sqlmodel, caplog): 1qrstuvwx

7 class Hero(SQLModel, table=True): 1abcdefgh

8 primary_key: int | None = Field( 1abcdefgh

9 default=None, 

10 primary_key=True, 

11 ) 

12 required_value: str 1abcdefgh

13 optional_default_ellipsis: str | None = Field(default=...) 1abcdefgh

14 optional_default_none: str | None = Field(default=None) 1abcdefgh

15 optional_non_nullable: str | None = Field( 1abcdefgh

16 nullable=False, 

17 ) 

18 optional_nullable: str | None = Field( 1abcdefgh

19 nullable=True, 

20 ) 

21 optional_default_ellipses_non_nullable: str | None = Field( 1abcdefgh

22 default=..., 

23 nullable=False, 

24 ) 

25 optional_default_ellipses_nullable: str | None = Field( 1abcdefgh

26 default=..., 

27 nullable=True, 

28 ) 

29 optional_default_none_non_nullable: str | None = Field( 1abcdefgh

30 default=None, 

31 nullable=False, 

32 ) 

33 optional_default_none_nullable: str | None = Field( 1abcdefgh

34 default=None, 

35 nullable=True, 

36 ) 

37 default_ellipses_non_nullable: str = Field(default=..., nullable=False) 1abcdefgh

38 optional_default_str: str | None = "default" 1abcdefgh

39 optional_default_str_non_nullable: str | None = Field( 1abcdefgh

40 default="default", nullable=False 

41 ) 

42 optional_default_str_nullable: str | None = Field( 1abcdefgh

43 default="default", nullable=True 

44 ) 

45 str_default_str: str = "default" 1abcdefgh

46 str_default_str_non_nullable: str = Field(default="default", nullable=False) 1abcdefgh

47 str_default_str_nullable: str = Field(default="default", nullable=True) 1abcdefgh

48 str_default_ellipsis_non_nullable: str = Field(default=..., nullable=False) 1abcdefgh

49 str_default_ellipsis_nullable: str = Field(default=..., nullable=True) 1abcdefgh

50 

51 engine = create_engine("sqlite://", echo=True) 1abcdefgh

52 SQLModel.metadata.create_all(engine) 1abcdefgh

53 

54 create_table_log = [ 1abcdefgh

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

56 ][0] 

57 assert "primary_key INTEGER NOT NULL," in create_table_log 1abcdefgh

58 assert "required_value VARCHAR NOT NULL," in create_table_log 1abcdefgh

59 assert "optional_default_ellipsis VARCHAR," in create_table_log 1abcdefgh

60 assert "optional_default_none VARCHAR," in create_table_log 1abcdefgh

61 assert "optional_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

62 assert "optional_nullable VARCHAR," in create_table_log 1abcdefgh

63 assert ( 1abcdefgh

64 "optional_default_ellipses_non_nullable VARCHAR NOT NULL," in create_table_log 

65 ) 

66 assert "optional_default_ellipses_nullable VARCHAR," in create_table_log 1abcdefgh

67 assert "optional_default_none_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

68 assert "optional_default_none_nullable VARCHAR," in create_table_log 1abcdefgh

69 assert "default_ellipses_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

70 assert "optional_default_str VARCHAR," in create_table_log 1abcdefgh

71 assert "optional_default_str_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

72 assert "optional_default_str_nullable VARCHAR," in create_table_log 1abcdefgh

73 assert "str_default_str VARCHAR NOT NULL," in create_table_log 1abcdefgh

74 assert "str_default_str_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

75 assert "str_default_str_nullable VARCHAR," in create_table_log 1abcdefgh

76 assert "str_default_ellipsis_non_nullable VARCHAR NOT NULL," in create_table_log 1abcdefgh

77 assert "str_default_ellipsis_nullable VARCHAR," in create_table_log 1abcdefgh

78 

79 

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

81def test_non_nullable_optional_field_with_no_default_set(clear_sqlmodel, caplog): 1qrstuvwx

82 class Hero(SQLModel, table=True): 1ijklmnop

83 primary_key: int | None = Field( 1ijklmnop

84 default=None, 

85 primary_key=True, 

86 ) 

87 

88 optional_non_nullable_no_default: str | None = Field(nullable=False) 1ijklmnop

89 

90 engine = create_engine("sqlite://", echo=True) 1ijklmnop

91 SQLModel.metadata.create_all(engine) 1ijklmnop

92 

93 create_table_log = [ 1ijklmnop

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

95 ][0] 

96 assert "primary_key INTEGER NOT NULL," in create_table_log 1ijklmnop

97 assert "optional_non_nullable_no_default VARCHAR NOT NULL," in create_table_log 1ijklmnop

98 

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

100 hero = Hero(primary_key=123, optional_non_nullable_no_default=None) 1ijklmnop

101 # But we cannot commit it. 

102 with Session(engine) as session: 1ijklmnop

103 session.add(hero) 1ijklmnop

104 with pytest.raises(IntegrityError): 1ijklmnop

105 session.commit() 1ijklmnop

106 

107 

108def test_nullable_primary_key(clear_sqlmodel, caplog): 1qrstuvwx

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

110 class Hero(SQLModel, table=True): 1yzABCDEF

111 nullable_integer_primary_key: int | None = Field( 1yzABCDEF

112 default=None, 

113 primary_key=True, 

114 nullable=True, 

115 ) 

116 

117 engine = create_engine("sqlite://", echo=True) 1yzABCDEF

118 SQLModel.metadata.create_all(engine) 1yzABCDEF

119 

120 create_table_log = [ 1yzABCDEF

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

122 ][0] 

123 assert "nullable_integer_primary_key INTEGER," in create_table_log 1yzABCDEF