Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,10 @@
## Goal
- Make **Python** a first class citizen for interacting with **Tabular models**.

## Some rules
- See [github actions](https://github.com/Curts0/PyTabular/actions) for checks run on pull request.
- We `docstring-coverage` to check for 100% docstring coverage.
- `flake8` also runs, but with a few extra packages. (pep8-naming, flake8-docstrings).
- Updates of any kind are welcome! Even just letting me know of the issues. Or updating docstrings...
- Limit any extra packages, see `pyproject.toml` for dependencies
- Install pre-commit. Pre-commit will run pytest, flake8, and docstr-coverage before push.
```powershell
pip install pre-commit
pre-commit install --hook-type pre-push
```
- **This will take a while...** pytest will open a PBIX file in repository and run tests on it... Eventually these tests will be run on a model that is not local.
## Info

## Documentation help
- Docstrings follow the google docstring convention. See [Example](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html).
- The `flake8-docstrings` will check that google docstring format is followed.
- Docstrings get converted to markdown with the `mkdocstring` package.
- Then gets converted to a site with the `mkdocs` package.
- Use `tox` for local testing.
- Will run docstring coverage, linter, and python versions needed to support w/ pytest.

## Misc
- Work will be distributed under a MIT license.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python_tabular"
version = "0.5.6"
version = "0.5.7"
authors = [
{ name="Curtis Stallings", email="curtisrstallings@gmail.com" },
]
Expand Down
66 changes: 66 additions & 0 deletions pytabular/currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""List of [unicode currencies](https://www.unicode.org/charts/PDF/U20A0.pdf).

Used to strip formatted values from DAX queries.
For example `$(12,345.67)` will output -> `-12345.67`.
See `logic_utils.clean_formatting` for more.
"""

unicode_list = [
"\u0024",
"\u00a2",
"\u00a3",
"\u00a4",
"\u00a5",
"\u0192",
"\u058f",
"\u060b",
"\u09f2",
"\u09f3",
"\u0af1",
"\u0bf9",
"\u0e3f",
"\u17db",
"\u2133",
"\u5143",
"\u5186",
"\u5706",
"\u5713",
"\ufdfc",
# "\u1E2FF",
"\u0024",
"\u20a0",
"\u20a1",
"\u20a2",
"\u20a3",
"\u20a4",
"\u20a5",
"\u20a6",
"\u20a7",
"\u20a8",
"\u20a9",
"\u20aa",
"\u20ab",
"\u20ac",
"\u20ad",
"\u20ae",
"\u20af",
"\u20b0",
"\u20b1",
"\u20b2",
"\u20b3",
"\u20b4",
"\u20b5",
"\u20b6",
"\u20b7",
"\u20b8",
"\u20b9",
"\u20ba",
"\u20bb",
"\u20bc",
"\u20bd",
"\u20be",
"\u20bf",
"\u20c0",
]

unicodes: str.maketrans = str.maketrans(dict.fromkeys(unicode_list, ""))
33 changes: 33 additions & 0 deletions pytabular/logic_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import os
from typing import Dict, List
import pandas as pd

from pytabular.currency import unicodes

from Microsoft.AnalysisServices.Tabular import DataType
from Microsoft.AnalysisServices.AdomdClient import AdomdDataReader

Expand Down Expand Up @@ -162,6 +165,28 @@ def get_sub_list(lst: list, n: int) -> list:
return [lst[i : i + n] for i in range(0, len(lst), n)]


def clean_formatting(query_result: str, sep: str = ",") -> float:
"""Attempts to clean DAX formatting.

For example, `$(12,345.67)` will output -> `-12345.67`.

Args:
query_result (str): The value given from `get_value_to_df`
sep (str, optional): The thousands separator. Defaults to ",".

Returns:
float: Value of DAX query cell.
"""
multiplier = 1
if "(" in query_result and ")" in query_result:
multiplier = -1
query_result = query_result.replace("(", "").replace(")", "")

query_result = query_result.replace(sep, "")

return float(query_result.translate(unicodes)) * multiplier


def get_value_to_df(query: AdomdDataReader, index: int):
"""Gets the values from the AdomdDataReader to convert to python df.

Expand All @@ -171,11 +196,19 @@ def get_value_to_df(query: AdomdDataReader, index: int):
query (AdomdDataReader): The AdomdDataReader .Net object.
index (int): Index of the value to perform the logic on.
"""
# TODO: Clean this up
if (
query.GetDataTypeName((index)) in ("Decimal")
and query.GetValue(index) is not None
):
return query.GetValue(index).ToDouble(query.GetValue(index))
elif query.GetDataTypeName((index)) in (
"String"
):
try:
return clean_formatting(query.GetValue(index))
except Exception:
return query.GetValue(index)
else:
return query.GetValue(index)

Expand Down
27 changes: 27 additions & 0 deletions test/test_3tabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@ def test_datatype_query(model):
assert result == query[0]


number_queries = (
pytest.param(("EVALUATE {1}", 1), id="basic"),
pytest.param(
("""EVALUATE {FORMAT(12345.67, "$#,##0.00;($#,##0.00)")}""", 12345.67),
id="$#,##0.00",
),
pytest.param(
("""EVALUATE {FORMAT(12345.00, "$#,##0.00;($#,##0.00)")}""", 12345.00),
id="$#,##0.00 w/o decimal",
),
pytest.param(
("""EVALUATE {FORMAT(-12345.67, "$#,##0.00;($#,##0.00)")}""", -12345.67),
id="$#,##0.00 w/ -",
),
pytest.param(
("""EVALUATE {FORMAT(12345.67, "#,##0.00;(#,##0.00)")}""", 12345.67),
id="#,##0.00",
),
)


@pytest.mark.parametrize("number_queries", number_queries)
def test_parsing_query(model, number_queries):
"""Assert that the proper query result stripping is happenign."""
assert model.query(number_queries[0]) == number_queries[1]


def test_file_query(model):
"""Test `query()` via a file."""
singlevaltest = get_test_path() + "\\singlevaltest.dax"
Expand Down