@@ 20,7 20,6 @@ from __future__ import annotations
import builtins
from collections import defaultdict
import copy
-from dataclasses import dataclass
import datetime as dt
import inspect
import logging
@@ 50,44 49,6 @@ if platform.system() in ["Windows"]:
import xlwings as xw
-@dataclass
-class SheetStructure:
- """Struct detailing the location of the output of a report to Excel.
-
- Attributes
- ----------
- sheet : str
- The name of the Excel sheet to print the output to.
- cell : str
- The cell location (e.g. A1) to print the output to.
-
- """
-
- sheet: str
- cell: str
-
-
-@dataclass
-class DataStructure:
- """Struct that organizes the SheetStructure along with data itself and its Output.
-
- Attributes
- ----------
- structure : SheetStructure
- Struct detailing the Excel output location.
- data : pd.DataFrame
- The Output.df that will be pasted in the structure location.
- output : Output
- The class itself in order to keep a log of what was run for metadata to paste.
-
- """
-
- structure: SheetStructure
- data: pd.DataFrame
- output: Output
- pivot: Optional[rep.PivotArgs]
-
-
class _Parameters:
"""Subclass for Construct parameters, verifies the string pattern.
@@ 1015,19 976,22 @@ class Output:
except AttributeError:
params = self.construct.params.to_dict()
self.report = rep.Report(
- rep.Parent(
+ parent=rep.Parent(
"adhoc",
"__adhoc__",
".xlsx",
"Data pull or un-templated adhoc report",
+ None,
),
- "adhoc",
- "adhoc",
- True,
- f'{dt.datetime.today().strftime("%Y-%m-%dT%H%M%S")}_adhoc',
- None,
- rep.RequiredArgs(params["table"], params["columns"], params["date"]),
- rep.OptionalArgs(
+ name="adhoc",
+ key="adhoc",
+ filename=f'{dt.datetime.today().strftime("%Y-%m-%dT%H%M%S")}_adhoc',
+ structure=None,
+ email=None,
+ required=rep.RequiredArgs(
+ params["table"], params["columns"], params["date"]
+ ),
+ optional=rep.OptionalArgs(
**rep.fill_unused_optional_args(
{
key: value
@@ 1036,9 1000,9 @@ class Output:
}
)
),
- rep.FunctionArgs([], []),
- [],
- rep.CompilateArgs({}),
+ function=rep.FunctionArgs([], []),
+ pivot=[],
+ compilate=rep.CompilateArgs({}),
)
self._change_data_types()
@@ 2032,7 1996,7 @@ class Compile:
The Construct object.
report : rep.Report
The Report specification.
- data : dict[str, DataStructure]
+ data : dict[str, rep.DataStructure]
The amalgamation of the compiled report and its spreadsheet output location.
"""
@@ 2050,10 2014,9 @@ class Compile:
**self.output.report.compilate.args
)
if include_data:
- self.data["df"] = DataStructure(
- SheetStructure("df", "A1"),
+ self.data["df"] = rep.DataStructure(
+ rep.SheetStructure("df", "A1", True),
self.output.df,
- self.output,
None,
)
except AttributeError:
@@ 2062,15 2025,21 @@ class Compile:
getattr(self, "adhoc_pivot")()
if include_data or not self.report.pivot:
- structure = DataStructure(
- SheetStructure("df", "A1"),
- self.output.df,
- self.output,
- None,
- )
+ if self.report.structure:
+ structure = rep.DataStructure(
+ self.report.structure,
+ self.output.df,
+ None,
+ )
+ else:
+ structure = rep.DataStructure(
+ rep.SheetStructure("df", "A1", True),
+ self.output.df,
+ None,
+ )
try:
self.data["df"] = structure
- except NameError:
+ except AttributeError:
self.data = {"df": structure}
def _metadata(self) -> dict[str, Union[str, dict[str, Any]]]:
@@ 2122,13 2091,18 @@ class Compile:
pass
if collection.query:
- slice_for_pivot = self.output.df.query(collection.query)
+ slice_for_pivot = cleanup.na(self.output.df.query(collection.query))
else:
- slice_for_pivot = self.output.df
+ slice_for_pivot = cleanup.na(self.output.df)
+
+ if collection.structure:
+ structure = collection.structure
+ else:
+ structure = rep.SheetStructure(collection.name, "B4", True)
if collection.subs:
- self.data[collection.name] = DataStructure(
- SheetStructure(collection.name, "B4"),
+ self.data[collection.name] = rep.DataStructure(
+ structure,
util_data.subtotal(
slice_for_pivot,
collection.subs,
@@ 2138,12 2112,11 @@ class Compile:
aggfunc=collection.aggfunc,
margins=collection.margins,
),
- self.output,
collection,
)
else:
- self.data[collection.name] = DataStructure(
- SheetStructure(collection.name, "B4"),
+ self.data[collection.name] = rep.DataStructure(
+ structure,
slice_for_pivot.pivot_table(
index=collection.index,
columns=collection.columns,
@@ 2151,13 2124,12 @@ class Compile:
aggfunc=collection.aggfunc,
margins=collection.margins,
),
- self.output,
collection,
)
def equipment_report(
self, style: str = None, cols: list = None, agg: dict = None, sort: list = None
- ) -> dict[str, DataStructure]:
+ ) -> dict[str, rep.DataStructure]:
"""Compiles an equipment report.
Parameters
@@ 2177,15 2149,14 @@ class Compile:
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
# Sets the structure of where data gets pasted in the appropriate template
data = {
- "df": DataStructure(
- SheetStructure("Summary", "A8"),
+ "df": rep.DataStructure(
+ rep.SheetStructure("Summary", "A8", False),
self.output.df.copy(),
- self.output,
None,
)
}
@@ 2340,7 2311,7 @@ class Compile:
def national_account(
self, is_recursion: bool = False, pull_previous: bool = False
- ) -> dict[str, DataStructure]:
+ ) -> dict[str, rep.DataStructure]:
"""Compiles the national account report.
Parameters
@@ 2355,7 2326,7 @@ class Compile:
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
close = dates.close()[2]
@@ 2408,8 2379,8 @@ class Compile:
)
data = {
- "pivot": DataStructure(
- SheetStructure("ABI Data", "D1"), pivot, self.output, None
+ "pivot": rep.DataStructure(
+ rep.SheetStructure("ABI Data", "D1", False), pivot, None
),
}
@@ 2420,12 2391,12 @@ class Compile:
return data
- def client_slides(self) -> dict[str, DataStructure]:
+ def client_slides(self) -> dict[str, rep.DataStructure]:
"""Compiles a client_slides report.
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
# Data pivots that sum up everything we need
@@ 2480,28 2451,34 @@ class Compile:
).round()
data = {
- "revenue_location": DataStructure(
- SheetStructure("RevenueByLocation", "A1"), rev_lc, self.output, None
+ "revenue_location": rep.DataStructure(
+ rep.SheetStructure("RevenueByLocation", "A1", False),
+ rev_lc,
+ None,
),
- "revenue_product": DataStructure(
- SheetStructure("RevenueByProduct", "A1"), rev_pr, self.output, None
+ "revenue_product": rep.DataStructure(
+ rep.SheetStructure("RevenueByProduct", "A1", False),
+ rev_pr,
+ None,
),
- "dtp_location": DataStructure(
- SheetStructure("DaysToPay", "A1"), dtp_lc, self.output, None
+ "dtp_location": rep.DataStructure(
+ rep.SheetStructure("DaysToPay", "A1", False), dtp_lc, None
),
- "revenue_type": DataStructure(
- SheetStructure("RevenueByType", "A1"), rev_tp, self.output, None
+ "revenue_type": rep.DataStructure(
+ rep.SheetStructure("RevenueByType", "A1", False),
+ rev_tp,
+ None,
),
}
return data
- def rentals_rates(self) -> dict[str, DataStructure]:
+ def rentals_rates(self) -> dict[str, rep.DataStructure]:
"""Compiles a rentals and rates report.
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
df = self.output.df.copy()
@@ 2578,16 2555,18 @@ class Compile:
).reset_index(drop=True)
df.index.name = "Item"
- data = {"df": DataStructure(SheetStructure("ade", "B3"), df, self.output, None)}
+ data = {
+ "df": rep.DataStructure(rep.SheetStructure("ade", "B3", False), df, None)
+ }
return data
- def product_revenue(self) -> dict[str, DataStructure]:
+ def product_revenue(self) -> dict[str, rep.DataStructure]:
"""Compiles a product revenue report.
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
# Must create a separate listing DF as the template isn't a combined
@@ 2636,22 2615,22 @@ class Compile:
)
data = {
- "listing": DataStructure(
- SheetStructure("Summary", "A2"), listing, self.output, None
+ "listing": rep.DataStructure(
+ rep.SheetStructure("Summary", "A2", False), listing, None
),
- "values": DataStructure(
- SheetStructure("Summary", "I1"), values, self.output, None
+ "values": rep.DataStructure(
+ rep.SheetStructure("Summary", "I1", False), values, None
),
}
return data
- def rebate(self) -> dict[str, DataStructure]:
+ def rebate(self) -> dict[str, rep.DataStructure]:
"""Compiles a rebate report.
Returns
-------
- dict[str, DataStructure]
+ dict[str, rep.DataStructure]
"""
df = self.output.df.merge(
@@ 2663,8 2642,8 @@ class Compile:
on="invoiceNumber",
)
data = {
- "rebate": DataStructure(
- SheetStructure("Sheet1", "A1"), df, self.output, None
+ "rebate": rep.DataStructure(
+ rep.SheetStructure("Sheet1", "A1", False), df, None
),
}
@@ 2696,7 2675,7 @@ class File:
opened up for the user to modify.
compile : Compile
The Compile object.
- data : dict[str, DataStructure]
+ data : dict[str, rep.DataStructure]
The amalgamation of the compiled report and its spreadsheet output location
from compile.data.
output : Output
@@ 2714,7 2693,7 @@ class File:
self.open_file: bool = open_file
self.compile: Compile = compile
- self.data: dict[str, DataStructure] = self.compile.data
+ self.data: dict[str, rep.DataStructure] = self.compile.data
self.output: Output = self.compile.output
self.construct = self.output.construct
@@ 2762,24 2741,14 @@ class File:
if not report_dir.is_dir():
report_dir.mkdir()
- if is_template == True:
- try:
- shutil.copyfile(template_file, save_file) # type: ignore - impossible to reach here without is_template == True
- except FileNotFoundError:
- print(
- "This report requires a template, and none can be found on the server. Output is not guaranteed."
- )
- else:
- if is_template == True:
- try:
- if os.path.getmtime(save_file) < os.path.getmtime(template_file): # type: ignore - impossible as is_template == True
- if save_file.exists():
- save_file.unlink()
- shutil.copyfile(template_file, save_file) # type: ignore - unbound caught with NameError
- except FileNotFoundError:
- print(
- "This report requires a template, and none can be found on the server. Output is not guaranteed."
- )
+
+ if is_template == True:
+ if not save_file.exists():
+ shutil.copyfile(template_file, save_file) # type: ignore - template_file will always exist with is_template
+
+ if os.path.getmtime(save_file) < os.path.getmtime(template_file): # type: ignore - impossible as is_template == True
+ save_file.unlink()
+ shutil.copyfile(template_file, save_file) # type: ignore - unbound caught with NameError
return save_file, is_template
@@ 2795,7 2764,7 @@ class File:
pass
def _to_excel(self) -> None:
- """Saves the report as an Excel file following the SheetStructure."""
+ """Saves the report as an Excel file following the rep.SheetStructure."""
app = xw.App(visible=self.open_file, add_book=False)
try:
book = app.books.open(self.file)
@@ 2818,11 2787,12 @@ class File:
sht = book.sheets[sht_name]
rng = sht.range(data_struct.structure.cell)
+ # TODO: clear contents by manually getting range (# of rows/columns from DF.clear)
# full.expand("table").clear_contents()
rng.value = data_struct.data
- # full.api.AutoFilter()
+ # full.api.AutoFilter(Field=1)
- if not self.is_template:
+ if data_struct.structure.formatting:
if data_struct.pivot:
formatting.adhoc_pivot(
app, book, sht, rng, data_struct.data, data_struct.pivot
@@ 2830,11 2800,15 @@ class File:
else:
formatting.adhoc_data(sht, rng, data_struct.data)
+ if not self.is_template:
+ for sheet in book.sheets:
+ if sheet.name not in [
+ i.structure.sheet for i in self.data.values()
+ ]:
+ sheet.delete()
+
book.save(self.file)
- for sheet in book.sheets:
- if sheet.name not in [i.structure.sheet for i in self.data.values()]:
- sheet.delete()
# Closes out
if not self.open_file:
book.close()