Skip to content

Commit

Permalink
Fixes merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
danmilne1 committed Jul 3, 2024
2 parents d3fb932 + 8a368ea commit 27ca98b
Show file tree
Hide file tree
Showing 16 changed files with 243 additions and 54 deletions.
18 changes: 10 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
python-version: ['3.11']

steps:
- uses: actions/checkout@v2
Expand All @@ -38,7 +38,7 @@ jobs:
if: github.repository == 'best-practice-impact/gptables'
uses: codecov/codecov-action@v3
with:
name: gptables-pytests-py3.8
name: gptables-pytests-py3.11
flags: pytests
file: ./coverage.xml
fail_ci_if_error: true
Expand All @@ -49,7 +49,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, '3.10']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']


steps:
- uses: actions/checkout@v2
Expand All @@ -71,7 +72,8 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, '3.10']
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']


steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -99,10 +101,10 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: '3.11'
- uses: actions/cache@v2
with:
path: ~\AppData\Local\pip\Cache
Expand All @@ -126,10 +128,10 @@ jobs:
steps:
- name: Checkout source
uses: actions/checkout@v2
- name: Set up Python 3.7
- name: Set up Python 3.11
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: '3.11'
- name: Build package
run: |
pip install wheel
Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ machine-readable CSV-W files.
4. Optionally upload a ``notes_table`` with information about any notes.

5. You ``write_workbook`` to win.


**Note**: This package is not intending to create perfectly accessible spreadsheets but will help with the bulk of the work needed. Users of this packages should refer back to the `main spreadsheet guidance <https://analysisfunction.civilservice.gov.uk/policy-store/releasing-statistics-in-spreadsheets/>`_ or the `spreadsheet accessibility checklist <https://analysisfunction.civilservice.gov.uk/policy-store/making-spreadsheets-accessible-a-brief-checklist-of-the-basics/>`_ after using it to make sure nothing has been missed. Please email `Analysis.Function@ons.gov.uk <mailto:Analysis.Function@ons.gov.uk>`_ if you use the package so we can monitor use and the outputs produced.
16 changes: 16 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ and this project tries its very best to adhere to
Unreleased
===================

**Added**

* Column width customisation to cover sheet
* Incorporated rich text functionaliy into the cover page by using List[dict, str] to format text in the intro, about, and contact sections.
* Rich text format lists can now be of length 2.
* gptables now supports python 3.11
* Official disclaimer included at the bottom of the README and PyPI index

**Removed**

* CI for Python 3.6 on Linux, as no longer supported by GitHub action ``setup-python``

**Changed**

* Default theme now includes cover sheet text wrapping set to True


Released (PyPI)
===============
Expand Down
2 changes: 1 addition & 1 deletion docs/source/doc.gptable.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ formatting of that element specified in the :class:`~.core.theme.Theme`.

.. _`XlsxWriter format properties`: https://xlsxwriter.readthedocs.io/format.html#format-methods-and-format-properties

``["It is ", {"bold": True}, "inevitable"]`` would give you "It is *inevitable*".
``["It is ", {"bold": True}, "inevitable"]`` would give you "It is **inevitable**".

See this in practice under :ref:`Example Usage`.

Expand Down
3 changes: 3 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ machine-readable CSV-W files.
5. You :func:`~.core.api.write_workbook` to win.


**Note**: This package is not intending to create perfectly accessible spreadsheets but will help with the bulk of the work needed. Users of this packages should refer back to the `main spreadsheet guidance <https://analysisfunction.civilservice.gov.uk/policy-store/releasing-statistics-in-spreadsheets/>`_ or the `spreadsheet accessibility checklist <https://analysisfunction.civilservice.gov.uk/policy-store/making-spreadsheets-accessible-a-brief-checklist-of-the-basics/>`_ after using it to make sure nothing has been missed. Please email `Analysis.Function@ons.gov.uk <mailto:Analysis.Function@ons.gov.uk>`_ if you use the package so we can monitor use and the outputs produced.


.. toctree::
:maxdepth: 2
:hidden:
Expand Down
44 changes: 36 additions & 8 deletions gptables/core/cover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from dataclasses import dataclass
from typing import List

from gptables.core.gptable import FormatList

@dataclass
class Cover():
"""
Expand All @@ -10,17 +12,43 @@ class Cover():
----------
title : str
cover page title
intro : List[str, dict], optional
intro : List[str, list], optional
introductory text
about : List[str, dict], optional
about : List[str, list], optional
about/notes text
contact : List[str, dict], optional
contact : List[str, list], optional
contact details text
cover_label : str
cover page tab label, defaults to Cover
width: int
width of the column, defaults to 85
"""
title: str
intro: List = None
about: List = None
contact: List = None
cover_label: str = "Cover"

def __init__(self, title: str, intro: List = None, about: List = None,
contact: List = None, cover_label: str = "Cover", width: int = 85):

self.title = title
self.intro = self._parse_formatting(intro)
self.about = self._parse_formatting(about)
self.contact = self._parse_formatting(contact)
self.cover_label = cover_label
self.width = width

# TODO: Add input validation (e.g. empty list)

@staticmethod
def _parse_formatting(attribute):
"""Check attribute for a list. If there is a list then cast the list to a FormatList in attribute.
Parameters
----------
attribute : List[str, list]
Returns
-------
List[str, FormatList]
"""

if isinstance(attribute, list):
attribute = [FormatList(text) if isinstance(text, list) else text for text in attribute]
return attribute
5 changes: 3 additions & 2 deletions gptables/core/gptable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ class GPTable:
table : pandas.DataFrame
table to be written to an Excel workbook
table_name : str
name for table. Should be unique with no spaces and start with a
character or underscore.
name for table. Should be unique with no spaces and always begin with a
letter, an underscore character, or a backslash. Use letters, numbers,
periods, and underscore characters for the rest of the name.
title : str
title of the table
subtitles : List[str], optional
Expand Down
88 changes: 75 additions & 13 deletions gptables/core/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def write_cover(self, cover):
if cover.contact is not None:
pos = self._write_element(pos, "Contact", theme.cover_subtitle_format)
pos = self._write_element_list(pos, cover.contact, theme.cover_text_format)


self.set_column(0, 0, cover.width)

def write_gptable(self, gptable, auto_width, reference_order=[]):
"""
Expand Down Expand Up @@ -110,8 +111,6 @@ def write_gptable(self, gptable, auto_width, reference_order=[]):
auto_width,
)

self._mark_data_as_worksheet_table(gptable, theme.column_heading_format)


def _reference_annotations(self, gptable, reference_order):
"""
Expand Down Expand Up @@ -505,7 +504,7 @@ def _write_table_elements(self, pos, gptable, auto_width):
warnings.warn(msg)

# Raise error if any table element is only special characters
if gptable.table.stack().str.contains('^[^a-zA-Z0-9]*$').any():
if gptable.table.astype("string").stack().str.contains('^[^a-zA-Z0-9]*$').any():
msg = (f"""
Cell found in {gptable.table_name} containing only special characters,
replace with alphanumeric characters before inputting to GPTable.
Expand Down Expand Up @@ -560,7 +559,9 @@ def _write_table_elements(self, pos, gptable, auto_width):
formats.iloc[1:, col],
index_level_formats[level - 1] # Account for 0-indexing
)


self._apply_column_alignments(data, formats, index_columns)

## Add additional table-specific formatting from GPTable
self._apply_additional_formatting(
formats,
Expand All @@ -575,10 +576,50 @@ def _write_table_elements(self, pos, gptable, auto_width):
if auto_width:
widths = self._calculate_column_widths(data, formats)
self._set_column_widths(widths)

self._mark_data_as_worksheet_table(gptable, formats)

return pos



def _apply_column_alignments(self, data_table, formats_table, index_columns):
"""
Add column alignment to format based on datatype
Parameters
----------
data_table : pandas.DataFrame
table to be written to an Excel workbook
formats_table : pandas.DataFrame
table with same dimensions as `data_table`,
containing formating dictionaries
"""
# look for shorthand notation, usually a few letters in square brackets
# will also find note markers eg [Note 1]
# Using np.nan instead on None for backwards compatibility with pandas <=1.4
data_table_copy = data_table.replace(
regex=r"\[[\w\s]+\]",
value = np.nan,
)

data_table_copy = data_table_copy.convert_dtypes()

column_types = data_table_copy.dtypes

for column in data_table.columns:
if data_table.columns.get_loc(column) in index_columns:
alignment_dict = {"align": "left"}

elif pd.api.types.is_numeric_dtype(column_types[column]):
alignment_dict = {"align" : "right"}

else:
alignment_dict = {"align": "left"}

self._apply_format(formats_table[column], alignment_dict)


def _apply_additional_formatting(
self,
formats_table,
Expand Down Expand Up @@ -674,18 +715,32 @@ def _write_array(self, pos, data, formats):
pos = [pos[0] + rows, 0]

return pos

def _mark_data_as_worksheet_table(self, gptable, column_header_format_dict):


def _mark_data_as_worksheet_table(self, gptable, formats_dataframe):
"""
Marks the data to be recognised as a Worksheet Table in Excel.
Parameters
----------
gptable : gptables.GPTable
object containing the table
formats_dataframe : DataFrame
DataFrame with same dimensions as gptable.table, containing
formatting dictionaries
"""
data_range = gptable.data_range

column_header_format = self._workbook.add_format(column_header_format_dict)

column_list = gptable.table.columns.tolist()

column_headers = [{'header': column, 'header_format': column_header_format} for column in column_list]
formats_list = [
self._workbook.add_format(format_dict)
for format_dict in formats_dataframe.iloc[0, :].tolist()
]

column_headers = [
{'header': header, 'header_format': header_format}
for header, header_format in zip(column_list, formats_list)
]

self.add_table(*data_range,
{'header_row': True,
Expand All @@ -695,6 +750,7 @@ def _mark_data_as_worksheet_table(self, gptable, column_header_format_dict):
'name': gptable.table_name
})


def _smart_write(self, row, col, data, format_dict, *args):
"""
Depending on the input data, this function will write rich strings or
Expand Down Expand Up @@ -735,7 +791,13 @@ def _smart_write(self, row, col, data, format_dict, *args):
self._write_with_newlines(wb, row, col, data, format_dict, *args)

elif isinstance(data, FormatList):
self._write_with_custom_formats(wb, row, col, data, format_dict, *args)
if len(data.list) == 2:
text_format = format_dict.copy()
text_format.update(data.list[0])
text_data = data.list[1]
self._smart_write(row, col, text_data, text_format, *args)
else:
self._write_with_custom_formats(wb, row, col, data, format_dict, *args)

elif isinstance(data, dict):
self._write_dict_as_url(wb, row, col, data, format_dict, *args)
Expand Down
Binary file modified gptables/test/expected_workbook.xlsx
Binary file not shown.
27 changes: 27 additions & 0 deletions gptables/test/test_cover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest

from gptables.core.cover import Cover
from gptables.core.gptable import FormatList

class TestCover:

@pytest.mark.parametrize("input_data", [
None,
["text"],
[[{"bold":True}, "richtext"]],
[[{"bold":True}, "richtext", " "]],
[[{"bold":True}, "richtext", " "], "text"],
"text",
42,
[15]
])

def test_parse_formatting(self, input_data):

got = Cover._parse_formatting(input_data)

if isinstance(input_data, list):
assert all([got_element.list == input_element for input_element, got_element in zip(input_data, got) if isinstance(input_element, list)])

else:
assert got == input_data
4 changes: 2 additions & 2 deletions gptables/test/test_gptable.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def create_gptable_with_kwargs():
def generate_gptable(format_dict=None):
base_gptable = {
"table": pd.DataFrame(),
"table_name": "",
"table_name": "table_name",
"title": "",
"index_columns": {} # Override default, as no columns in table
}
Expand All @@ -72,7 +72,7 @@ def test_init_defaults(create_gptable_with_kwargs):

# Required args
assert empty_gptable.title == ""
assert empty_gptable.table_name == ""
assert empty_gptable.table_name == "table_name"

assert_frame_equal(
empty_gptable.table, pd.DataFrame().reset_index(drop=True)
Expand Down
Loading

0 comments on commit 27ca98b

Please sign in to comment.