diff --git a/remotespark/livyclientlib/livyclient.py b/remotespark/livyclientlib/livyclient.py
index eab5eed..6d56323 100644
--- a/remotespark/livyclientlib/livyclient.py
+++ b/remotespark/livyclientlib/livyclient.py
@@ -22,6 +22,12 @@ class LivyClient(object):
     def serialize(self):
         return self._session.get_state().to_dict()
 
+    def get_logs(self):
+        try:
+            return True, self._session.logs
+        except ValueError as err:
+            return False, "{}".format(err)
+
     def execute(self, commands):
         self._session.wait_for_idle(self._execute_timeout_seconds)
         return self._session.execute(commands)
diff --git a/remotespark/livyclientlib/livysession.py b/remotespark/livyclientlib/livysession.py
index 9993ad6..73ddcb3 100644
--- a/remotespark/livyclientlib/livysession.py
+++ b/remotespark/livyclientlib/livysession.py
@@ -112,16 +112,10 @@ class LivySession(object):
     def kind(self):
         return self._state.kind
 
-    def refresh_status(self):
-        (status, logs) = self._get_latest_status_and_logs()
-
-        if status in Constants.possible_session_status:
-            self._status = status
-            self._logs = logs
-        else:
-            raise ValueError("Status '{}' not supported by session.".format(status))
-
-        return self._status
+    @property
+    def logs(self):
+        self._refresh_logs()
+        return self._logs
 
     @property
     def http_client(self):
@@ -158,14 +152,14 @@ class LivySession(object):
         Parameters:
             seconds_to_wait : number of seconds to wait before giving up.
         """
-        self.refresh_status()
+        self._refresh_status()
         current_status = self._status
         if current_status == Constants.idle_session_status:
             return
 
         if current_status in Constants.final_status:
             error = "Session {} unexpectedly reached final status {}. See logs:\n{}"\
-                .format(self.id, current_status, "\n".join(self._logs))
+                .format(self.id, current_status, "\n".join(self.logs))
             self.logger.error(error)
             raise LivyUnexpectedStatusError(error)
 
@@ -185,18 +179,31 @@ class LivySession(object):
     def _statements_url(self):
         return "/sessions/{}/statements".format(self.id)
 
-    def _get_latest_status_and_logs(self):
-        """Get current session state. Network call."""
-        r = self._http_client.get("/sessions", [200])
-        sessions = r.json()["sessions"]
-        filtered_sessions = [s for s in sessions if s["id"] == int(self.id)]
+    def _refresh_status(self):
+        status = self._get_latest_status()
+
+        if status in Constants.possible_session_status:
+            self._status = status
+        else:
+            raise ValueError("Status '{}' not supported by session.".format(status))
+
+        return self._status
+
+    def _refresh_logs(self):
+        self._logs = self._get_latest_logs()
+
+    def _get_latest_status(self):
+        r = self._http_client.get("/sessions/{}".format(self.id), [200])
+        session = r.json()
                     
-        if len(filtered_sessions) != 1:
-            raise ValueError("Expected one session of id {} and got {} sessions."
-                             .format(self.id, len(filtered_sessions)))
-            
-        session = filtered_sessions[0]
-        return session['state'], session['log']
+        return session['state']
+
+    def _get_latest_logs(self):
+        r = self._http_client.get("/sessions/{}/log?from=0".format(self.id), [200])
+        log_array = r.json()['log']
+        logs = "\n".join(log_array)
+
+        return logs
     
     def _get_statement_output(self, statement_id):
         statement_running = True
diff --git a/remotespark/livyclientlib/sparkcontroller.py b/remotespark/livyclientlib/sparkcontroller.py
index 75b4854..df0924a 100644
--- a/remotespark/livyclientlib/sparkcontroller.py
+++ b/remotespark/livyclientlib/sparkcontroller.py
@@ -21,6 +21,10 @@ class SparkController(object):
         else:
             self.client_manager = ClientManager()
 
+    def get_logs(self, client_name=None):
+        client_to_use = self.get_client_by_name_or_default(client_name)
+        return client_to_use.get_logs()
+
     def run_cell(self, cell, client_name=None):
         client_to_use = self.get_client_by_name_or_default(client_name)
         return client_to_use.execute(cell)
@@ -40,7 +44,7 @@ class SparkController(object):
         session_list = [self.client_factory.create_session(self.ipython_display, connection_string, {"kind": s["kind"]}, s["id"])
                         for s in sessions]
         for s in session_list:
-            s.refresh_status()
+            s._refresh_status()
         return session_list
 
     def get_all_sessions_endpoint_info(self, connection_string):
diff --git a/remotespark/remotesparkmagics.py b/remotespark/remotesparkmagics.py
index 1a9687b..c763f16 100644
--- a/remotespark/remotesparkmagics.py
+++ b/remotespark/remotesparkmagics.py
@@ -39,7 +39,7 @@ class RemoteSparkMagics(Magics):
 
                 self.logger.debug("Will serialize to {}.".format(path_to_serialize))
 
-                self.spark_controller = SparkController(serialize_path=path_to_serialize)
+                self.spark_controller = SparkController(self.ipython_display, serialize_path=path_to_serialize)
             else:
                 self.logger.debug("Serialization NOT enabled.")
         except KeyError:
@@ -69,6 +69,7 @@ class RemoteSparkMagics(Magics):
            When the SQL context is used, the result will be a Pandas dataframe of a sample of the results.
 
            If invoked with no subcommand, the cell will be executed against the specified session.
+
            Subcommands
            -----------
            info
@@ -89,11 +90,14 @@ class RemoteSparkMagics(Magics):
                e.g. `%%spark config {"driverMemory":"1000M", "executorCores":4}`
            run
                Run Spark code against a session.
-               e.g. `%%spark -e testsession` will execute the cell code against the testsession previously created
-               e.g. `%%spark -e testsession -c sql` will execute the SQL code against the testsession previously created
-               e.g. `%%spark -e testsession -c sql -o my_var` will execute the SQL code against the testsession
+               e.g. `%%spark -s testsession` will execute the cell code against the testsession previously created
+               e.g. `%%spark -s testsession -c sql` will execute the SQL code against the testsession previously created
+               e.g. `%%spark -s testsession -c sql -o my_var` will execute the SQL code against the testsession
                         previously created and store the pandas dataframe created in the my_var variable in the
                         Python environment.
+           logs
+               Returns the logs for a given session.
+               e.g. `%%spark logs -s testsession` will return the logs for the testsession previously created
            delete
                Delete a Livy session. Argument is the name of the session to be deleted.
                e.g. `%%spark delete defaultlivy`
@@ -107,81 +111,94 @@ class RemoteSparkMagics(Magics):
 
         subcommand = args.command[0].lower()
 
-        # info
-        if subcommand == "info":
-            if len(args.command) == 2:
-                connection_string = args.command[1]
-                info_sessions = self.spark_controller.get_all_sessions_endpoint_info(connection_string)
-                self._print_endpoint_info(info_sessions)
-            elif len(args.command) == 1:
-                self._print_local_info()
-            else:
-                raise ValueError("Subcommand 'info' requires no value or a connection string to show all sessions. "
-                                 "{}".format(usage))
-        # config
-        elif subcommand == "config":
-            # Would normally do " ".join(args.command[1:]) but parse_argstring removes quotes...
-            rest_of_line = user_input[7:]
-            conf.override(conf.session_configs.__name__, json.loads(rest_of_line))
-        # add
-        elif subcommand == "add":
-            if len(args.command) != 4 and len(args.command) != 5:
-                raise ValueError("Subcommand 'add' requires three or four arguments. {}".format(usage))
-
-            name = args.command[1].lower()
-            language = args.command[2].lower()
-            connection_string = args.command[3]
-
-            if len(args.command) == 5:
-                skip = args.command[4].lower() == "skip"
-            else:
-                skip = False
-
-            properties = copy.deepcopy(conf.session_configs())
-            properties["kind"] = self._get_livy_kind(language)
+        try:
+            # info
+            if subcommand == "info":
+                if len(args.command) == 2:
+                    connection_string = args.command[1]
+                    info_sessions = self.spark_controller.get_all_sessions_endpoint_info(connection_string)
+                    self._print_endpoint_info(info_sessions)
+                elif len(args.command) == 1:
+                    self._print_local_info()
+                else:
+                    raise ValueError("Subcommand 'info' requires no value or a connection string to show all sessions.\n"
+                                     "{}".format(usage))
+            # config
+            elif subcommand == "config":
+                # Would normally do " ".join(args.command[1:]) but parse_argstring removes quotes...
+                rest_of_line = user_input[7:]
+                conf.override(conf.session_configs.__name__, json.loads(rest_of_line))
+            # add
+            elif subcommand == "add":
+                if len(args.command) != 4 and len(args.command) != 5:
+                    raise ValueError("Subcommand 'add' requires three or four arguments.\n{}".format(usage))
 
-            self.spark_controller.add_session(name, connection_string, skip, properties)
-        # delete
-        elif subcommand == "delete":
-            if len(args.command) == 2:
                 name = args.command[1].lower()
-                self.spark_controller.delete_session_by_name(name)
-            elif len(args.command) == 3:
-                connection_string = args.command[1]
-                session_id = args.command[2]
-                self.spark_controller.delete_session_by_id(connection_string, session_id)
-            else:
-                raise ValueError("Subcommand 'delete' requires a session name, or a connection string and id. {}"
-                                 .format(usage))
-        # cleanup 
-        elif subcommand == "cleanup":
-            if len(args.command) == 2:
-                connection_string = args.command[1]
-                self.spark_controller.cleanup_endpoint(connection_string)
-            elif len(args.command) == 1:
-                self.spark_controller.cleanup()
-            else:
-                raise ValueError("Subcommand 'cleanup' requires no value or a connection string to clean up sessions. "
-                                 "{}".format(usage))
-        # run
-        elif len(subcommand) == 0:
-            if args.context == Constants.context_name_spark:
-                (success, out) = self.spark_controller.run_cell(cell, args.session)
-                if success:
-                    self.ipython_display.write(out)
+                language = args.command[2].lower()
+                connection_string = args.command[3]
+
+                if len(args.command) == 5:
+                    skip = args.command[4].lower() == "skip"
+                else:
+                    skip = False
+
+                properties = copy.deepcopy(conf.session_configs())
+                properties["kind"] = self._get_livy_kind(language)
+
+                self.spark_controller.add_session(name, connection_string, skip, properties)
+            # delete
+            elif subcommand == "delete":
+                if len(args.command) == 2:
+                    name = args.command[1].lower()
+                    self.spark_controller.delete_session_by_name(name)
+                elif len(args.command) == 3:
+                    connection_string = args.command[1]
+                    session_id = args.command[2]
+                    self.spark_controller.delete_session_by_id(connection_string, session_id)
+                else:
+                    raise ValueError("Subcommand 'delete' requires a session name or a connection string and id.\n{}"
+                                     .format(usage))
+            # cleanup
+            elif subcommand == "cleanup":
+                if len(args.command) == 2:
+                    connection_string = args.command[1]
+                    self.spark_controller.cleanup_endpoint(connection_string)
+                elif len(args.command) == 1:
+                    self.spark_controller.cleanup()
                 else:
-                    self.ipython_display.send_error(out)
-            elif args.context == Constants.context_name_sql:
-                return self._execute_against_context_that_returns_df(self.spark_controller.run_cell_sql, cell,
-                                                                     args.session, args.output)
-            elif args.context == Constants.context_name_hive:
-                return self._execute_against_context_that_returns_df(self.spark_controller.run_cell_hive, cell,
-                                                                     args.session, args.output)
+                    raise ValueError("Subcommand 'cleanup' requires no further values or a connection string to clean up "
+                                     "sessions.\n{}".format(usage))
+            # logs
+            elif subcommand == "logs":
+                if len(args.command) == 1:
+                    (success, out) = self.spark_controller.get_logs(args.session)
+                    if success:
+                        self.ipython_display.write(out)
+                    else:
+                        self.ipython_display.send_error(out)
+                else:
+                    raise ValueError("Subcommand 'logs' requires no further values.\n{}".format(usage))
+            # run
+            elif len(subcommand) == 0:
+                if args.context == Constants.context_name_spark:
+                    (success, out) = self.spark_controller.run_cell(cell, args.session)
+                    if success:
+                        self.ipython_display.write(out)
+                    else:
+                        self.ipython_display.send_error(out)
+                elif args.context == Constants.context_name_sql:
+                    return self._execute_against_context_that_returns_df(self.spark_controller.run_cell_sql, cell,
+                                                                         args.session, args.output)
+                elif args.context == Constants.context_name_hive:
+                    return self._execute_against_context_that_returns_df(self.spark_controller.run_cell_hive, cell,
+                                                                         args.session, args.output)
+                else:
+                    raise ValueError("Context '{}' not found".format(args.context))
+            # error
             else:
-                raise ValueError("Context '{}' not found".format(args.context))
-        # error
-        else:
-            raise ValueError("Subcommand '{}' not found. {}".format(subcommand, usage))
+                raise ValueError("Subcommand '{}' not found. {}".format(subcommand, usage))
+        except ValueError as err:
+            self.ipython_display.send_error("{}".format(err))
 
     def _execute_against_context_that_returns_df(self, method, cell, session, output_var):
         try:
diff --git a/remotespark/sparkkernelbase.py b/remotespark/sparkkernelbase.py
index 626b74c..ab3dd5e 100644
--- a/remotespark/sparkkernelbase.py
+++ b/remotespark/sparkkernelbase.py
@@ -17,6 +17,7 @@ class SparkKernelBase(IPythonKernel):
     info_command = "info"
     delete_command = "delete"
     clean_up_command = "cleanup"
+    logs_command = "logs"
 
     force_flag = "f"
 
@@ -78,10 +79,10 @@ class SparkKernelBase(IPythonKernel):
             if self._session_started:
                 if self.force_flag not in flags:
                     self._show_user_error("A session has already been started. In order to modify the Spark configura"
-                                           "tion, please provide the '-f' flag at the beginning of the config magic:\n"
-                                           "\te.g. `%config -f {}`\n\nNote that this will kill the current session and"
-                                           " will create a new one with the configuration provided. All previously run "
-                                           "commands in the session will be lost.")
+                                          "tion, please provide the '-f' flag at the beginning of the config magic:\n"
+                                          "\te.g. `%config -f {}`\n\nNote that this will kill the current session and"
+                                          " will create a new one with the configuration provided. All previously run "
+                                          "commands in the session will be lost.")
                     code_to_run = ""
                 else:
                     restart_session = True
@@ -118,6 +119,12 @@ class SparkKernelBase(IPythonKernel):
                 code_to_run = "%spark cleanup {}".format(self.connection_string)
 
             return self._run_without_session(code_to_run, silent, store_history, user_expressions, allow_stdin)
+        elif subcommand == self.logs_command:
+            if self._session_started:
+                code_to_run = "%spark logs"
+            else:
+                code_to_run = "print('No logs yet.')"
+            return self._execute_cell(code_to_run, silent, store_history, user_expressions, allow_stdin)
         else:
             self._show_user_error("Magic '{}' not supported.".format(subcommand))
             return self._run_without_session("", silent, store_history, user_expressions, allow_stdin)
@@ -145,7 +152,6 @@ ip.display_formatter.ipython_display_formatter.for_type_by_name('pandas.core.fra
     def _start_session(self):
         if not self._session_started:
             self._session_started = True
-            self._ipython_display.writeln('Starting Livy Session')
 
             add_session_code = "%spark add {} {} {} skip".format(
                 self.client_name, self.session_language, self.connection_string)
@@ -266,4 +272,4 @@ ip.display_formatter.ipython_display_formatter.for_type_by_name('pandas.core.fra
         error = conf.fatal_error_suggestion().format(self._fatal_error)
         self._logger.error(error)
         self._ipython_display.send_error(error)
-        raise ValueError(self._fatal_error)
\ No newline at end of file
+        raise ValueError(self._fatal_error)

Provide %logs for session and in wrapper kernels
This should query `/sessions/ID/log?from=0` and return output nicely formatted.
diff --git a/tests/test_livyclient.py b/tests/test_livyclient.py
index 1a2931a..b0d89c2 100644
--- a/tests/test_livyclient.py
+++ b/tests/test_livyclient.py
@@ -118,3 +118,27 @@ def test_session_id():
     i = client.session_id
 
     assert i == session_id
+
+
+def test_get_logs_returns_session_logs():
+    logs = "hi"
+    mock_spark_session = MagicMock()
+    mock_spark_session.logs = logs
+    client = LivyClient(mock_spark_session)
+
+    res, logs_r = client.get_logs()
+
+    assert res
+    assert logs_r == logs
+
+
+def test_get_logs_returns_false_with_value_error():
+    err = "err"
+    mock_spark_session = MagicMock()
+    type(mock_spark_session).logs = PropertyMock(side_effect=ValueError(err))
+    client = LivyClient(mock_spark_session)
+
+    res, logs_r = client.get_logs()
+
+    assert not res
+    assert logs_r == err
diff --git a/tests/test_livysession.py b/tests/test_livysession.py
index 72d3f58..3e576b3 100644
--- a/tests/test_livysession.py
+++ b/tests/test_livysession.py
@@ -31,14 +31,14 @@ class TestLivySession:
         self.pi_result = "Pi is roughly 3.14336"
 
         self.session_create_json = '{"id":0,"state":"starting","kind":"spark","log":[]}'
-        self.ready_sessions_json = '{"from":0,"total":1,"sessions":[{"id":0,"state":"idle","kind":"spark","log":[""]}]}'
-        self.error_sessions_json = '{"from":0,"total":1,"sessions":[{"id":0,"state":"error","kind":"spark","log":' \
-                                   '[""]}]}'
-        self.busy_sessions_json = '{"from":0,"total":1,"sessions":[{"id":0,"state":"busy","kind":"spark","log":[""]}]}'
+        self.ready_sessions_json = '{"id":0,"state":"idle","kind":"spark","log":[""]}'
+        self.error_sessions_json = '{"id":0,"state":"error","kind":"spark","log":[""]}'
+        self.busy_sessions_json = '{"id":0,"state":"busy","kind":"spark","log":[""]}'
         self.post_statement_json = '{"id":0,"state":"running","output":null}'
         self.running_statement_json = '{"total_statements":1,"statements":[{"id":0,"state":"running","output":null}]}'
         self.ready_statement_json = '{"total_statements":1,"statements":[{"id":0,"state":"available","output":{"statu' \
                                     's":"ok","execution_count":0,"data":{"text/plain":"Pi is roughly 3.14336"}}}]}'
+        self.log_json = '{"id":6,"from":0,"total":212,"log":["hi","hi"]}'
 
         self.get_responses = []
         self.post_responses = []
@@ -217,7 +217,7 @@ class TestLivySession:
         http_client.post.assert_called_with(
             "/sessions", [201], properties)
 
-    def test_status_gets_latest(self):
+    def test_status_gets_latest_status(self):
         http_client = MagicMock()
         http_client.post.return_value = DummyResponse(201, self.session_create_json)
         http_client.get.return_value = DummyResponse(200, self.ready_sessions_json)
@@ -229,11 +229,28 @@ class TestLivySession:
         conf.load()
         session.start()
 
-        session.refresh_status()
+        session._refresh_status()
         state = session._status
 
         assert_equals("idle", state)
-        http_client.get.assert_called_with("/sessions", [200])
+        http_client.get.assert_called_with("/sessions/0", [200])
+
+    def test_logs_gets_latest_logs(self):
+        http_client = MagicMock()
+        http_client.post.return_value = DummyResponse(201, self.session_create_json)
+        http_client.get.return_value = DummyResponse(200, self.log_json)
+        conf.override_all({
+            "status_sleep_seconds": 0.01,
+            "statement_sleep_seconds": 0.01
+        })
+        session = self._create_session(http_client=http_client)
+        conf.load()
+        session.start()
+
+        logs = session.logs
+
+        assert_equals("hi\nhi", logs)
+        http_client.get.assert_called_with("/sessions/0/log?from=0", [200])
 
     def test_wait_for_idle_returns_when_in_state(self):
         http_client = MagicMock()
@@ -253,7 +270,7 @@ class TestLivySession:
 
         session.wait_for_idle(30)
 
-        http_client.get.assert_called_with("/sessions", [200])
+        http_client.get.assert_called_with("/sessions/0", [200])
         assert_equals(2, http_client.get.call_count)
 
     @raises(LivyUnexpectedStatusError)
@@ -262,7 +279,8 @@ class TestLivySession:
         http_client.post.return_value = DummyResponse(201, self.session_create_json)
         self.get_responses = [DummyResponse(200, self.busy_sessions_json),
                               DummyResponse(200, self.busy_sessions_json),
-                              DummyResponse(200, self.error_sessions_json)]
+                              DummyResponse(200, self.error_sessions_json),
+                              DummyResponse(200, self.log_json)]
         http_client.get.side_effect = self._next_response_get
 
         conf.override_all({
diff --git a/tests/test_remotesparkmagics.py b/tests/test_remotesparkmagics.py
index e87e52f..ac9b96c 100644
--- a/tests/test_remotesparkmagics.py
+++ b/tests/test_remotesparkmagics.py
@@ -150,13 +150,15 @@ def test_cleanup_endpoint_command_parses():
     mock_method.assert_called_once_with("conn_str")
 
 
-@raises(ValueError)
 @with_setup(_setup, _teardown)
-def test_bad_command_throws_exception():
+def test_bad_command_writes_error():
     line = "bad_command"
+    usage = "Please look at usage of %spark by executing `%spark?`."
 
     magic.spark(line)
 
+    ipython_display.send_error.assert_called_once_with("Subcommand '{}' not found. {}".format(line, usage))
+
 
 @with_setup(_setup, _teardown)
 def test_run_cell_command_parses():
@@ -302,3 +304,33 @@ def test_run_sql_command_stores_variable_in_user_ns():
 def test_get_livy_kind_covers_all_langs():
     for lang in Constants.lang_supported:
         RemoteSparkMagics._get_livy_kind(lang)
+
+
+@with_setup(_setup, _teardown)
+def test_logs_subcommand():
+    get_logs_method = MagicMock()
+    result_value = ""
+    get_logs_method.return_value = (True, result_value)
+    spark_controller.get_logs = get_logs_method
+
+    command = "logs -s"
+    name = "sessions_name"
+    line = " ".join([command, name])
+    cell = "cell code"
+
+    # Could get results
+    result = magic.spark(line, cell)
+
+    get_logs_method.assert_called_once_with(name)
+    assert result is None
+    ipython_display.write.assert_called_once_with(result_value)
+
+    # Could not get results
+    get_logs_method.reset_mock()
+    get_logs_method.return_value = (False, result_value)
+
+    result = magic.spark(line, cell)
+
+    get_logs_method.assert_called_once_with(name)
+    assert result is None
+    ipython_display.send_error.assert_called_once_with(result_value)
diff --git a/tests/test_sparkcontroller.py b/tests/test_sparkcontroller.py
index c1c97b8..3a8a6ae 100644
--- a/tests/test_sparkcontroller.py
+++ b/tests/test_sparkcontroller.py
@@ -179,3 +179,13 @@ def test_delete_session_by_id_non_existent():
 
     assert len(create_session_method.mock_calls) == 0
     assert len(session.delete.mock_calls) == 0
+
+
+@with_setup(_setup, _teardown)
+def test_get_logs():
+    chosen_client = MagicMock()
+    controller.get_client_by_name_or_default = MagicMock(return_value=chosen_client)
+
+    controller.get_logs()
+
+    chosen_client.get_logs.assert_called_with()
diff --git a/tests/test_sparkkernelbase.py b/tests/test_sparkkernelbase.py
index 02628f3..6bef40b 100644
--- a/tests/test_sparkkernelbase.py
+++ b/tests/test_sparkkernelbase.py
@@ -388,3 +388,21 @@ def test_register_auto_viz():
     assert call("from remotespark.datawidgets.utils import display_dataframe\nip = get_ipython()\nip.display_formatter"
                 ".ipython_display_formatter.for_type_by_name('pandas.core.frame', 'DataFrame', display_dataframe)",
                 True, False, None, False) in execute_cell_mock.mock_calls
+
+
+@with_setup(_setup(), _teardown())
+def test_logs_magic():
+    kernel._session_started = True
+
+    kernel.do_execute("%logs", False)
+
+    assert call("%spark logs", False, True, None, False) in execute_cell_mock.mock_calls
+
+
+@with_setup(_setup(), _teardown())
+def test_logs_magic_prints_without_session():
+    kernel._session_started = False
+
+    kernel.do_execute("%logs", False)
+
+    assert call("print('No logs yet.')", False, True, None, False) in execute_cell_mock.mock_calls

pip install -r requirements.txt -e .
parse_log_pytest
requirements.txt
nose pytest
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
anyio==4.9.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
async-lru==2.0.5
attrs==25.3.0
babel==2.17.0
beautifulsoup4==4.13.3
bleach==6.2.0
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
comm==0.2.2
decorator==5.2.1
defusedxml==0.7.1
exceptiongroup==1.2.2
fastjsonschema==2.21.1
fqdn==1.5.1
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
importlib_metadata==8.6.1
iniconfig==2.1.0
ipykernel==4.2.2
ipython==4.0.2
ipython-genutils==0.2.0
ipywidgets==7.8.5
isoduration==20.11.0
Jinja2==3.1.6
json5==0.10.0
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter-events==0.12.0
jupyter-lsp==2.2.5
jupyter_client==8.6.3
jupyter_core==5.7.2
jupyter_server==2.15.0
jupyter_server_terminals==0.5.3
jupyterlab==4.1.5
jupyterlab_pygments==0.3.0
jupyterlab_server==2.27.3
jupyterlab_widgets==1.1.11
MarkupSafe==3.0.2
mistune==3.1.3
mock==5.2.0
nbclient==0.10.2
nbconvert==7.16.6
nbformat==5.10.4
nose==1.3.7
notebook==7.1.3
notebook_shim==0.2.4
numpy==2.0.2
overrides==7.7.0
packaging==24.2
pandas==2.2.3
pandocfilters==1.5.1
pexpect==4.9.0
pickleshare==0.7.5
platformdirs==4.3.7
plotly==1.9.4
pluggy==1.5.0
prometheus_client==0.21.1
ptyprocess==0.7.0
pycparser==2.22
Pygments==2.19.1
pytest==8.3.5
python-dateutil==2.9.0.post0
python-json-logger==3.3.0
pytz==2025.2
PyYAML==6.0.2
pyzmq==26.3.0
referencing==0.36.2
-e git+https://github.com/jupyter-incubator/sparkmagic.git@ac43b2838efaae766a7071a79699b9b192899dd2#egg=remotespark
requests==2.32.3
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.24.0
Send2Trash==1.8.3
simplegeneric==0.8.1
six==1.17.0
sniffio==1.3.1
soupsieve==2.6
terminado==0.18.1
tinycss2==1.4.0
tomli==2.2.1
tornado==6.4.2
traitlets==5.14.3
types-python-dateutil==2.9.0.20241206
typing_extensions==4.13.0
tzdata==2025.2
uri-template==1.3.0
urllib3==2.3.0
webcolors==24.11.1
webencodings==0.5.1
websocket-client==1.8.0
widgetsnbextension==3.6.10
zipp==3.21.0

name: sparkmagic
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - anyio==4.9.0
      - argon2-cffi==23.1.0
      - argon2-cffi-bindings==21.2.0
      - arrow==1.3.0
      - async-lru==2.0.5
      - attrs==25.3.0
      - babel==2.17.0
      - beautifulsoup4==4.13.3
      - bleach==6.2.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - comm==0.2.2
      - decorator==5.2.1
      - defusedxml==0.7.1
      - exceptiongroup==1.2.2
      - fastjsonschema==2.21.1
      - fqdn==1.5.1
      - h11==0.14.0
      - httpcore==1.0.7
      - httpx==0.28.1
      - idna==3.10
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - ipykernel==4.2.2
      - ipython==4.0.2
      - ipython-genutils==0.2.0
      - ipywidgets==7.8.5
      - isoduration==20.11.0
      - jinja2==3.1.6
      - json5==0.10.0
      - jsonpointer==3.0.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - jupyter-client==8.6.3
      - jupyter-core==5.7.2
      - jupyter-events==0.12.0
      - jupyter-lsp==2.2.5
      - jupyter-server==2.15.0
      - jupyter-server-terminals==0.5.3
      - jupyterlab==4.1.5
      - jupyterlab-pygments==0.3.0
      - jupyterlab-server==2.27.3
      - jupyterlab-widgets==1.1.11
      - markupsafe==3.0.2
      - mistune==3.1.3
      - mock==5.2.0
      - nbclient==0.10.2
      - nbconvert==7.16.6
      - nbformat==5.10.4
      - nose==1.3.7
      - notebook==7.1.3
      - notebook-shim==0.2.4
      - numpy==2.0.2
      - overrides==7.7.0
      - packaging==24.2
      - pandas==2.2.3
      - pandocfilters==1.5.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - platformdirs==4.3.7
      - plotly==1.9.4
      - pluggy==1.5.0
      - prometheus-client==0.21.1
      - ptyprocess==0.7.0
      - pycparser==2.22
      - pygments==2.19.1
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - python-json-logger==3.3.0
      - pytz==2025.2
      - pyyaml==6.0.2
      - pyzmq==26.3.0
      - referencing==0.36.2
      - requests==2.32.3
      - rfc3339-validator==0.1.4
      - rfc3986-validator==0.1.1
      - rpds-py==0.24.0
      - send2trash==1.8.3
      - simplegeneric==0.8.1
      - six==1.17.0
      - sniffio==1.3.1
      - soupsieve==2.6
      - terminado==0.18.1
      - tinycss2==1.4.0
      - tomli==2.2.1
      - tornado==6.4.2
      - traitlets==5.14.3
      - types-python-dateutil==2.9.0.20241206
      - typing-extensions==4.13.0
      - tzdata==2025.2
      - uri-template==1.3.0
      - urllib3==2.3.0
      - webcolors==24.11.1
      - webencodings==0.5.1
      - websocket-client==1.8.0
      - widgetsnbextension==3.6.10
      - zipp==3.21.0
prefix: /opt/conda/envs/sparkmagic

tests/test_livyclient.py::test_get_logs_returns_session_logs tests/test_livyclient.py::test_get_logs_returns_false_with_value_error
tests/test_remotesparkmagics.py::test_info_command_parses tests/test_remotesparkmagics.py::test_info_endpoint_command_parses tests/test_remotesparkmagics.py::test_add_sessions_command_parses tests/test_remotesparkmagics.py::test_add_sessions_command_extra_properties tests/test_remotesparkmagics.py::test_delete_sessions_command_parses tests/test_remotesparkmagics.py::test_cleanup_command_parses tests/test_remotesparkmagics.py::test_cleanup_endpoint_command_parses tests/test_remotesparkmagics.py::test_bad_command_writes_error tests/test_remotesparkmagics.py::test_run_cell_command_parses tests/test_remotesparkmagics.py::test_run_cell_command_writes_to_err tests/test_remotesparkmagics.py::test_run_sql_command_parses tests/test_remotesparkmagics.py::test_run_hive_command_parses tests/test_remotesparkmagics.py::test_run_sql_command_returns_none_when_exception tests/test_remotesparkmagics.py::test_run_hive_command_returns_none_when_exception tests/test_remotesparkmagics.py::test_run_sql_command_stores_variable_in_user_ns tests/test_remotesparkmagics.py::test_logs_subcommand tests/test_sparkcontroller.py::test_add_session tests/test_sparkcontroller.py::test_add_session_skip tests/test_sparkcontroller.py::test_delete_session tests/test_sparkcontroller.py::test_cleanup tests/test_sparkcontroller.py::test_run_cell tests/test_sparkcontroller.py::test_get_client_keys tests/test_sparkcontroller.py::test_get_all_sessions tests/test_sparkcontroller.py::test_cleanup_endpoint tests/test_sparkcontroller.py::test_delete_session_by_id_existent tests/test_sparkcontroller.py::test_delete_session_by_id_non_existent tests/test_sparkcontroller.py::test_get_logs tests/test_sparkkernelbase.py::test_set_config tests/test_sparkkernelbase.py::test_do_execute_initializes_magics_if_not_run tests/test_sparkkernelbase.py::test_magic_not_supported tests/test_sparkkernelbase.py::test_info tests/test_sparkkernelbase.py::test_delete_force tests/test_sparkkernelbase.py::test_delete_not_force tests/test_sparkkernelbase.py::test_cleanup_force tests/test_sparkkernelbase.py::test_cleanup_not_force tests/test_sparkkernelbase.py::test_call_spark tests/test_sparkkernelbase.py::test_execute_throws_if_fatal_error_happened tests/test_sparkkernelbase.py::test_execute_throws_if_fatal_error_happens_for_execution tests/test_sparkkernelbase.py::test_call_spark_sql_new_line tests/test_sparkkernelbase.py::test_call_spark_hive_new_line tests/test_sparkkernelbase.py::test_register_auto_viz tests/test_sparkkernelbase.py::test_logs_magic tests/test_sparkkernelbase.py::test_logs_magic_prints_without_session
tests/test_livyclient.py::test_doesnt_create_sql_context_automatically tests/test_livyclient.py::test_start_creates_sql_context tests/test_livyclient.py::test_execute_code tests/test_livyclient.py::test_execute_sql tests/test_livyclient.py::test_execute_hive tests/test_livyclient.py::test_serialize tests/test_livyclient.py::test_close_session tests/test_livyclient.py::test_kind tests/test_livyclient.py::test_session_id tests/test_remotesparkmagics.py::test_get_livy_kind_covers_all_langs tests/test_sparkkernelbase.py::test_get_config tests/test_sparkkernelbase.py::test_get_config_not_set tests/test_sparkkernelbase.py::test_initialize_magics tests/test_sparkkernelbase.py::test_start_session tests/test_sparkkernelbase.py::test_delete_session tests/test_sparkkernelbase.py::test_shutdown_cleans_up
remotespark/remotesparkmagics.py remotespark/livyclientlib/livyclient.py remotespark/sparkkernelbase.py remotespark/livyclientlib/livysession.py remotespark/livyclientlib/sparkcontroller.py
remotespark/remotesparkmagics.py remotespark/livyclientlib/livyclient.py remotespark/sparkkernelbase.py remotespark/livyclientlib/livysession.py remotespark/livyclientlib/sparkcontroller.py