Skip to content

Commit f962536

Browse files
committed
Add is_formula field option
1 parent 34c1f04 commit f962536

File tree

8 files changed

+86
-17
lines changed

8 files changed

+86
-17
lines changed

.github/workflows/unit_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ jobs:
66
runs-on: ubuntu-latest
77
strategy:
88
matrix:
9-
python-version: ["3.7", "3.10"]
9+
python-version: ["3.10", "3.11", "3.12"]
1010
name: Python ${{ matrix.python-version }}
1111
steps:
1212
- uses: actions/checkout@v3

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test = [
2929
"mypy==0.961",
3030
"isort==5.10.1",
3131
"pytest==7.1.2",
32-
"autoflake==1.4",
32+
"autoflake==2.3.1",
3333
"types-requests==2.28.6",
3434
"coverage==6.4.4",
3535
]
@@ -43,7 +43,7 @@ line_length = 120
4343

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

4949
[tool.mypy]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ black>=24.3.0
66
mypy==0.961
77
isort==5.10.1
88
pytest==7.1.2
9-
autoflake==1.4
9+
autoflake==2.3.1
1010
types-requests==2.28.6
1111
coverage==6.4.4

src/pyfreedb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""PyFreeDB is a Python library that provides common and simple database abstractions on top of Google Sheets."""
22

3-
__version__ = "1.0.3"
3+
__version__ = "1.0.4"

src/pyfreedb/row/models.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,25 @@ class _Field(Generic[T]):
2020
_typ: Type[T]
2121
_column_name: str
2222
_field_name: str
23-
24-
def __init__(self, column_name: str = "") -> None:
23+
_is_formula: bool
24+
25+
def __init__(
26+
self,
27+
column_name: str = "",
28+
is_formula: bool = False,
29+
) -> None:
30+
"""Defines the internal representation of the model fields.
31+
This is where we can put per field custom config as well.
32+
33+
Args:
34+
column_name: an alias of the field name to represent the actual column name in the sheets.
35+
is_formula: a boolean indicating if the field is a formula or not. Only applicable for StringField.
36+
"""
2537
self._column_name = column_name
38+
self._is_formula = is_formula
2639

2740
def __set_name__(self, _: Any, name: str) -> None:
2841
self._field_name = name
29-
3042
if self._column_name == "":
3143
self._column_name = name
3244

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

56+
self._ensure_is_formula()
4457
return setattr(obj._data, self._field_name, value)
4558

59+
def _ensure_is_formula(self) -> None:
60+
if self._is_formula and self._typ is not str:
61+
raise TypeError(f"Field {self._field_name} must be a StringField when is_formula is true")
62+
4663
def _ensure_type(self, value: Any) -> None:
4764
if value is None or value is NotSet:
4865
return

src/pyfreedb/row/stmt.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,10 @@ def _get_raw_values(self) -> List[List[str]]:
183183
# Set _rid value according to the insert protocol.
184184
raw = ["=ROW()"]
185185

186-
for field_name in row._fields:
187-
value = _escape_val(getattr(row, field_name))
186+
for field_name, field in row._fields.items():
187+
field_is_formula = field._is_formula
188+
raw_value = getattr(row, field_name)
189+
value = raw_value if field_is_formula else _escape_val(raw_value)
188190
raw.append(value)
189191

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

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

322325

323-
__pdoc__ = {}
324-
__pdoc__["CountStmt"] = CountStmt.__init__.__doc__
325-
__pdoc__["SelectStmt"] = SelectStmt.__init__.__doc__
326-
__pdoc__["InsertStmt"] = InsertStmt.__init__.__doc__
327-
__pdoc__["DeleteStmt"] = DeleteStmt.__init__.__doc__
328-
__pdoc__["UpdateStmt"] = UpdateStmt.__init__.__doc__
326+
__pdoc__ = {
327+
"CountStmt": CountStmt.__init__.__doc__,
328+
"SelectStmt": SelectStmt.__init__.__doc__,
329+
"InsertStmt": InsertStmt.__init__.__doc__,
330+
"DeleteStmt": DeleteStmt.__init__.__doc__,
331+
"UpdateStmt": UpdateStmt.__init__.__doc__,
332+
}

tests/integration/test_gsheet_row_store.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,40 @@ def test_gsheet_row_number_boundaries(config: IntegrationTestConfig) -> None:
118118

119119
returned_rows = row_store.select().limit(1).execute()
120120
assert [expected_rows[1]] == returned_rows
121+
122+
123+
class InsertModel(models.Model):
124+
value = models.StringField(is_formula=True)
125+
126+
127+
class ReadModel(models.Model):
128+
value = models.IntegerField()
129+
130+
131+
@pytest.mark.integration
132+
def test_gsheet_row_formula(config: IntegrationTestConfig) -> None:
133+
insert_store = GoogleSheetRowStore(
134+
config.auth_client,
135+
spreadsheet_id=config.spreadsheet_id,
136+
sheet_name="row_store_formula",
137+
object_cls=InsertModel,
138+
)
139+
read_store = GoogleSheetRowStore(
140+
config.auth_client,
141+
spreadsheet_id=config.spreadsheet_id,
142+
sheet_name="row_store_formula",
143+
object_cls=ReadModel,
144+
)
145+
146+
rows = [InsertModel(value="=ROW()-1")]
147+
insert_store.insert(rows).execute()
148+
149+
expected_rows = [ReadModel(value=1)]
150+
returned_rows = read_store.select().execute()
151+
assert expected_rows == returned_rows
152+
153+
insert_store.update({"value": "=ROW()"}).execute()
154+
155+
expected_rows = [ReadModel(value=2)]
156+
returned_rows = read_store.select().execute()
157+
assert expected_rows == returned_rows

tests/row/test_models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,14 @@ def test_is_ieee754_safe_integer() -> None:
8989
assert not models._is_ieee754_safe_integer(9007199254740993)
9090

9191
assert models._is_ieee754_safe_integer(1 << 54)
92+
93+
94+
class FormulaTest(models.Model):
95+
string_no_formula = models.StringField(is_formula=False)
96+
string_with_formula = models.StringField(is_formula=True)
97+
98+
99+
def test_field_is_formula() -> None:
100+
f = FormulaTest(string_no_formula="", string_with_formula="")
101+
assert not f._fields["string_no_formula"]._is_formula
102+
assert f._fields["string_with_formula"]._is_formula

0 commit comments

Comments
 (0)