Skip to content

Commit

Permalink
Merge pull request #2 from awesome-panel/enhancement/support-decimal-…
Browse files Browse the repository at this point in the history
…symbol-and-index

support decimal symbol
  • Loading branch information
MarcSkovMadsen authored Nov 25, 2024
2 parents 8277776 + c92adb1 commit c384bc7
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 20 deletions.
8 changes: 6 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
# Run the linter.
# Run the import sorter.
- id: ruff
args: [ --fix ]
args: ["check", "--select", "I", "--fix"]
files: "^src/"
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]
# Run the linter.
- id: ruff
args: [ --fix ]
- repo: https://github.com/hoxbro/clean_notebook
rev: v0.1.15
hooks:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ exclude = [
]
line-length = 165
fix = true

[tool.ruff.lint]
ignore = [
"D203", # one-blank-line-before-class and `no-blank-line-before-class` (D211) are incompatible.
Expand Down Expand Up @@ -118,6 +117,8 @@ select = [
"UP034",
"UP036",
]
[tool.ruff.lint.isort]
force-single-line = true

[tool.pytest.ini_options]
addopts = "--pyargs --doctest-ignore-import-errors --color=yes"
Expand Down
3 changes: 2 additions & 1 deletion src/panel_copy_paste/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import warnings

from panel_copy_paste._copy_button import CopyButton
from panel_copy_paste._paste_button import PasteButton, PasteToDataFrameButton
from panel_copy_paste._paste_button import PasteButton
from panel_copy_paste._paste_button import PasteToDataFrameButton

try:
__version__ = importlib.metadata.version(__name__)
Expand Down
43 changes: 36 additions & 7 deletions src/panel_copy_paste/_copy_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import panel as pn
import param
from narwhals.dependencies import is_into_dataframe, is_pandas_dataframe, is_polars_dataframe
from narwhals.dependencies import is_into_dataframe
from narwhals.dependencies import is_pandas_dataframe
from narwhals.dependencies import is_polars_dataframe
from narwhals.typing import IntoDataFrame

logger = logging.getLogger(__name__)
Expand All @@ -24,15 +26,35 @@ class CopyButton(pn.custom.JSComponent):
value = param.Parameter(doc="""A String or DataFrame. Or a callback, Parameter or Parameterized object providing such types.""")
button = pn.custom.Child(constant=True, doc="""An optional custom Button or ButtonIcon to use.""")

decimal_separator = param.Selector(
default=None,
objects=[None, ".", ","],
doc="""The decimal symbol used when transforming a DataFrame. If not provided set to the decimal symbol of the client.""",
)
index = param.Boolean(default=False, doc="""Whether to include the index when copying a Pandas DataFrame.""")

_DEFAULT_BUTTON = pn.widgets.ButtonIcon(description="Copy to clip board.", icon="copy", active_icon="check", toggle_duration=1500)
_data = param.Parameter(doc="""The value to be transferred to the clip board.""")

_rename = {"value": None}
_rename = {"value": None, "index": None}
_esm = """
function getDecimalSeparator(locale) {
const numberWithDecimalSeparator = 1.1;
return Intl.NumberFormat(locale)
.formatToParts(numberWithDecimalSeparator)
.find(part => part.type === 'decimal')
.value;
}
export function render({ model, el }) {
const button = model.get_child("button")
el.appendChild(button)
if (model.decimal_separator === null) {
model.decimal_separator = getDecimalSeparator();
console.log("set")
}
model.on("_data", (e)=>{
navigator.clipboard.writeText(model._data).then(function() { }, function(err) {
console.error('Could not write to clipboard: ', err);
Expand All @@ -52,20 +74,27 @@ def _get_new_button(cls):

@param.depends("button.clicks", watch=True)
def _handle_clicks(self):
self._data = self._transform_value(self.value)
self._data = self._transform_value(self.value, decimal_separator=self.decimal_separator)

@staticmethod
def _transform_frame(value: IntoDataFrame) -> str:
def _transform_frame(value: IntoDataFrame, index=False, decimal_separator=None) -> str:
if decimal_separator not in [".", ","]:
decimal_separator = "."

if is_pandas_dataframe(value):
return value.to_csv(sep="\t")
return value.to_csv(sep="\t", decimal=decimal_separator, index=index)
if is_polars_dataframe(value):
# Polars does not support ",": https://github.com/pola-rs/polars/issues/19963
# This assumes pandas and pyarrow is installed
if decimal_separator == ",":
return value.to_pandas().to_csv(sep="\t", decimal=decimal_separator, index=False)
return value.write_csv(separator="\t")

msg = f"Value of type '{type(value)} is not supported yet."
raise ValueError(msg)

@classmethod
def _transform_value(cls, value, transform_func=None) -> str:
def _transform_value(cls, value, transform_func=None, index=False, decimal_separator=None) -> str:
if isinstance(value, param.Parameterized):
if hasattr(value, "value"):
return cls._transform_frame(value.value)
Expand All @@ -81,7 +110,7 @@ def _transform_value(cls, value, transform_func=None) -> str:
if isinstance(value, str):
return value
if is_into_dataframe(value):
return cls._transform_frame(value)
return cls._transform_frame(value, index=index, decimal_separator=decimal_separator)

msg = f"Value of type '{type(value)} is not supported yet."
raise ValueError(msg)
Expand Down
4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import pytest
from panel.config import panel_extension
from panel.io.reload import _local_modules, _modules, _watched_files
from panel.io.reload import _local_modules
from panel.io.reload import _modules
from panel.io.reload import _watched_files
from panel.io.state import state
from panel.theme import Design

Expand Down
45 changes: 38 additions & 7 deletions tests/test_copy_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pandas as pd
import param
import polars as pl
import pytest

from panel_copy_paste import CopyButton

Expand Down Expand Up @@ -39,17 +40,47 @@ def test_transform_str():
assert CopyButton._transform_value(value) == value


def test_transform_pandas_dataframe():
@pytest.mark.parametrize(
["decimal_seperator", "expected"],
[
(None, "x\n1.1\n"),
(".", "x\n1.1\n"),
(",", "x\n1,1\n"),
],
)
def test_transform_pandas_dataframe(decimal_seperator, expected):
"""Can copy with the CopyButton."""
value = pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})
value = pd.DataFrame({"x": [1.1]})

assert CopyButton._transform_value(value) == "\tx\ty\n0\t1\ta\n1\t2\tb\n"
assert CopyButton._transform_value(value, decimal_separator=decimal_seperator) == expected


def test_transform_polars_dataframe():
@pytest.mark.parametrize(
["index", "expected"],
[
(True, "\tx\n0\t1.1\n"),
(False, "x\n1.1\n"),
],
)
def test_transform_pandas_dataframe_index(index, expected):
"""Can copy with the CopyButton."""
value = pd.DataFrame({"x": [1.1]})

assert CopyButton._transform_value(value, index=index) == expected


@pytest.mark.parametrize(
["decimal_seperator", "expected"],
[
(None, "x\n1.1\n"),
(".", "x\n1.1\n"),
(",", "x\n1,1\n"),
],
)
def test_transform_polars_dataframe(decimal_seperator, expected):
"""Can transform Polars DataFrame."""
value = pl.DataFrame({"x": [1, 2], "y": ["a", "b"]})
assert CopyButton._transform_value(value) == "x\ty\n1\ta\n2\tb\n"
value = pl.DataFrame({"x": [1.1]})
assert CopyButton._transform_value(value, decimal_separator=decimal_seperator) == expected


def test_transform_callback():
Expand All @@ -58,7 +89,7 @@ def test_transform_callback():
def callback():
return pd.DataFrame({"x": [1, 2], "y": ["a", "b"]})

assert CopyButton._transform_value(callback) == "\tx\ty\n0\t1\ta\n1\t2\tb\n"
assert CopyButton._transform_value(callback) == "x\ty\n1\ta\n2\tb\n"


def test_transform_parameter():
Expand Down
3 changes: 2 additions & 1 deletion tests/test_paste_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import pandas as pd
import panel as pn

from panel_copy_paste import PasteButton, PasteToDataFrameButton
from panel_copy_paste import PasteButton
from panel_copy_paste import PasteToDataFrameButton


def test_paste_string_input():
Expand Down

0 comments on commit c384bc7

Please sign in to comment.