Repository: gabrieldemarmiesse/python-on-whales (language: python)

Issue:
compose run
Hello,
I know that a number of compose commands are still not implemented and I can imagine that you have your own priority list about them, but in case of tie can I ask you to push the run command higher in the ranking? :smile: 

I think that compose run is the last command that I need to complete the migration of all my use cases to python on whales, so the implementation of that command would be very appealing to me, but as usual I don't want to bother you with many requests, also considering that I have a number of other issues still open... so please consider this as a low priority request

Thank you again!


Discussion / hints:
What kind of options do you usually use with this command? That would help me put a priority on the options to implement the most useful first
This is the list extracted by all my use cases:
```
[COMMAND] [ARGS...]
--detach
--publish
--name
--no-deps
--rm
--user
```
Thank you, I'll focus on those first!
Thank to you!
As reported in the PR comments the flag on compose run is --rm,
so `full_cmd.add_flag("--remove", remove)` should replaced with `full_cmd.add_flag("--rm", remove)`

The flag is therefore currently broken

```
The docker command executed was `/usr/local/bin/docker compose ....... --remove ....`.
It returned with code 16
The content of stdout is ''
The content of stderr is 'unknown flag: --remove
```
I was sure I pushed the commit. It seems not. I'll fix it in the next PR
Sorry to bother you, but can I ask to release the remove fix? Or before that you are waiting to complete the interactive part of compose.run?
thank you for reminding me. I'll make a release right now.
From what I remember this issue is still open due to the missing stream and interactive options. We discussed about that in the PR https://github.com/gabrieldemarmiesse/python-on-whales/pull/251 but since it is closed the comments are not very visible. I hope to do something useful in reporting here:

For simple-noninteractve commands everything works well

```
>>> print(docker.compose.run(service="my_container", command=["ls"]))
a
b
c
```

For long-lasting commands the lack of a stream brings to a bad user experience. Let's image to the execute a script like:

test.sh
```
for i in $(seq 1 10); 
do 
	echo $i; 
	sleep 1
done
```

The expected output would be

1
... 1 second
2
... 1 second
3
... 1 second

and so on

but without the stream the result will be

... 10 seconds...
1
2
3
...

Interactive commands are not supported at all, but probably the some solution applied in #250 may work here too


Fix patch:
diff --git a/python_on_whales/components/compose/cli_wrapper.py b/python_on_whales/components/compose/cli_wrapper.py
index a5452b14..5d28a1ea 100644
--- a/python_on_whales/components/compose/cli_wrapper.py
+++ b/python_on_whales/components/compose/cli_wrapper.py
@@ -8,7 +8,7 @@
 import python_on_whales.components.container.cli_wrapper
 from python_on_whales.client_config import DockerCLICaller
 from python_on_whales.components.compose.models import ComposeConfig
-from python_on_whales.utils import run, to_list
+from python_on_whales.utils import run, stream_stdout_and_stderr, to_list
 
 
 class ComposeCLI(DockerCLICaller):
@@ -241,6 +241,7 @@ def run(
         # labels: Dict[str, str] = {},
         name: Optional[str] = None,
         tty: bool = True,
+        stream: bool = False,
         dependencies: bool = True,
         publish: List[
             python_on_whales.components.container.cli_wrapper.ValidPortMapping
@@ -252,7 +253,46 @@ def run(
         # volumes: bool = "todo",
         workdir: Union[None, str, Path] = None,
     ) -> Union[str, python_on_whales.components.container.cli_wrapper.Container]:
-        """Not yet implemented"""
+        """Run a one-off command on a service.
+
+        # Arguments
+            service: The name of the service.
+            command: The command to execute.
+            detach: if `True`, returns immediately with the Container.
+                    If `False`, returns the command stdout as string.
+            name: Assign a name to the container.
+            dependencies: Also start linked services.
+            publish: Publish a container's port(s) to the host.
+            service_ports: Enable service's ports and map them to the host.
+            remove: Automatically remove the container when it exits.
+            use_aliases: Use the service's network aliases in the connected network(s).
+            tty: Allocate a pseudo-TTY. Allow the process to access your terminal
+                to write on it.
+            stream: Similar to `docker.run(..., stream=True)`.
+            user: Username or UID, format: `"<name|uid>[:<group|gid>]"`
+            workdir: Working directory inside the container
+
+        # Returns:
+            Optional[str]
+
+        """
+
+        if tty and stream:
+            raise ValueError(
+                "You can't set tty=True and stream=True at the same"
+                "time. Their purpose are not compatible. Try setting tty=False in docker.compose.run"
+            )
+
+        if detach and stream:
+            raise ValueError(
+                "You can't detach and stream at the same time. It's not compatible."
+            )
+
+        if detach and tty:
+            raise ValueError(
+                "You can't detach and set tty=True at the same time. It's not compatible. "
+                "Try setting tty=False in docker.compose.run(...)."
+            )
         full_cmd = self.docker_compose_cmd + ["run"]
         full_cmd.add_flag("--detach", detach)
         full_cmd.add_simple_arg("--name", name)
@@ -275,12 +315,15 @@ def run(
         full_cmd.append(service)
         full_cmd += command
 
-        result = run(full_cmd)
-        if detach:
-            Container = python_on_whales.components.container.cli_wrapper.Container
-            return Container(self.client_config, result, is_immutable_id=True)
+        if stream:
+            return stream_stdout_and_stderr(full_cmd)
         else:
-            return result
+            result = run(full_cmd, tty=tty)
+            if detach:
+                Container = python_on_whales.components.container.cli_wrapper.Container
+                return Container(self.client_config, result, is_immutable_id=True)
+            else:
+                return result
 
     def start(self, services: Union[str, List[str]] = []):
         """Start the specified services.


Test patch:
diff --git a/tests/python_on_whales/components/test_compose.py b/tests/python_on_whales/components/test_compose.py
index 10595909..9c610ea1 100644
--- a/tests/python_on_whales/components/test_compose.py
+++ b/tests/python_on_whales/components/test_compose.py
@@ -320,12 +320,12 @@ def test_load_json(json_file):
 
 
 def test_compose_run_simple():
-    result = docker.compose.run("alpine", ["echo", "dodo"], remove=True)
+    result = docker.compose.run("alpine", ["echo", "dodo"], remove=True, tty=False)
     assert result == "dodo"
 
 
 def test_compose_run_detach():
-    container = docker.compose.run("alpine", ["echo", "dodo"], detach=True)
+    container = docker.compose.run("alpine", ["echo", "dodo"], detach=True, tty=False)
 
     time.sleep(0.1)
     assert not container.state.running