[docs] 19classContextNotExistError(ValueError): 20""" 21 The given Docker context does not exist. 22 23 This should only happen if your Docker client is misconfigured. 24 """
25 26 27def_docker_3190_workaround(): 28""" 29 Work around for https://github.com/docker/docker-py/issues/3190 30 """ 31ifdocker.utils.config.find_config_file()isNone: 32# TODO: Prefer .config_path_from_environment() over .home_dir() 33config_path=( 34Path(docker.utils.config.home_dir()) 35/docker.utils.config.DOCKER_CONFIG_FILENAME 36) 37 38ifconfig_path.parent.exists(): 39# If .docker doesn't exist, it doesn't contain contexts 40config_path.touch() 41 42 43@functools.cache 44def_get_docker_client(use:str|None=None)->docker.DockerClient: 45""" 46 Get a docker client for the given docker context. 47 48 Unlike docker.from_env(), this considers the user's configured context. 49 """ 50_docker_3190_workaround() 51 52context=docker.ContextAPI.get_context(use) 53ifcontextisNone: 54raiseContextNotExistError(f"Docker context {use!r} not found") 55returndocker.DockerClient( 56base_url=context.endpoints["docker"]["Host"],tls=context.TLSConfig 57) 58 59 60# https://stackoverflow.com/a/45690594 61def_find_free_port(): 62withcontextlib.closing(socket.socket(socket.AF_INET,socket.SOCK_STREAM))ass: 63s.bind(("",0)) 64s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 65returns.getsockname()[1] 66 67
[docs] 68@contextlib.contextmanager 69defspawn_docker_couchdb()->typing.Iterator[str]: 70""" 71 Creates a tempory CouchDB instance using Docker, and automatically cleans it up. 72 73 Returns the URL by which it's accessible. 74 """ 75client=_get_docker_client() 76 77couch_container=client.containers.run( 78detach=True, 79image="ghcr.io/teahouse-hosting/quick-and-dirty-couch:latest", 80auto_remove=True, 81environment={ 82"COUCHDB_USER":"admin", 83"COUCHDB_PASSWORD":"admin", 84}, 85ports={"5984/tcp":None}, 86) 87# TODO: Stream container stdout 88 89port_config=None 90whileport_configisNone: 91couch_container.reload() 92try: 93# Dig out the connected port 94port_config=couch_container.attrs["NetworkSettings"]["Ports"]["5984/tcp"][ 950 96] 97exceptIndexError: 98time.sleep(0.1) 99100couch_ip=port_config["HostIp"]101ifcouch_ip=="0.0.0.0":102couch_ip="127.0.0.1"103elifcouch_ip=="::":104couch_ip="::1"105couch_port=port_config["HostPort"]106107try:108yieldf"http://admin:admin@{couch_ip}:{couch_port}/"109finally:110couch_container.stop()
111112
[docs]113asyncdefwait_for_readiness(couch_url:str,*,timeout:float=60)->None:114"""115 Waits for a CouchDB instance to actually initialize and be ready to accept116 requests.117118 Raises TimeoutError on failure.119 """120timeout_increment=0.1121url=httpx.URL(couch_url).join("_up")122client=httpx.AsyncClient()123for_inrange(int(timeout/timeout_increment)):124try:125resp=awaitclient.get(url)126excepthttpx.RequestError:127awaitanyio.sleep(timeout_increment)128else:129ifresp.is_success:130return131else:132awaitanyio.sleep(timeout_increment)133else:134raiseTimeoutError(f"Timeout waiting for CouchDB to initialize ({couch_url})")
[docs]148@contextlib.asynccontextmanager149asyncdefrun_cli_apply(couch_url:str,dbs_module:str):150"""151 Context manager that runs the apply CLI command, and then cleans up152 databases afterwards.153 """154await_call_cli(couch_url,"apply",dbs_module)155yield156# TODO: Look up what databases were actually defined157fromchaise.cli.clientimportConstantPool158159session=awaitConstantPool(couch_url).session()160dbs={dbasyncfordbinsession.iter_dbs()ifnotdb.startswith("_")}161fordbindbs:162awaitsession.delete_db(db)