Skip to content

Commit

Permalink
Add is_formula field option
Browse files Browse the repository at this point in the history
  • Loading branch information
edocsss committed Jan 24, 2025
1 parent 34c1f04 commit 30e2549
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 21 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/integration_test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Integration Test
on:
pull_request_review:
types: [submitted]
pull_request:
types: [opened, synchronize]

env:
INTEGRATION_TEST_AUTH_JSON: ${{ secrets.INTEGRATION_TEST_AUTH_JSON }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.7", "3.10"]
python-version: ["3.10", "3.11", "3.12"]
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v3
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ Home = "https://github.com/FreeLeh/PyFreeDB"

[project.optional-dependencies]
test = [
"black==22.3.0",
"black==24.10.0",
"mypy==0.961",
"isort==5.10.1",
"pytest==7.1.2",
"autoflake==1.4",
"autoflake==2.3.1",
"types-requests==2.28.6",
"coverage==6.4.4",
]
Expand All @@ -43,7 +43,7 @@ line_length = 120

[tool.black]
line-length = 120
target-version = ['py37', 'py38']
target-version = ['py37', 'py38', 'py39', 'py310', 'py311', 'py312']
include = '\.pyi?$'

[tool.mypy]
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ google-api-python-client==2.51.0
google-auth-httplib2==0.1.0
google-auth-oauthlib==0.5.2
requests>=2.30, <3
black>=24.3.0
black>=24.10.0
mypy==0.961
isort==5.10.1
pytest==7.1.2
autoflake==1.4
autoflake==2.3.1
types-requests==2.28.6
coverage==6.4.4
2 changes: 1 addition & 1 deletion src/pyfreedb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""PyFreeDB is a Python library that provides common and simple database abstractions on top of Google Sheets."""

__version__ = "1.0.3"
__version__ = "1.0.4"
23 changes: 20 additions & 3 deletions src/pyfreedb/row/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,25 @@ class _Field(Generic[T]):
_typ: Type[T]
_column_name: str
_field_name: str

def __init__(self, column_name: str = "") -> None:
_is_formula: bool

def __init__(
self,
column_name: str = "",
is_formula: bool = False,
) -> None:
"""Defines the internal representation of the model fields.
This is where we can put per field custom config as well.
Args:
column_name: an alias of the field name to represent the actual column name in the sheets.
is_formula: a boolean indicating if the field is a formula or not. Only applicable for StringField.
"""
self._column_name = column_name
self._is_formula = is_formula

def __set_name__(self, _: Any, name: str) -> None:
self._field_name = name

if self._column_name == "":
self._column_name = name

Expand All @@ -41,8 +53,13 @@ def __set__(self, obj: Any, value: Optional[T]) -> None:
# as float by Google Sheet's API.
value = self._typ(value) # type: ignore [call-arg]

self._ensure_is_formula()
return setattr(obj._data, self._field_name, value)

def _ensure_is_formula(self) -> None:
if self._is_formula and self._typ is not str:
raise TypeError(f"Field {self._field_name} must be a StringField when is_formula is true")

def _ensure_type(self, value: Any) -> None:
if value is None or value is NotSet:
return
Expand Down
22 changes: 13 additions & 9 deletions src/pyfreedb/row/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ def _get_raw_values(self) -> List[List[str]]:
# Set _rid value according to the insert protocol.
raw = ["=ROW()"]

for field_name in row._fields:
value = _escape_val(getattr(row, field_name))
for field_name, field in row._fields.items():
field_is_formula = field._is_formula
raw_value = getattr(row, field_name)
value = raw_value if field_is_formula else _escape_val(raw_value)
raw.append(value)

raw_values.append(raw)
Expand Down Expand Up @@ -246,7 +248,8 @@ def _update_rows(self, indices: List[int]) -> None:
if col not in self._update_values:
continue

value = _escape_val(self._update_values[col])
field_is_formula = self._store._object_cls._fields[col]._is_formula
value = self._update_values[col] if field_is_formula else _escape_val(self._update_values[col])
cell_selector = _A1CellSelector.from_rc(col_idx + 2, row_idx)
update_range = _A1Range(self._store._sheet_name, cell_selector, cell_selector)
requests.append(_BatchUpdateRowsRequest(update_range, [[value]]))
Expand Down Expand Up @@ -320,9 +323,10 @@ def _escape_val(val: Any) -> Any:
return val


__pdoc__ = {}
__pdoc__["CountStmt"] = CountStmt.__init__.__doc__
__pdoc__["SelectStmt"] = SelectStmt.__init__.__doc__
__pdoc__["InsertStmt"] = InsertStmt.__init__.__doc__
__pdoc__["DeleteStmt"] = DeleteStmt.__init__.__doc__
__pdoc__["UpdateStmt"] = UpdateStmt.__init__.__doc__
__pdoc__ = {
"CountStmt": CountStmt.__init__.__doc__,
"SelectStmt": SelectStmt.__init__.__doc__,
"InsertStmt": InsertStmt.__init__.__doc__,
"DeleteStmt": DeleteStmt.__init__.__doc__,
"UpdateStmt": UpdateStmt.__init__.__doc__,
}
37 changes: 37 additions & 0 deletions tests/integration/test_gsheet_row_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,40 @@ def test_gsheet_row_number_boundaries(config: IntegrationTestConfig) -> None:

returned_rows = row_store.select().limit(1).execute()
assert [expected_rows[1]] == returned_rows


class InsertModel(models.Model):
value = models.StringField(is_formula=True)


class ReadModel(models.Model):
value = models.IntegerField()


@pytest.mark.integration
def test_gsheet_row_formula(config: IntegrationTestConfig) -> None:
insert_store = GoogleSheetRowStore(
config.auth_client,
spreadsheet_id=config.spreadsheet_id,
sheet_name="row_store_formula",
object_cls=InsertModel,
)
read_store = GoogleSheetRowStore(
config.auth_client,
spreadsheet_id=config.spreadsheet_id,
sheet_name="row_store_formula",
object_cls=ReadModel,
)

rows = [InsertModel(value="=ROW()-1")]
insert_store.insert(rows).execute()

expected_rows = [ReadModel(value=1)]
returned_rows = read_store.select().execute()
assert expected_rows == returned_rows

insert_store.update({"value": "=ROW()"}).execute()

expected_rows = [ReadModel(value=2)]
returned_rows = read_store.select().execute()
assert expected_rows == returned_rows
11 changes: 11 additions & 0 deletions tests/row/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,14 @@ def test_is_ieee754_safe_integer() -> None:
assert not models._is_ieee754_safe_integer(9007199254740993)

assert models._is_ieee754_safe_integer(1 << 54)


class FormulaTest(models.Model):
string_no_formula = models.StringField(is_formula=False)
string_with_formula = models.StringField(is_formula=True)


def test_field_is_formula() -> None:
f = FormulaTest(string_no_formula="", string_with_formula="")
assert not f._fields["string_no_formula"]._is_formula
assert f._fields["string_with_formula"]._is_formula

0 comments on commit 30e2549

Please sign in to comment.