From b350ae1cf9e311fdbf0cdec8c491b2b4450f2241 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Mon, 24 Mar 2025 17:39:21 -0300
Subject: [PATCH 01/22] Removed pd.io.sql.execute calls (deprecated).
---
mysql_kernel/kernel.py | 64 ++++++++++++++++++++++++++++++++++++------
1 file changed, 56 insertions(+), 8 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 3370920..874d591 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -1,6 +1,7 @@
import pandas as pd
import sqlalchemy as sa
from ipykernel.kernelbase import Kernel
+import re
__version__ = '0.4.1'
@@ -36,6 +37,51 @@ def err(self, msg):
'execution_count':self.execution_count,
'payload':[],
'user_expressions':{}}
+
+ def generic_ddl(self, query, msg):
+ try:
+ with self.engine.connect() as con:
+ result = con.execute(sa.sql.text(query))
+ con.commit()
+ split_query = query.split()
+ if len(split_query) > 2:
+ object_name = re.match("([^ ]+ ){2}(if (not )?exists )?([^ ]+)", query, re.IGNORECASE).group(4)
+ else:
+ object_name = query.split()[1]
+ rows_affected = result.rowcount
+ if result.rowcount > 0:
+ self.output((msg + '\nRows affected: %d.') % (object_name, rows_affected))
+ else:
+ self.output(msg % (object_name))
+ return
+ except Exception as msg:
+ self.output(str(msg))
+ return
+
+ def create_db(self, query):
+ self.generic_ddl(query, 'Database %s created successfully.')
+
+
+ def drop_db(self, query):
+ self.generic_ddl(query, 'Database %s dropped successfully.')
+
+ def create_table(self, query):
+ self.generic_ddl(query, 'Table %s created successfully.')
+
+ def drop_table(self, query):
+ self.generic_ddl(query, 'Table %s dropped successfully.')
+
+ def delete(self, query):
+ self.generic_ddl(query, 'Data deleted from %s successfully.')
+
+ def alter_table(self, query):
+ self.generic_ddl(query, 'Table %s altered successfully.')
+
+ def insert_into(self, query):
+ self.generic_ddl(query, 'Data inserted into %s successfully.')
+
+ def use_db(self, query):
+ self.generic_ddl(query, 'Changed to database %s successfully.')
def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False):
self.silent = silent
@@ -54,26 +100,28 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
else:
self.engine = sa.create_engine(f'mysql+py{v}')
elif l.startswith('create database '):
- pd.io.sql.execute(v, con=self.engine)
+ self.create_db(v)
elif l.startswith('drop database '):
- pd.io.sql.execute(v, con=self.engine)
+ self.drop_db(v)
elif l.startswith('create table '):
- pd.io.sql.execute(v, con=self.engine)
+ self.create_table(v)
elif l.startswith('drop table '):
- pd.io.sql.execute(v, con=self.engine)
+ self.drop_table(v)
elif l.startswith('delete '):
- pd.io.sql.execute(v, con=self.engine)
+ self.delete(v)
elif l.startswith('alter table '):
- pd.io.sql.execute(v, con=self.engine)
+ self.alter_table(v)
+ elif l.startswith('use '):
+ self.use_db(v)
elif l.startswith('insert into '):
- pd.io.sql.execute(v, con=self.engine)
+ self.insert_into(v)
else:
if self.engine:
if ' like ' in l:
if l[l.find(' like ')+6:].count('%')<4:
self.output("sql code ' like %xx%' should be replace ' like %%xx%%'.")
return self.ok()
- if l.startswith('select ') and ' limit ' not in l:
+ if l.startswith('select ') and 'limit ' not in l:
output = pd.read_sql(f'{v} limit 1000', self.engine).to_html()
else:
output = pd.read_sql(v, self.engine).to_html()
From 35210218918ce0739c599ae053f7c8a481b374f8 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Tue, 25 Mar 2025 14:14:24 -0300
Subject: [PATCH 02/22] Update kernel.py
Fix % sign in cell and maximum cell height with scroll
---
mysql_kernel/kernel.py | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 874d591..f8edc27 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -98,6 +98,8 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
if l.count('@')>1:
self.output("Connection failed, The Mysql address cannot have two '@'.")
else:
+ print(l)
+ self.output(l)
self.engine = sa.create_engine(f'mysql+py{v}')
elif l.startswith('create database '):
self.create_db(v)
@@ -117,14 +119,20 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
self.insert_into(v)
else:
if self.engine:
- if ' like ' in l:
- if l[l.find(' like ')+6:].count('%')<4:
- self.output("sql code ' like %xx%' should be replace ' like %%xx%%'.")
- return self.ok()
+ v = re.sub('(?Results limitted to 1000 (explicitly add LIMIT to display beyond that)
+ {results.to_html()}
+ '''
+ else:
+ output = results.to_html()
else:
output = pd.read_sql(v, self.engine).to_html()
+ output = f'''{output}
'''
else:
output = 'Unable to connect to Mysql server. Check that the server is running.'
self.output(output)
From 85cb65e63eafcc4327c2e9b19230adb6faf4af52 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 26 Mar 2025 11:46:53 -0300
Subject: [PATCH 03/22] Ignore comments, spaces, \r\n\t
---
mysql_kernel/kernel.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index f8edc27..73f12d7 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -42,7 +42,8 @@ def generic_ddl(self, query, msg):
try:
with self.engine.connect() as con:
result = con.execute(sa.sql.text(query))
- con.commit()
+ if callable(getattr(con, 'commit', None)):
+ con.commit()
split_query = query.split()
if len(split_query) > 2:
object_name = re.match("([^ ]+ ){2}(if (not )?exists )?([^ ]+)", query, re.IGNORECASE).group(4)
@@ -92,15 +93,16 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
try:
for v in sql.split(";"):
v = v.rstrip()
+ v = re.sub('^[ \r\n\t]+', '', v)
+ v = re.sub('\n *--.*\n', '', v) # remove comments
l = v.lower()
if len(l)>0:
if l.startswith('mysql://'):
if l.count('@')>1:
self.output("Connection failed, The Mysql address cannot have two '@'.")
else:
- print(l)
- self.output(l)
self.engine = sa.create_engine(f'mysql+py{v}')
+ self.output('Connected successfully!')
elif l.startswith('create database '):
self.create_db(v)
elif l.startswith('drop database '):
@@ -135,7 +137,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
output = f'''{output}
'''
else:
output = 'Unable to connect to Mysql server. Check that the server is running.'
- self.output(output)
+ self.output(output)
return self.ok()
except Exception as msg:
self.output(str(msg))
From 42cdca619b522814d8da9f915d911e4379abdbdf Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 26 Mar 2025 11:49:41 -0300
Subject: [PATCH 04/22] Ignore comment in the beggining
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 73f12d7..b1d1012 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -94,7 +94,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
for v in sql.split(";"):
v = v.rstrip()
v = re.sub('^[ \r\n\t]+', '', v)
- v = re.sub('\n *--.*\n', '', v) # remove comments
+ v = re.sub('\n* *--.*\n', '', v) # remove comments
l = v.lower()
if len(l)>0:
if l.startswith('mysql://'):
From b8abc10325ce96a90d8e22e6dc7e306c6a995a05 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 26 Mar 2025 12:15:56 -0300
Subject: [PATCH 05/22] Use engine.begin instead of connect
---
mysql_kernel/kernel.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index b1d1012..cbde18d 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -40,10 +40,8 @@ def err(self, msg):
def generic_ddl(self, query, msg):
try:
- with self.engine.connect() as con:
+ with self.engine.begin() as con:
result = con.execute(sa.sql.text(query))
- if callable(getattr(con, 'commit', None)):
- con.commit()
split_query = query.split()
if len(split_query) > 2:
object_name = re.match("([^ ]+ ){2}(if (not )?exists )?([^ ]+)", query, re.IGNORECASE).group(4)
From 2411f464013d9df59e77252659fbe5f439dd44a3 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 1 Apr 2025 23:37:47 -0300
Subject: [PATCH 06/22] Fixed kernel
---
.gitignore | 5 +
mysql_kernel/autocomplete.py | 207 ++++++++++++++++++++++++++++
mysql_kernel/kernel.py | 159 +++++++++++++++++----
mysql_kernel/pygment_error_lexer.py | 23 ++++
mysql_kernel/style.py | 82 +++++++++++
5 files changed, 449 insertions(+), 27 deletions(-)
create mode 100644 .gitignore
create mode 100644 mysql_kernel/autocomplete.py
create mode 100644 mysql_kernel/pygment_error_lexer.py
create mode 100644 mysql_kernel/style.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1e14f7c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+build
+dist
+.ipynb_checkpoints
+*.egg-info/
+**/__pycache__
\ No newline at end of file
diff --git a/mysql_kernel/autocomplete.py b/mysql_kernel/autocomplete.py
new file mode 100644
index 0000000..bbc2985
--- /dev/null
+++ b/mysql_kernel/autocomplete.py
@@ -0,0 +1,207 @@
+import re
+from sqlalchemy import inspect
+
+class SQLAutocompleter:
+ def __init__(self, engine, log):
+ """
+ Initializes the autocompleter with an SQLAlchemy engine.
+
+ Parameters:
+ - engine: SQLAlchemy engine connected to a database.
+ """
+ self.engine = engine
+ self.inspector = inspect(engine)
+ self.default_schema = self.inspector.default_schema_name
+ self.log = log
+ self.log.info(f"Autocompleter initialized with engine: {engine}")
+
+ def get_real_previous_keyword(self, tokens):
+ """
+ Identifies the real previous keyword in SQL syntax, i.e., section delimiters like `SELECT`, `FROM`, `WHERE`.
+
+ Parameters:
+ - tokens (list): List of tokens (words) before the cursor position.
+
+ Returns:
+ - str: The most recent real SQL keyword (e.g., `SELECT`, `FROM`).
+ """
+ sql_keywords = {
+ "SELECT", "FROM", "WHERE", "GROUP", "ORDER", "HAVING", "INSERT", "UPDATE", "DELETE",
+ "JOIN", "ON", "LIMIT", "DISTINCT", "SET"
+ }
+ for token in reversed(tokens):
+ if token.upper() in sql_keywords:
+ return token.upper()
+ return ""
+
+
+ def get_completions(self, code, cursor_pos):
+ """
+ Returns autocompletions based on the SQL context.
+
+ Parameters:
+ - code (str): Full SQL query being typed.
+ - cursor_pos (int): Cursor position in the query.
+
+ Returns:
+ - list: Suggested completions.
+ """
+ preceding_text = code[:cursor_pos]
+ tokens = re.findall(r"[^ ;\(\)\r\n\t,]+", preceding_text, re.IGNORECASE)
+ previous_keyword = self.get_real_previous_keyword(tokens)
+ previous_word = tokens[-1].upper() if tokens else ""
+ is_preceding_comma = preceding_text.rstrip().endswith(",")
+ is_preceding_space = preceding_text.endswith(" ")
+ is_completing_word = preceding_text[-1].isalpha()
+ current_completing = ''
+ if is_completing_word:
+ previous_word = tokens[-2].upper() if len(tokens) > 1 else ""
+ current_completing = tokens[-1].upper() if tokens else ""
+
+ if previous_keyword == "SELECT":
+ if is_preceding_comma == False and is_preceding_space == True and previous_word != "SELECT":
+ completions = ["FROM"]
+ else:
+ completions = self.get_columns(code) + self.get_functions()
+ elif previous_keyword in {"FROM", "JOIN"}:
+ completions = self.get_tables()
+ elif previous_keyword == "WHERE":
+ completions = self.get_columns(code) + self.get_functions()
+ elif previous_word == "GROUP":
+ completions = ["BY"]
+ elif previous_word == "ORDER":
+ completions = ["BY"]
+ elif previous_word == "INSERT":
+ completions = ["INTO"]
+ elif previous_word == "UPDATE":
+ completions = self.get_tables()
+ elif previous_keyword == 'UPDATE':
+ completions = ["SET"]
+ completions += self.get_tables()
+ elif previous_word == "DELETE":
+ completions = ["FROM"]
+ elif previous_word == "DISTINCT":
+ completions = self.get_columns(code)
+ elif previous_keyword == "DISTINCT":
+ completions = self.get_columns(code) + self.get_functions()
+ elif previous_keyword in {"GROUP", "ORDER"}:
+ completions = self.get_columns(code)
+ elif previous_keyword == "HAVING":
+ completions = self.get_columns(code) + self.get_functions()
+ elif previous_keyword == "SET":
+ completions = self.get_columns(code)
+ elif previous_word == "VALUES":
+ completions = '('
+ elif previous_keyword == "VALUES":
+ completions = self.get_columns(code)
+ elif previous_word in {"INNER", "LEFT", "RIGHT", "FULL"}:
+ completions = ["JOIN"]
+ elif previous_keyword == "DISTINCT" or previous_keyword == "LIMIT" or previous_keyword == "OFFSET":
+ completions = []
+ else:
+ completions = self.get_sql_keywords()
+
+ if is_completing_word:
+ filter_func = lambda x: x.lower().startswith(current_completing.lower())
+ if is_preceding_comma == False and is_preceding_space == False:
+ filtered_suggestions = [suggestion for suggestion in completions if filter_func(suggestion)]
+ return sorted(filtered_suggestions)
+
+ return completions
+
+
+ def get_tables(self):
+ """b
+ Returns a list of available tables, excluding the default schema.
+
+ Returns:
+ - list: Tables without default schema.
+ """
+
+ schemas = self.inspector.get_schema_names()
+ tables = self.inspector.get_table_names(schema=self.default_schema) # Get tables in default schema
+
+ if self.default_schema:
+ for schema in schemas:
+ schema_tables = self.inspector.get_table_names(schema=schema)
+
+ if schema != self.default_schema:
+ tables.extend([f"{schema}.{table}" for table in schema_tables]) # Keep schema.table
+
+ return tables
+
+ def get_columns(self, code):
+ """
+ Extracts tables from the query and returns relevant columns.
+
+ Parameters:
+ - code (str): SQL query.
+
+ Returns:
+ - list: Column names from the tables used in the query.
+ """
+ table_names = self.extract_table_names(code)
+ columns = []
+ for table in table_names:
+ schema, table_name = self.split_schema_table(table)
+ try:
+ table_columns = [col["name"] for col in self.inspector.get_columns(table_name, schema=schema)]
+ columns.extend(table_columns)
+ except Exception:
+ pass # Ignore missing tables
+ return columns
+
+ def get_functions(self):
+ """Returns common SQL functions."""
+ return [
+ "COUNT()", "AVG()", "SUM()", "MIN()", "MAX()",
+ "LOWER()", "UPPER()", "NOW()", "DATE()", "ROUND()"
+ ]
+
+ def get_sql_keywords(self):
+ """Returns a list of common SQL keywords."""
+ return [
+ "SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "HAVING",
+ "INSERT INTO", "VALUES", "UPDATE", "SET", "DELETE FROM",
+ "JOIN", "INNER JOIN", "LEFT JOIN", "RIGHT JOIN", "FULL JOIN",
+ "ON", "DISTINCT", "LIMIT", "OFFSET"
+ ]
+
+ def extract_table_names(self, code):
+ """
+ Extracts table names (including schema-qualified) from an SQL query.
+
+ Parameters:
+ - code (str): SQL query.
+
+ Returns:
+ - list: Table names found in the query.
+ """
+ matches = re.findall(r"FROM\s+([\w.]+)|JOIN\s+([\w.]+)|UPDATE\s+([\w.]+)", code, re.IGNORECASE)
+ return [table for tup in matches for table in tup if table]
+
+ def split_schema_table(self, table):
+ """
+ Splits a schema-qualified table into schema and table parts.
+
+ Parameters:
+ - table (str): Table name (could be schema-qualified like 'schema.table').
+
+ Returns:
+ - tuple: (schema, table_name) or (None, table_name) if no schema.
+ """
+ parts = table.split(".")
+ if len(parts) == 2:
+ schema, table_name = parts
+ if schema == self.default_schema: # Remove default schema
+ return None, table_name
+ return schema, table_name
+ return self.default_schema, table # No schema
+
+
+# Example usage
+# from sqlalchemy import create_engine
+# engine = create_engine("postgresql://postgres@localhost/tume")
+# completer = SQLAutocompleter(engine)
+# completions = completer.get_completions("SELECT municipio, FROM tume.cadastro", 17)
+# print(completions)
\ No newline at end of file
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index cbde18d..33f3ff3 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -2,10 +2,31 @@
import sqlalchemy as sa
from ipykernel.kernelbase import Kernel
import re
-
+from .autocomplete import SQLAutocompleter
+import logging
+import traceback
+from pygments import highlight
+from pygments.lexers.python import PythonLexer
+from pygments.formatters import HtmlFormatter, TerminalFormatter
+from .pygment_error_lexer import SqlErrorLexer
+from .style import ThisStyle
__version__ = '0.4.1'
+class FixedWidthHtmlFormatter(HtmlFormatter):
+
+ def wrap(self, source):
+ return self._wrap_code(source)
+
+ def _wrap_code(self, source):
+ yield 0, ''
+ for i, t in source:
+ if i == 1:
+ # it's a line of formatted code
+ t += '
'
+ yield i, t
+ yield 0, '
'
+
class MysqlKernel(Kernel):
implementation = 'mysql_kernel'
implementation_version = __version__
@@ -19,11 +40,19 @@ class MysqlKernel(Kernel):
def __init__(self, **kwargs):
Kernel.__init__(self, **kwargs)
self.engine = False
+ self.log.setLevel(logging.DEBUG)
+ print('Mysql kernel initialized')
+ self.log.info('Mysql kernel initialized')
- def output(self, output):
+ def output(self, output, plain_text = None):
+ if plain_text == None:
+ plain_text = output
if not self.silent:
display_content = {'source': 'kernel',
- 'data': {'text/html': output},
+ 'data': {
+ 'text/html': output,
+ 'text/plain': plain_text,
+ },
'metadata': {}}
self.send_response(self.iopub_socket, 'display_data', display_content)
@@ -54,33 +83,35 @@ def generic_ddl(self, query, msg):
self.output(msg % (object_name))
return
except Exception as msg:
- self.output(str(msg))
- return
+ return self.handle_error(msg)
def create_db(self, query):
- self.generic_ddl(query, 'Database %s created successfully.')
+ return self.generic_ddl(query, 'Database %s created successfully.')
def drop_db(self, query):
- self.generic_ddl(query, 'Database %s dropped successfully.')
+ return self.generic_ddl(query, 'Database %s dropped successfully.')
def create_table(self, query):
- self.generic_ddl(query, 'Table %s created successfully.')
+ return self.generic_ddl(query, 'Table %s created successfully.')
def drop_table(self, query):
- self.generic_ddl(query, 'Table %s dropped successfully.')
+ return self.generic_ddl(query, 'Table %s dropped successfully.')
def delete(self, query):
- self.generic_ddl(query, 'Data deleted from %s successfully.')
+ return self.generic_ddl(query, 'Data deleted from %s successfully.')
def alter_table(self, query):
- self.generic_ddl(query, 'Table %s altered successfully.')
+ return self.generic_ddl(query, 'Table %s altered successfully.')
def insert_into(self, query):
- self.generic_ddl(query, 'Data inserted into %s successfully.')
+ return self.generic_ddl(query, 'Data inserted into %s successfully.')
def use_db(self, query):
- self.generic_ddl(query, 'Changed to database %s successfully.')
+ new_database = re.match("use ([^ ]+)", query, re.IGNORECASE).group(1)
+ self.engine = sa.create_engine(self.engine.url.set(database=new_database))
+ self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
+ return self.generic_ddl(query, 'Changed to database %s successfully.')
def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False):
self.silent = silent
@@ -88,6 +119,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
if not code.strip():
return self.ok()
sql = code.rstrip()+('' if code.rstrip().endswith(";") else ';')
+ results_raw = None
try:
for v in sql.split(";"):
v = v.rstrip()
@@ -99,30 +131,36 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
if l.count('@')>1:
self.output("Connection failed, The Mysql address cannot have two '@'.")
else:
- self.engine = sa.create_engine(f'mysql+py{v}')
+ if v.startswith('mysql://'):
+ v = v.replace('mysql://', 'mysql+pymysql://')
+ self.engine = sa.create_engine(v)
+ self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
self.output('Connected successfully!')
+
+
elif l.startswith('create database '):
- self.create_db(v)
+ return self.create_db(v)
elif l.startswith('drop database '):
- self.drop_db(v)
+ return self.drop_db(v)
elif l.startswith('create table '):
- self.create_table(v)
+ return self.create_table(v)
elif l.startswith('drop table '):
- self.drop_table(v)
+ return self.drop_table(v)
elif l.startswith('delete '):
- self.delete(v)
+ return self.delete(v)
elif l.startswith('alter table '):
- self.alter_table(v)
+ return self.alter_table(v)
elif l.startswith('use '):
- self.use_db(v)
+ return self.use_db(v)
elif l.startswith('insert into '):
- self.insert_into(v)
+ return self.insert_into(v)
else:
if self.engine:
v = re.sub('(?Results limitted to 1000 (explicitly add LIMIT to display beyond that)
@@ -131,12 +169,79 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
else:
output = results.to_html()
else:
- output = pd.read_sql(v, self.engine).to_html()
+ with self.engine.begin() as con:
+ execution = con.execute(sa.sql.text(v))
+ if execution.returns_rows:
+ results = pd.DataFrame(execution.fetchall(), columns=execution.keys())
+ output = results.to_html()
+ elif execution.rowcount > 0:
+ output = f'Rows affected: {execution.rowcount}'
+ else:
+ output = 'No rows affected'
output = f'''{output}
'''
else:
output = 'Unable to connect to Mysql server. Check that the server is running.'
- self.output(output)
+ self.output(output, plain_text = results_raw if results_raw else output)
return self.ok()
- except Exception as msg:
- self.output(str(msg))
- return self.err('Error executing code ' + sql)
+ except Exception as e:
+ return self.handle_error(e)
+
+ def handle_error(self, e):
+ msg = re.search(r'\d+,[^"]*"([^"]+)', e.args[0]).group(1)
+
+ # Convert to HTML with Pygments
+ formatter = FixedWidthHtmlFormatter(full=True, style=ThisStyle, traceback=False)
+ tb_html = highlight("ERROR: " + msg, SqlErrorLexer(), formatter)
+ tb_terminal = highlight(msg, SqlErrorLexer(), TerminalFormatter())
+
+ # Send formatted traceback as an HTML response
+ self.send_response(
+ self.iopub_socket,
+ "display_data",
+ {
+ "data": {
+ "text/html": tb_html,
+ "text/plain": tb_terminal
+ },
+ "metadata": {}
+ },
+ )
+ return {"status": "error", "execution_count": self.execution_count}
+
+ def do_complete(self, code, cursor_pos):
+ self.log.info('Try to autocomplete')
+ if not self.autocompleter:
+ return {"status": "ok", "matches": []}
+ completion_list = self.autocompleter.get_completions(code, cursor_pos)
+ # match_text_list = [completion.text for completion in completion_list]
+ # offset = 0
+ # if len(completion_list) > 0:
+ # offset = completion_list[
+ # 0
+ # ].start_position # if match part is 'sel', then start_position would be -3
+ # type_dict_list = []
+ # for completion in completion_list:
+ # if completion.display_meta is not None:
+ # type_dict_list.append(
+ # {
+ # "start": completion.start_position,
+ # "end": len(completion.text) + completion.start_position,
+ # "text": completion.text,
+ # # display_meta is FormattedText object
+ # "type": completion.display_meta_text,
+ # }
+ # )
+
+ cursor_offset = 0
+
+ word_completing = re.search('[^ ]+$',code[:cursor_pos])
+ if word_completing:
+ cursor_offset += len(word_completing.group(0))
+
+ return {
+ "status": "ok",
+ "matches": completion_list,
+ "cursor_start": cursor_pos - cursor_offset,
+ "cursor_end": cursor_pos,
+ "metadata": {},
+ }
\ No newline at end of file
diff --git a/mysql_kernel/pygment_error_lexer.py b/mysql_kernel/pygment_error_lexer.py
new file mode 100644
index 0000000..bc5cd1e
--- /dev/null
+++ b/mysql_kernel/pygment_error_lexer.py
@@ -0,0 +1,23 @@
+from pygments.lexer import RegexLexer, bygroups
+from pygments.token import *
+
+__all__ = ['SqlErrorLexer']
+
+class SqlErrorLexer(RegexLexer):
+ """
+ A lexer for highlighting errors from MySQL/MariaDB.
+ """
+
+ name = 'sqlerror'
+ aliases = ['sqlerror']
+ filenames = ['*.sqlerror']
+
+ tokens = {
+ 'root': [
+ (r"(ERROR)(.*Table ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
+ (r"(ERROR)(.*column ')([^']+)(.*in ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class, Token.Generic, Token.Name.Builtin)),
+ (r"(ERROR)(.*near ')([^']+)(.*line [0-9]+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Builtin, Token.Number)),
+ (r"(ERROR)(.*database ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
+ ]
+ }
+
diff --git a/mysql_kernel/style.py b/mysql_kernel/style.py
new file mode 100644
index 0000000..5b63f42
--- /dev/null
+++ b/mysql_kernel/style.py
@@ -0,0 +1,82 @@
+"""
+ Kernel version of `Dracula` derived from Pygments
+ :license: BSD, see LICENSE for details.
+"""
+
+from pygments.style import Style
+from pygments.token import Keyword, Name, Comment, String, Error, Literal, \
+ Number, Operator, Other, Punctuation, Text, Generic, Whitespace
+
+
+__all__ = ['ThisStyle']
+
+background = "#282a36"
+foreground = "#f8f8f2"
+selection = "#44475a"
+comment = "#6272a4"
+cyan = "#8be9fd"
+green = "#50fa7b"
+orange = "#ffb86c"
+pink = "#ff79c6"
+purple = "#bd93f9"
+red = "#ff5555"
+yellow = "#f1fa8c"
+
+deletion = "#8b080b"
+
+class ThisStyle(Style):
+ name = 'this'
+
+ background_color = background
+ highlight_color = selection
+ line_number_color = yellow
+ line_number_background_color = selection
+ line_number_special_color = green
+ line_number_special_background_color = comment
+
+ styles = {
+ Whitespace: foreground,
+
+ Comment: comment,
+ Comment.Preproc: pink,
+
+ Generic: foreground,
+ Generic.Deleted: red,
+ Generic.Emph: "underline",
+ Generic.Heading: "bold",
+ Generic.Inserted: "bold",
+ Generic.Output: selection,
+ Generic.EmphStrong: "underline",
+ Generic.Subheading: "bold",
+
+ Error: foreground,
+
+ Keyword: pink,
+ Keyword.Constant: pink,
+ Keyword.Declaration: cyan + " italic",
+ Keyword.Type: cyan,
+
+ Literal: foreground,
+
+ Name: foreground,
+ Name.Attribute: green,
+ Name.Builtin: cyan + " italic",
+ Name.Builtin.Pseudo: foreground,
+ Name.Class: green,
+ Name.Function: green,
+ Name.Label: cyan + " italic",
+ Name.Tag: pink,
+ Name.Variable: cyan + " italic",
+
+ Number: orange + ' bold',
+
+ Operator: pink,
+
+ Other: foreground,
+
+ Punctuation: foreground,
+
+ String: purple,
+
+ Text: foreground,
+ }
\ No newline at end of file
From e54a2e81d94b3f8e602ed645f5042173fa006108 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 1 Apr 2025 23:43:16 -0300
Subject: [PATCH 07/22] Only return if there is an error
---
mysql_kernel/kernel.py | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 33f3ff3..85f2455 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -115,6 +115,7 @@ def use_db(self, query):
def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False):
self.silent = silent
+ res = {}
output = ''
if not code.strip():
return self.ok()
@@ -139,21 +140,21 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
elif l.startswith('create database '):
- return self.create_db(v)
+ res = self.create_db(v)
elif l.startswith('drop database '):
- return self.drop_db(v)
+ res = self.drop_db(v)
elif l.startswith('create table '):
- return self.create_table(v)
+ res = self.create_table(v)
elif l.startswith('drop table '):
- return self.drop_table(v)
+ res = self.drop_table(v)
elif l.startswith('delete '):
- return self.delete(v)
+ res = self.delete(v)
elif l.startswith('alter table '):
- return self.alter_table(v)
+ res = self.alter_table(v)
elif l.startswith('use '):
- return self.use_db(v)
+ res = self.use_db(v)
elif l.startswith('insert into '):
- return self.insert_into(v)
+ res = self.insert_into(v)
else:
if self.engine:
v = re.sub('(?
Date: Wed, 2 Apr 2025 11:28:28 -0300
Subject: [PATCH 08/22] Update kernel.py
---
mysql_kernel/kernel.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 85f2455..7c2fc2f 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -15,7 +15,7 @@
class FixedWidthHtmlFormatter(HtmlFormatter):
- def wrap(self, source):
+ def wrap(self, source, outfile=None):
return self._wrap_code(source)
def _wrap_code(self, source):
@@ -247,4 +247,4 @@ def do_complete(self, code, cursor_pos):
"cursor_start": cursor_pos - cursor_offset,
"cursor_end": cursor_pos,
"metadata": {},
- }
\ No newline at end of file
+ }
From 4ac3b509a094bf60d6a8b93ff0cb55a53e1b69f4 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 2 Apr 2025 11:31:35 -0300
Subject: [PATCH 09/22] Update kernel.py
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 7c2fc2f..6a24a14 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -15,7 +15,7 @@
class FixedWidthHtmlFormatter(HtmlFormatter):
- def wrap(self, source, outfile=None):
+ def wrap(self, source, *, include_div):
return self._wrap_code(source)
def _wrap_code(self, source):
From 85b17c4d05435a0046d426c4d8d909865031a62a Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 2 Apr 2025 11:35:22 -0300
Subject: [PATCH 10/22] Update setup.py
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index c8c4d58..9021eda 100644
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@ def readme():
author_email='hourout@163.com',
keywords=['jupyter_kernel', 'mysql_kernel'],
license='Apache License Version 2.0',
- install_requires=['pymysql', 'sqlalchemy', 'pandas', 'jupyter'],
+ install_requires=['pymysql', 'sqlalchemy', 'pandas', 'jupyter','pygments>=2.12'],
classifiers = [
'Framework :: IPython',
'License :: OSI Approved :: Apache Software License',
From eb4fe07c86c71a4ff234f6365de4f223340abe36 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 2 Apr 2025 11:41:04 -0300
Subject: [PATCH 11/22] Update kernel.py
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 6a24a14..6cd86fa 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -190,7 +190,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
return self.handle_error(e)
def handle_error(self, e):
- msg = re.search(r'\d+,[^"]*"([^"]+)', e.args[0]).group(1)
+ msg = re.search(r'\d+,[^"']*["']([^"']+)', e.args[0]).group(1)
# Convert to HTML with Pygments
formatter = FixedWidthHtmlFormatter(full=True, style=ThisStyle, traceback=False)
From 002cf5adb4a08f684ebba86dc1b1417e40e26427 Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 2 Apr 2025 11:44:10 -0300
Subject: [PATCH 12/22] Update kernel.py
---
mysql_kernel/kernel.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 6cd86fa..2d46fca 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -190,7 +190,10 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
return self.handle_error(e)
def handle_error(self, e):
- msg = re.search(r'\d+,[^"']*["']([^"']+)', e.args[0]).group(1)
+ search_res = re.search(r'\d+,[^"']*["']([^"']+)', e.args[0]).group(1)
+ msg = str(e)
+ if search_res and search_res.last_index >= 1:
+ msg = search_res.group(1)
# Convert to HTML with Pygments
formatter = FixedWidthHtmlFormatter(full=True, style=ThisStyle, traceback=False)
From 9fe654399abdbd493fc0dfb2e011dc743cb0bd2c Mon Sep 17 00:00:00 2001
From: Caio Hamamura
Date: Wed, 2 Apr 2025 11:50:35 -0300
Subject: [PATCH 13/22] Update kernel.py
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 2d46fca..67e1f9f 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -190,7 +190,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
return self.handle_error(e)
def handle_error(self, e):
- search_res = re.search(r'\d+,[^"']*["']([^"']+)', e.args[0]).group(1)
+ search_res = re.search(r'\d+,[^"\']*["\']([^"\']+)', e.args[0]).group(1)
msg = str(e)
if search_res and search_res.last_index >= 1:
msg = search_res.group(1)
From 25d946ca0c133cd5b6c5f34854a9303583e3cdaa Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Wed, 2 Apr 2025 12:16:51 -0300
Subject: [PATCH 14/22] Fix for robustness against errors
---
mysql_kernel/kernel.py | 6 +++---
mysql_kernel/pygment_error_lexer.py | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 67e1f9f..30de3f3 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -15,7 +15,7 @@
class FixedWidthHtmlFormatter(HtmlFormatter):
- def wrap(self, source, *, include_div):
+ def wrap(self, source):
return self._wrap_code(source)
def _wrap_code(self, source):
@@ -190,9 +190,9 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
return self.handle_error(e)
def handle_error(self, e):
- search_res = re.search(r'\d+,[^"\']*["\']([^"\']+)', e.args[0]).group(1)
+ search_res = re.search(r'\d+,[^"\']*["\']([^"\']+)', e.args[0])
msg = str(e)
- if search_res and search_res.last_index >= 1:
+ if search_res and search_res.lastindex >= 1:
msg = search_res.group(1)
# Convert to HTML with Pygments
diff --git a/mysql_kernel/pygment_error_lexer.py b/mysql_kernel/pygment_error_lexer.py
index bc5cd1e..23a43cc 100644
--- a/mysql_kernel/pygment_error_lexer.py
+++ b/mysql_kernel/pygment_error_lexer.py
@@ -16,7 +16,7 @@ class SqlErrorLexer(RegexLexer):
'root': [
(r"(ERROR)(.*Table ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
(r"(ERROR)(.*column ')([^']+)(.*in ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class, Token.Generic, Token.Name.Builtin)),
- (r"(ERROR)(.*near ')([^']+)(.*line [0-9]+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Builtin, Token.Number)),
+ (r"(ERROR)(.*near ['\"])([^'\"]+)(.*line [0-9]+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Builtin, Token.Number)),
(r"(ERROR)(.*database ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
]
}
From e58731e2766279076f5177c267d9f143f3f8e4f0 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Wed, 2 Apr 2025 13:24:20 -0300
Subject: [PATCH 15/22] Make it usable with postgresql
---
mysql_kernel/kernel.py | 15 +++++++++------
mysql_kernel/pygment_error_lexer.py | 14 ++++++++++----
setup.py | 2 +-
3 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 30de3f3..2888f06 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -109,7 +109,7 @@ def insert_into(self, query):
def use_db(self, query):
new_database = re.match("use ([^ ]+)", query, re.IGNORECASE).group(1)
- self.engine = sa.create_engine(self.engine.url.set(database=new_database))
+ self.engine = sa.create_engine(self.engine.url.set(database=new_database), isolation_level='AUTOCOMMIT')
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
return self.generic_ddl(query, 'Changed to database %s successfully.')
@@ -128,13 +128,13 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
v = re.sub('\n* *--.*\n', '', v) # remove comments
l = v.lower()
if len(l)>0:
- if l.startswith('mysql://'):
+ if re.search('\w+://', l):
if l.count('@')>1:
self.output("Connection failed, The Mysql address cannot have two '@'.")
else:
if v.startswith('mysql://'):
v = v.replace('mysql://', 'mysql+pymysql://')
- self.engine = sa.create_engine(v)
+ self.engine = sa.create_engine(v, isolation_level='AUTOCOMMIT')
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
self.output('Connected successfully!')
@@ -190,13 +190,16 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
return self.handle_error(e)
def handle_error(self, e):
- search_res = re.search(r'\d+,[^"\']*["\']([^"\']+)', e.args[0])
+ search_res = re.search(r'\d+, *["\'](.*)(?=["\']\))', e.args[0])
msg = str(e)
- if search_res and search_res.lastindex >= 1:
+ if search_res and len(search_res.groups()) > 0:
msg = search_res.group(1)
+ msg = re.sub(r'\[SQL:.*', '', msg)
+ msg = re.sub(r'\(Background on this error at.*', '', msg)
+
# Convert to HTML with Pygments
- formatter = FixedWidthHtmlFormatter(full=True, style=ThisStyle, traceback=False)
+ formatter = FixedWidthHtmlFormatter(noclasses=True, style=ThisStyle, traceback=False)
tb_html = highlight("ERROR: " + msg, SqlErrorLexer(), formatter)
tb_terminal = highlight(msg, SqlErrorLexer(), TerminalFormatter())
diff --git a/mysql_kernel/pygment_error_lexer.py b/mysql_kernel/pygment_error_lexer.py
index 23a43cc..8968f0a 100644
--- a/mysql_kernel/pygment_error_lexer.py
+++ b/mysql_kernel/pygment_error_lexer.py
@@ -1,5 +1,6 @@
from pygments.lexer import RegexLexer, bygroups
from pygments.token import *
+import re
__all__ = ['SqlErrorLexer']
@@ -12,12 +13,17 @@ class SqlErrorLexer(RegexLexer):
aliases = ['sqlerror']
filenames = ['*.sqlerror']
+ flags = re.IGNORECASE
tokens = {
'root': [
- (r"(ERROR)(.*Table ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
- (r"(ERROR)(.*column ')([^']+)(.*in ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class, Token.Generic, Token.Name.Builtin)),
- (r"(ERROR)(.*near ['\"])([^'\"]+)(.*line [0-9]+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Builtin, Token.Number)),
- (r"(ERROR)(.*database ')([^']+)", bygroups(Token.Generic.Deleted, Token.Generic, Token.Name.Class)),
+ (r"^ERROR", Token.Generic.Deleted),
+ (r"(?<=table [\"'])[^\"']+", Token.Name.Class),
+ (r"(?<=relation [\"'])[^\"']+", Token.Name.Class),
+ (r"(?<=column [\"'])[^\"']+", Token.Name.Class),
+ (r"(?<=column )[^ \"]+", Token.Name.Class),
+ (r"(?<=database [\"'])[^\"']+", Token.Name.Class),
+ (r"(?<=near [\"'])[^\"']+", Token.Name.Builtin),
+ (r"line [0-9]+", Token.Number),
]
}
diff --git a/setup.py b/setup.py
index 9021eda..d056ac7 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def readme():
return f.read()
setup(name='mysql_kernel',
- version='0.4.1',
+ version='0.5.0',
description='A mysql kernel for Jupyter.',
long_description=readme(),
long_description_content_type='text/markdown',
From 4719b512fad97767cdb111d0884243f0032fe5ed Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 15 Apr 2025 18:41:41 -0300
Subject: [PATCH 16/22] Make duckdb usable
---
mysql_kernel/kernel.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 2888f06..5845132 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -109,7 +109,11 @@ def insert_into(self, query):
def use_db(self, query):
new_database = re.match("use ([^ ]+)", query, re.IGNORECASE).group(1)
- self.engine = sa.create_engine(self.engine.url.set(database=new_database), isolation_level='AUTOCOMMIT')
+ if self.engine.url.engine == 'duckdb':
+ self.engine = sa.create_engine(self.engine.url.set(database=new_database))
+ else:
+ self.engine = sa.create_engine(self.engine.url.set(database=new_database), isolation_level='AUTOCOMMIT')
+
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
return self.generic_ddl(query, 'Changed to database %s successfully.')
@@ -134,7 +138,10 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
else:
if v.startswith('mysql://'):
v = v.replace('mysql://', 'mysql+pymysql://')
- self.engine = sa.create_engine(v, isolation_level='AUTOCOMMIT')
+ if (v.startswith('duckdb')):
+ self.engine = sa.create_engine(v)
+ else:
+ self.engine = sa.create_engine(v, isolation_level='AUTOCOMMIT')
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
self.output('Connected successfully!')
From ce8915722e9223040c14b6277c2749afd7bf706c Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 15 Apr 2025 18:42:43 -0300
Subject: [PATCH 17/22] Update details and bump version 0.5.1
---
mysql_kernel/__init__.py | 2 +-
setup.py | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/mysql_kernel/__init__.py b/mysql_kernel/__init__.py
index 40c89a9..beb694e 100644
--- a/mysql_kernel/__init__.py
+++ b/mysql_kernel/__init__.py
@@ -1,4 +1,4 @@
"""A mysql kernel for Jupyter"""
from .kernel import __version__
-__author__ = 'JinQing Lee'
+__author__ = 'Caio Hamamura'
diff --git a/setup.py b/setup.py
index d056ac7..56b9bd5 100644
--- a/setup.py
+++ b/setup.py
@@ -10,13 +10,13 @@ def readme():
return f.read()
setup(name='mysql_kernel',
- version='0.5.0',
- description='A mysql kernel for Jupyter.',
+ version='0.5.1',
+ description='A generic kernel for Jupyter forked from JinQing Lees mysql_kernel',
long_description=readme(),
long_description_content_type='text/markdown',
url='https://github.com/Hourout/mysql_kernel',
- author='JinQing Lee',
- author_email='hourout@163.com',
+ author='Caio Hamamura',
+ author_email='caiohamamura@gmail.com',
keywords=['jupyter_kernel', 'mysql_kernel'],
license='Apache License Version 2.0',
install_requires=['pymysql', 'sqlalchemy', 'pandas', 'jupyter','pygments>=2.12'],
From 1a38028c6795f1909e0200f0d38b939a2f1cc43b Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 15 Apr 2025 18:44:19 -0300
Subject: [PATCH 18/22] Use raw strings for regex
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 5845132..039db7a 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -132,7 +132,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
v = re.sub('\n* *--.*\n', '', v) # remove comments
l = v.lower()
if len(l)>0:
- if re.search('\w+://', l):
+ if re.search(r'\w+://', l):
if l.count('@')>1:
self.output("Connection failed, The Mysql address cannot have two '@'.")
else:
From c79d5be0a5aa80968cd52951062d7d74ebaae769 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 15 Apr 2025 20:34:03 -0300
Subject: [PATCH 19/22] Fix url drivername
---
mysql_kernel/kernel.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 039db7a..3f7808e 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -109,7 +109,7 @@ def insert_into(self, query):
def use_db(self, query):
new_database = re.match("use ([^ ]+)", query, re.IGNORECASE).group(1)
- if self.engine.url.engine == 'duckdb':
+ if self.engine.url.drivername == 'duckdb':
self.engine = sa.create_engine(self.engine.url.set(database=new_database))
else:
self.engine = sa.create_engine(self.engine.url.set(database=new_database), isolation_level='AUTOCOMMIT')
From af29f03df7b556afe7968774758d7c969fde4662 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Mon, 21 Apr 2025 19:29:51 -0300
Subject: [PATCH 20/22] Localize messages
---
MANIFEST.in | 3 +
mysql_kernel/i18n.py | 27 ++++++
mysql_kernel/kernel.py | 46 +++++----
.../locale/pt_br/LC_MESSAGES/messages.mo | Bin 0 -> 1971 bytes
.../locale/pt_br/LC_MESSAGES/messages.po | 89 ++++++++++++++++++
mysql_kernel/messages.pot | 89 ++++++++++++++++++
setup.py | 1 +
7 files changed, 235 insertions(+), 20 deletions(-)
create mode 100644 MANIFEST.in
create mode 100644 mysql_kernel/i18n.py
create mode 100644 mysql_kernel/locale/pt_br/LC_MESSAGES/messages.mo
create mode 100644 mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
create mode 100644 mysql_kernel/messages.pot
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..ea6a5d1
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+recursive-include mysql_kernel/locale *.mo
+recursive-include mysql_kernel/locale *.po
+
diff --git a/mysql_kernel/i18n.py b/mysql_kernel/i18n.py
new file mode 100644
index 0000000..b85df78
--- /dev/null
+++ b/mysql_kernel/i18n.py
@@ -0,0 +1,27 @@
+# i18n.py
+import gettext
+import locale
+import os
+
+def get_translator(lang=None):
+ base_dir = os.path.dirname(os.path.abspath(__file__))
+ locale_dir = os.path.join(base_dir, 'locale')
+ if lang is None:
+ lang = locale.getdefaultlocale()[0]
+ if gettext.find('messages', localedir=locale_dir, languages=[lang]) is None:
+ lang = lang[0:2]
+
+ return gettext.translation(
+ 'messages',
+ localedir=locale_dir,
+ languages=[lang],
+ fallback=True
+ ).gettext
+
+def has_translation(lang=None):
+ lang = locale.getdefaultlocale()[0]
+ try:
+ get_translator(lang)
+ return f"Tem tradução {lang}"
+ except:
+ return "False"
\ No newline at end of file
diff --git a/mysql_kernel/kernel.py b/mysql_kernel/kernel.py
index 3f7808e..bdfff0f 100644
--- a/mysql_kernel/kernel.py
+++ b/mysql_kernel/kernel.py
@@ -10,6 +10,10 @@
from pygments.formatters import HtmlFormatter, TerminalFormatter
from .pygment_error_lexer import SqlErrorLexer
from .style import ThisStyle
+from .i18n import get_translator
+
+_ = get_translator()
+
__version__ = '0.4.1'
@@ -41,8 +45,8 @@ def __init__(self, **kwargs):
Kernel.__init__(self, **kwargs)
self.engine = False
self.log.setLevel(logging.DEBUG)
- print('Mysql kernel initialized')
- self.log.info('Mysql kernel initialized')
+ print(_('Mysql kernel initialized'))
+ self.log.info(_('Mysql kernel initialized'))
def output(self, output, plain_text = None):
if plain_text == None:
@@ -78,7 +82,8 @@ def generic_ddl(self, query, msg):
object_name = query.split()[1]
rows_affected = result.rowcount
if result.rowcount > 0:
- self.output((msg + '\nRows affected: %d.') % (object_name, rows_affected))
+ msgpart = _('Rows affected')
+ self.output((msg + f'\n{msgpart}: %d.') % (object_name, rows_affected))
else:
self.output(msg % (object_name))
return
@@ -86,26 +91,26 @@ def generic_ddl(self, query, msg):
return self.handle_error(msg)
def create_db(self, query):
- return self.generic_ddl(query, 'Database %s created successfully.')
+ return self.generic_ddl(query, _('Database %s created successfully.'))
def drop_db(self, query):
- return self.generic_ddl(query, 'Database %s dropped successfully.')
+ return self.generic_ddl(query, _('Database %s dropped successfully.'))
def create_table(self, query):
- return self.generic_ddl(query, 'Table %s created successfully.')
+ return self.generic_ddl(query, _('Table %s created successfully.'))
def drop_table(self, query):
- return self.generic_ddl(query, 'Table %s dropped successfully.')
+ return self.generic_ddl(query, _('Table %s dropped successfully.'))
def delete(self, query):
- return self.generic_ddl(query, 'Data deleted from %s successfully.')
+ return self.generic_ddl(query, _('Data deleted from %s successfully.'))
def alter_table(self, query):
- return self.generic_ddl(query, 'Table %s altered successfully.')
+ return self.generic_ddl(query, _('Table %s altered successfully.'))
def insert_into(self, query):
- return self.generic_ddl(query, 'Data inserted into %s successfully.')
+ return self.generic_ddl(query, _('Data inserted into %s successfully.'))
def use_db(self, query):
new_database = re.match("use ([^ ]+)", query, re.IGNORECASE).group(1)
@@ -115,7 +120,7 @@ def use_db(self, query):
self.engine = sa.create_engine(self.engine.url.set(database=new_database), isolation_level='AUTOCOMMIT')
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
- return self.generic_ddl(query, 'Changed to database %s successfully.')
+ return self.generic_ddl(query, _('Changed to database %s successfully.'))
def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False):
self.silent = silent
@@ -134,7 +139,7 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
if len(l)>0:
if re.search(r'\w+://', l):
if l.count('@')>1:
- self.output("Connection failed, The Mysql address cannot have two '@'.")
+ self.output(_("Connection failed, The Mysql address cannot have two '@'."))
else:
if v.startswith('mysql://'):
v = v.replace('mysql://', 'mysql+pymysql://')
@@ -143,9 +148,9 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
else:
self.engine = sa.create_engine(v, isolation_level='AUTOCOMMIT')
self.autocompleter = SQLAutocompleter(engine=self.engine, log=self.log)
- self.output('Connected successfully!')
-
-
+ self.output(_('Connected successfully!'))
+ elif self.engine == False:
+ self.output(_('Please connect to a database first!'))
elif l.startswith('create database '):
res = self.create_db(v)
elif l.startswith('drop database '):
@@ -170,8 +175,9 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
results = pd.read_sql(v, self.engine)
results_raw = results.to_string()
if results.shape[0] == 1000:
+ msg_part = _('Results truncated to 1000 (explicitly add LIMIT to display beyond that)')
output = f'''
- Results limitted to 1000 (explicitly add LIMIT to display beyond that)
+ {msg_part}
{results.to_html()}
'''
else:
@@ -183,12 +189,13 @@ def do_execute(self, code, silent, store_history=True, user_expressions=None, al
results = pd.DataFrame(execution.fetchall(), columns=execution.keys())
output = results.to_html()
elif execution.rowcount > 0:
- output = f'Rows affected: {execution.rowcount}'
+ msg_part = _('Rows affected')
+ output = f'{msg_part}: {execution.rowcount}'
else:
- output = 'No rows affected'
+ output = _('No rows affected')
output = f'''{output}
'''
else:
- output = 'Unable to connect to Mysql server. Check that the server is running.'
+ output = _('Unable to connect to Mysql server. Check that the server is running.')
self.output(output, plain_text = results_raw if results_raw else output)
if res and 'status' in res.keys() and res['status'] == 'error':
return res
@@ -225,7 +232,6 @@ def handle_error(self, e):
return {"status": "error", "execution_count": self.execution_count}
def do_complete(self, code, cursor_pos):
- self.log.info('Try to autocomplete')
if not self.autocompleter:
return {"status": "ok", "matches": []}
completion_list = self.autocompleter.get_completions(code, cursor_pos)
diff --git a/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.mo b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000000000000000000000000000000000000..018198f460ac83a10b88e9bd7c2391404258a771
GIT binary patch
literal 1971
zcmZ{k&u<$=6vqcBKWcs>@k`>zOG6bZWw%aB)M5&XoU|pvPC{@&f&&`wKHEcgXWW@x
zH|8$@!Ic9C4v0#Tkjjle09oSBkqaj-{0ZFn&a9m{Nm*(9*_nBs_kC~P+rOSW^PRx+
zEXFGscQIbXSbhLMc)kMP0KW$P`Y+(y;P2o$P&_EaBCrT9fH$U8;7gc)13m-Z1-;#`
z;0^Fk@JVptAt7D^FHX5VWe#p){af%deEk7>zh@s7;stO6Y=WPHi{KC7Iq)~|Joq=b
z1lAs@;#vit$9xC81Twe-eh)qk{sX=a{tLbezWiu4zYKnc`6nP`h^H_h{qT7VZ;&R=
zVw}P7XFZPLV}1ey#|cO|e0AWLXCJ8&&E3<4I
ziw&bSCk4#r*B55%%4q6Im2tXEodHvO>~3cy)70V!N~G4Npn)7Q6?Y6RtuMu56TT?r
zjQycKYx2`hgNf3Pt+!Dc3Qr01HZ#eIWqDF!8n
zs>sGZH`Ll{Z*@Z6l^bSqOkExujhh(A;)2*a=D8!gS-2OO6>Lw1aJYT{92{=nKLIy&
zI2}6YBCE23I;KIj-JNKUN6P;x!i{o+R_m*4QT?rGb%j>e8f*2aeyLus
zwWKSej+NSFvM{zm8&Vm1U*<9|t)#07ri1Bhy`Q5Z|Bqo9NBr*J+{#`otP9EYtWUhDr%x#rczH1Bn>4(Fw~VWKxd$^QsaEZ
z<5H5WoZL$#UUT1p=qeMM5($UZ{PKdhF0+9o?Tt0>Pkusz4Y5v9Hj@LKY|x$`eb^$6
zEgBklf+30uJGjqjb9&djU%ACAjDnCk@0&qhXipWaLkAxd*gPGIRVVbu;CJO
zd|a{kplZ-V{pi#rbSh31+pGs=E-6zQ!OI>aFH+#5@m(4jyBv~%4_Jiul~m>@(>$~)
z_f;_K-w<5Y!alfgH&qkM6mF5(Y5UH4TIQ$*t_*o!b(JO5pSvJhUXlNX1W8Vy7dewY
zouQQ>^LUs?`-xB1|L#tcbYg$oUuw@NTGdn0_&d>NojEJ+jk7Kyc03B-g>RXugP
b^c{^V@ogQp*%gx?iF4EdC*|Y-r^ezxunb0J
literal 0
HcmV?d00001
diff --git a/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..425fb76
--- /dev/null
+++ b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
@@ -0,0 +1,89 @@
+# Tradução em Português do Brasil para kernel jupyter para SQL
+# Copyright (C) 2025
+# Este arquivo é distribuído sob a mesma licença do pacote.
+# Tradutor: Caio Hamamura , 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: kernel-mysql 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-04-21 15:46-0300\n"
+"PO-Revision-Date: 2025-04-21 15:50-0300\n"
+"Last-Translator: Caio Hamamura \n"
+"Language-Team: Português Brasileiro \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: kernel.py:51 kernel.py:52
+msgid "Mysql kernel initialized"
+msgstr "Kernel do MySQL inicializado"
+
+#: kernel.py:88 kernel.py:195
+msgid "Rows affected"
+msgstr "Linhas afetadas"
+
+#: kernel.py:97
+#, python-format
+msgid "Database %s created successfully."
+msgstr "Banco de dados %s criado com sucesso."
+
+#: kernel.py:101
+#, python-format
+msgid "Database %s dropped successfully."
+msgstr "Banco de dados %s removido com sucesso."
+
+#: kernel.py:104
+#, python-format
+msgid "Table %s created successfully."
+msgstr "Tabela %s criada com sucesso."
+
+#: kernel.py:107
+#, python-format
+msgid "Table %s dropped successfully."
+msgstr "Tabela %s removida com sucesso."
+
+#: kernel.py:110
+#, python-format
+msgid "Data deleted from %s successfully."
+msgstr "Dados excluídos de %s com sucesso."
+
+#: kernel.py:113
+#, python-format
+msgid "Table %s altered successfully."
+msgstr "Tabela %s alterada com sucesso."
+
+#: kernel.py:116
+#, python-format
+msgid "Data inserted into %s successfully."
+msgstr "Dados inseridos em %s com sucesso."
+
+#: kernel.py:126
+#, python-format
+msgid "Changed to database %s successfully."
+msgstr "Mudança para o banco de dados %s concluída com sucesso."
+
+#: kernel.py:145
+msgid "Connection failed, The Mysql address cannot have two '@'."
+msgstr "Falha na conexão: o endereço do MySQL não pode conter dois '@'."
+
+#: kernel.py:154
+msgid "Connected successfully!"
+msgstr "Conectado com sucesso!"
+
+#: kernel.py:156
+msgid "Please connect to a database first!"
+msgstr "Por favor, conecte-se a um banco de dados primeiro!"
+
+#: kernel.py:181
+msgid "Results truncated to 1000 (explicitly add LIMIT to display beyond that)"
+msgstr "Resultados truncados para 1000 (adicione LIMIT explicitamente para exibir mais)"
+
+#: kernel.py:198
+msgid "No rows affected"
+msgstr "Nenhuma linha afetada"
+
+#: kernel.py:201
+msgid "Unable to connect to Mysql server. Check that the server is running."
+msgstr "Não foi possível conectar ao servidor MySQL. Verifique se o servidor está em execução."
diff --git a/mysql_kernel/messages.pot b/mysql_kernel/messages.pot
new file mode 100644
index 0000000..c993b9f
--- /dev/null
+++ b/mysql_kernel/messages.pot
@@ -0,0 +1,89 @@
+# Translation in ... for SQL jupyter kernel
+# Copyright (C) 2025
+# This file is distributed under the same license of the code
+# Translator: Caio Hamamura , 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: kernel-mysql 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-04-21 15:46-0300\n"
+"PO-Revision-Date: 2025-04-21 15:50-0300\n"
+"Last-Translator: ... <...@...>\n"
+"Language-Team: \n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: kernel.py:51 kernel.py:52
+msgid "Mysql kernel initialized"
+msgstr ""
+
+#: kernel.py:88 kernel.py:195
+msgid "Rows affected"
+msgstr ""
+
+#: kernel.py:97
+#, python-format
+msgid "Database %s created successfully."
+msgstr ""
+
+#: kernel.py:101
+#, python-format
+msgid "Database %s dropped successfully."
+msgstr ""
+
+#: kernel.py:104
+#, python-format
+msgid "Table %s created successfully."
+msgstr ""
+
+#: kernel.py:107
+#, python-format
+msgid "Table %s dropped successfully."
+msgstr ""
+
+#: kernel.py:110
+#, python-format
+msgid "Data deleted from %s successfully."
+msgstr ""
+
+#: kernel.py:113
+#, python-format
+msgid "Table %s altered successfully."
+msgstr ""
+
+#: kernel.py:116
+#, python-format
+msgid "Data inserted into %s successfully."
+msgstr ""
+
+#: kernel.py:126
+#, python-format
+msgid "Changed to database %s successfully."
+msgstr ""
+
+#: kernel.py:145
+msgid "Connection failed, The Mysql address cannot have two '@'."
+msgstr ""
+
+#: kernel.py:154
+msgid "Connected successfully!"
+msgstr ""
+
+#: kernel.py:156
+msgid "Please connect to a database first!"
+msgstr ""
+
+#: kernel.py:181
+msgid "Results truncated to 1000 (explicitly add LIMIT to display beyond that)"
+msgstr ""
+
+#: kernel.py:198
+msgid "No rows affected"
+msgstr ""
+
+#: kernel.py:201
+msgid "Unable to connect to Mysql server. Check that the server is running."
+msgstr ""
diff --git a/setup.py b/setup.py
index 56b9bd5..7200dc9 100644
--- a/setup.py
+++ b/setup.py
@@ -34,5 +34,6 @@ def readme():
'Topic :: System :: Shells',
],
packages=['mysql_kernel'],
+ include_package_data=True,
zip_safe=False
)
From d11ee4f547a621073d8918281be2ab5d83f3e4e7 Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Mon, 21 Apr 2025 19:30:27 -0300
Subject: [PATCH 21/22] Bump version 0.6.0
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 7200dc9..a931ea8 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def readme():
return f.read()
setup(name='mysql_kernel',
- version='0.5.1',
+ version='0.6.0',
description='A generic kernel for Jupyter forked from JinQing Lees mysql_kernel',
long_description=readme(),
long_description_content_type='text/markdown',
From c5bff5310a7a944503df986564b51dd692811a1d Mon Sep 17 00:00:00 2001
From: caiohamamura
Date: Tue, 17 Jun 2025 11:48:57 -0300
Subject: [PATCH 22/22] Update messages
---
.../locale/pt_br/LC_MESSAGES/messages.mo | Bin 1971 -> 1971 bytes
.../locale/pt_br/LC_MESSAGES/messages.po | 35 ++++-------
mysql_kernel/messages.pot | 55 +++++++++---------
3 files changed, 40 insertions(+), 50 deletions(-)
diff --git a/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.mo b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.mo
index 018198f460ac83a10b88e9bd7c2391404258a771..27a97afc162189a606f5e385170dcf4d48083632 100644
GIT binary patch
delta 17
YcmdnYznOo7DhrF5uA%v6O_nZZ04`_*ng9R*
delta 17
YcmdnYznOo7DhrE=u94wpO_nZZ04_NMlK=n!
diff --git a/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
index 425fb76..d8570b3 100644
--- a/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
+++ b/mysql_kernel/locale/pt_br/LC_MESSAGES/messages.po
@@ -1,14 +1,15 @@
-# Tradução em Português do Brasil para kernel jupyter para SQL
+# A jupyter kernel for mysql.
# Copyright (C) 2025
-# Este arquivo é distribuído sob a mesma licença do pacote.
-# Tradutor: Caio Hamamura , 2025.
+# This file is distributed under the same license as the mysql_kernel package.
+# Caio Hamamura , 2025.
#
+#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: kernel-mysql 1.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-04-21 15:46-0300\n"
-"PO-Revision-Date: 2025-04-21 15:50-0300\n"
+"POT-Creation-Date: 2025-04-21 19:29-0300\n"
+"PO-Revision-Date: 2025-06-17 15:50-0300\n"
"Last-Translator: Caio Hamamura \n"
"Language-Team: Português Brasileiro \n"
"Language: pt_BR\n"
@@ -16,74 +17,62 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: kernel.py:51 kernel.py:52
msgid "Mysql kernel initialized"
msgstr "Kernel do MySQL inicializado"
-#: kernel.py:88 kernel.py:195
msgid "Rows affected"
msgstr "Linhas afetadas"
-#: kernel.py:97
#, python-format
msgid "Database %s created successfully."
msgstr "Banco de dados %s criado com sucesso."
-#: kernel.py:101
#, python-format
msgid "Database %s dropped successfully."
msgstr "Banco de dados %s removido com sucesso."
-#: kernel.py:104
#, python-format
msgid "Table %s created successfully."
msgstr "Tabela %s criada com sucesso."
-#: kernel.py:107
#, python-format
msgid "Table %s dropped successfully."
msgstr "Tabela %s removida com sucesso."
-#: kernel.py:110
#, python-format
msgid "Data deleted from %s successfully."
msgstr "Dados excluídos de %s com sucesso."
-#: kernel.py:113
#, python-format
msgid "Table %s altered successfully."
msgstr "Tabela %s alterada com sucesso."
-#: kernel.py:116
#, python-format
msgid "Data inserted into %s successfully."
msgstr "Dados inseridos em %s com sucesso."
-#: kernel.py:126
#, python-format
msgid "Changed to database %s successfully."
msgstr "Mudança para o banco de dados %s concluída com sucesso."
-#: kernel.py:145
msgid "Connection failed, The Mysql address cannot have two '@'."
msgstr "Falha na conexão: o endereço do MySQL não pode conter dois '@'."
-#: kernel.py:154
msgid "Connected successfully!"
msgstr "Conectado com sucesso!"
-#: kernel.py:156
msgid "Please connect to a database first!"
msgstr "Por favor, conecte-se a um banco de dados primeiro!"
-#: kernel.py:181
msgid "Results truncated to 1000 (explicitly add LIMIT to display beyond that)"
-msgstr "Resultados truncados para 1000 (adicione LIMIT explicitamente para exibir mais)"
+msgstr ""
+"Resultados truncados para 1000 (adicione LIMIT explicitamente para exibir "
+"mais)"
-#: kernel.py:198
msgid "No rows affected"
msgstr "Nenhuma linha afetada"
-#: kernel.py:201
msgid "Unable to connect to Mysql server. Check that the server is running."
-msgstr "Não foi possível conectar ao servidor MySQL. Verifique se o servidor está em execução."
+msgstr ""
+"Não foi possível conectar ao servidor MySQL. Verifique se o servidor está em "
+"execução."
diff --git a/mysql_kernel/messages.pot b/mysql_kernel/messages.pot
index c993b9f..ec28f4a 100644
--- a/mysql_kernel/messages.pot
+++ b/mysql_kernel/messages.pot
@@ -1,89 +1,90 @@
-# Translation in ... for SQL jupyter kernel
-# Copyright (C) 2025
-# This file is distributed under the same license of the code
-# Translator: Caio Hamamura , 2025.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
#
+#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: kernel-mysql 1.0\n"
+"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-04-21 15:46-0300\n"
-"PO-Revision-Date: 2025-04-21 15:50-0300\n"
-"Last-Translator: ... <...@...>\n"
-"Language-Team: \n"
-"Language: pt_BR\n"
+"POT-Creation-Date: 2025-04-21 19:29-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: kernel.py:51 kernel.py:52
+#: kernel.py:48 kernel.py:49
msgid "Mysql kernel initialized"
msgstr ""
-#: kernel.py:88 kernel.py:195
+#: kernel.py:85 kernel.py:192
msgid "Rows affected"
msgstr ""
-#: kernel.py:97
+#: kernel.py:94
#, python-format
msgid "Database %s created successfully."
msgstr ""
-#: kernel.py:101
+#: kernel.py:98
#, python-format
msgid "Database %s dropped successfully."
msgstr ""
-#: kernel.py:104
+#: kernel.py:101
#, python-format
msgid "Table %s created successfully."
msgstr ""
-#: kernel.py:107
+#: kernel.py:104
#, python-format
msgid "Table %s dropped successfully."
msgstr ""
-#: kernel.py:110
+#: kernel.py:107
#, python-format
msgid "Data deleted from %s successfully."
msgstr ""
-#: kernel.py:113
+#: kernel.py:110
#, python-format
msgid "Table %s altered successfully."
msgstr ""
-#: kernel.py:116
+#: kernel.py:113
#, python-format
msgid "Data inserted into %s successfully."
msgstr ""
-#: kernel.py:126
+#: kernel.py:123
#, python-format
msgid "Changed to database %s successfully."
msgstr ""
-#: kernel.py:145
+#: kernel.py:142
msgid "Connection failed, The Mysql address cannot have two '@'."
msgstr ""
-#: kernel.py:154
+#: kernel.py:151
msgid "Connected successfully!"
msgstr ""
-#: kernel.py:156
+#: kernel.py:153
msgid "Please connect to a database first!"
msgstr ""
-#: kernel.py:181
+#: kernel.py:178
msgid "Results truncated to 1000 (explicitly add LIMIT to display beyond that)"
msgstr ""
-#: kernel.py:198
+#: kernel.py:195
msgid "No rows affected"
msgstr ""
-#: kernel.py:201
+#: kernel.py:198
msgid "Unable to connect to Mysql server. Check that the server is running."
msgstr ""