1
+ import types
2
+ import warnings
3
+ from collections .abc import Mapping
1
4
from dataclasses import dataclass , field
2
5
from enum import Enum
3
6
from logging import warning
4
7
from os import environ
5
8
from os .path import exists
6
9
from pathlib import Path
7
- from typing import Optional , Union
10
+ from typing import Final , Optional , Union
8
11
9
12
import docker
10
13
@@ -30,28 +33,27 @@ def get_docker_socket() -> str:
30
33
31
34
Using the docker api ensure we handle rootless docker properly
32
35
"""
33
- if socket_path := environ .get ("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE" ):
36
+ if socket_path := environ .get ("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE" , "" ):
34
37
return socket_path
35
38
36
- client = docker .from_env ()
37
39
try :
40
+ client = docker .from_env ()
38
41
socket_path = client .api .get_adapter (client .api .base_url ).socket_path
39
42
# return the normalized path as string
40
43
return str (Path (socket_path ).absolute ())
41
- except AttributeError :
44
+ except Exception :
42
45
return "/var/run/docker.sock"
43
46
44
47
45
- MAX_TRIES = int (environ .get ("TC_MAX_TRIES" , 120 ))
46
- SLEEP_TIME = int (environ .get ("TC_POOLING_INTERVAL" , 1 ))
47
- TIMEOUT = MAX_TRIES * SLEEP_TIME
48
+ def get_bool_env (name : str ) -> bool :
49
+ """
50
+ Get environment variable named `name` and convert it to bool.
51
+
52
+ Defaults to False.
53
+ """
54
+ value = environ .get (name , "" )
55
+ return value .lower () in ("yes" , "true" , "t" , "y" , "1" )
48
56
49
- RYUK_IMAGE : str = environ .get ("RYUK_CONTAINER_IMAGE" , "testcontainers/ryuk:0.8.1" )
50
- RYUK_PRIVILEGED : bool = environ .get ("TESTCONTAINERS_RYUK_PRIVILEGED" , "false" ) == "true"
51
- RYUK_DISABLED : bool = environ .get ("TESTCONTAINERS_RYUK_DISABLED" , "false" ) == "true"
52
- RYUK_DOCKER_SOCKET : str = get_docker_socket ()
53
- RYUK_RECONNECTION_TIMEOUT : str = environ .get ("RYUK_RECONNECTION_TIMEOUT" , "10s" )
54
- TC_HOST_OVERRIDE : Optional [str ] = environ .get ("TC_HOST" , environ .get ("TESTCONTAINERS_HOST_OVERRIDE" ))
55
57
56
58
TC_FILE = ".testcontainers.properties"
57
59
TC_GLOBAL = Path .home () / TC_FILE
@@ -94,16 +96,16 @@ def read_tc_properties() -> dict[str, str]:
94
96
95
97
@dataclass
96
98
class TestcontainersConfiguration :
97
- max_tries : int = MAX_TRIES
98
- sleep_time : int = SLEEP_TIME
99
- ryuk_image : str = RYUK_IMAGE
100
- ryuk_privileged : bool = RYUK_PRIVILEGED
101
- ryuk_disabled : bool = RYUK_DISABLED
102
- ryuk_docker_socket : str = RYUK_DOCKER_SOCKET
103
- ryuk_reconnection_timeout : str = RYUK_RECONNECTION_TIMEOUT
99
+ max_tries : int = int ( environ . get ( "TC_MAX_TRIES" , "120" ))
100
+ sleep_time : int = int ( environ . get ( "TC_POOLING_INTERVAL" , "1" ))
101
+ ryuk_image : str = environ . get ( "RYUK_CONTAINER_IMAGE" , "testcontainers/ryuk:0.8.1" )
102
+ ryuk_privileged : bool = get_bool_env ( "TESTCONTAINERS_RYUK_PRIVILEGED" )
103
+ ryuk_disabled : bool = get_bool_env ( "TESTCONTAINERS_RYUK_DISABLED" )
104
+ _ryuk_docker_socket : str = ""
105
+ ryuk_reconnection_timeout : str = environ . get ( " RYUK_RECONNECTION_TIMEOUT" , "10s" )
104
106
tc_properties : dict [str , str ] = field (default_factory = read_tc_properties )
105
107
_docker_auth_config : Optional [str ] = field (default_factory = lambda : environ .get ("DOCKER_AUTH_CONFIG" ))
106
- tc_host_override : Optional [str ] = TC_HOST_OVERRIDE
108
+ tc_host_override : Optional [str ] = environ . get ( "TC_HOST" , environ . get ( "TESTCONTAINERS_HOST_OVERRIDE" ))
107
109
connection_mode_override : Optional [ConnectionMode ] = field (default_factory = get_user_overwritten_connection_mode )
108
110
109
111
"""
@@ -131,19 +133,54 @@ def tc_properties_get_tc_host(self) -> Union[str, None]:
131
133
def timeout (self ) -> int :
132
134
return self .max_tries * self .sleep_time
133
135
136
+ @property
137
+ def ryuk_docker_socket (self ) -> str :
138
+ if not self ._ryuk_docker_socket :
139
+ self .ryuk_docker_socket = get_docker_socket ()
140
+ return self ._ryuk_docker_socket
134
141
135
- testcontainers_config = TestcontainersConfiguration ()
142
+ @ryuk_docker_socket .setter
143
+ def ryuk_docker_socket (self , value : str ) -> None :
144
+ self ._ryuk_docker_socket = value
145
+
146
+
147
+ testcontainers_config : Final = TestcontainersConfiguration ()
136
148
137
149
__all__ = [
138
- # Legacy things that are deprecated:
139
- "MAX_TRIES" ,
140
- "RYUK_DISABLED" ,
141
- "RYUK_DOCKER_SOCKET" ,
142
- "RYUK_IMAGE" ,
143
- "RYUK_PRIVILEGED" ,
144
- "RYUK_RECONNECTION_TIMEOUT" ,
145
- "SLEEP_TIME" ,
146
- "TIMEOUT" ,
147
150
# Public API of this module:
148
151
"testcontainers_config" ,
149
152
]
153
+
154
+ _deprecated_attribute_mapping : Final [Mapping [str , str ]] = types .MappingProxyType (
155
+ {
156
+ "MAX_TRIES" : "max_tries" ,
157
+ "RYUK_DISABLED" : "ryuk_disabled" ,
158
+ "RYUK_DOCKER_SOCKET" : "ryuk_docker_socket" ,
159
+ "RYUK_IMAGE" : "ryuk_image" ,
160
+ "RYUK_PRIVILEGED" : "ryuk_privileged" ,
161
+ "RYUK_RECONNECTION_TIMEOUT" : "ryuk_reconnection_timeout" ,
162
+ "SLEEP_TIME" : "sleep_time" ,
163
+ "TIMEOUT" : "timeout" ,
164
+ }
165
+ )
166
+
167
+
168
+ def __dir__ () -> list [str ]:
169
+ return __all__ + list (_deprecated_attribute_mapping .keys ())
170
+
171
+
172
+ def __getattr__ (name : str ) -> object :
173
+ """
174
+ Allow getting deprecated legacy settings.
175
+ """
176
+ module = f"{ __name__ !r} "
177
+
178
+ if name in _deprecated_attribute_mapping :
179
+ attrib = _deprecated_attribute_mapping [name ]
180
+ warnings .warn (
181
+ f"{ module } .{ name } is deprecated. Use { module } .testcontainers_config.{ attrib } instead." ,
182
+ DeprecationWarning ,
183
+ stacklevel = 2 ,
184
+ )
185
+ return getattr (testcontainers_config , attrib )
186
+ raise AttributeError (f"module { module } has no attribute { name !r} " )
0 commit comments