Skip to content

Commit f45872b

Browse files
Merge remote-tracking branch 'upstream/main' into fix/#2409
2 parents 478fc70 + a4e72e4 commit f45872b

8 files changed

Lines changed: 310 additions & 127 deletions

File tree

awswrangler/dynamodb/_read.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ def _convert_condition_base_to_expression(
456456
@_utils.validate_distributed_kwargs(
457457
unsupported_kwargs=["boto3_session", "dtype_backend"],
458458
)
459-
def read_items( # noqa: PLR0912
459+
def read_items( # noqa: PLR0912, PLR0915
460460
table_name: str,
461461
index_name: str | None = None,
462462
partition_values: Sequence[Any] | None = None,
@@ -475,6 +475,7 @@ def read_items( # noqa: PLR0912
475475
use_threads: bool | int = True,
476476
boto3_session: boto3.Session | None = None,
477477
pyarrow_additional_kwargs: dict[str, Any] | None = None,
478+
key_schema: list[dict[str, str]] | None = None,
478479
) -> pd.DataFrame | Iterator[pd.DataFrame] | _ItemsListType | Iterator[_ItemsListType]:
479480
"""Read items from given DynamoDB table.
480481
@@ -551,6 +552,10 @@ def read_items( # noqa: PLR0912
551552
Forwarded to `to_pandas` method converting from PyArrow tables to Pandas DataFrame.
552553
Valid values include "split_blocks", "self_destruct", "ignore_metadata".
553554
e.g. pyarrow_additional_kwargs={'split_blocks': True}.
555+
key_schema
556+
Key schema of the table (e.g. `[{"AttributeName": "key", "KeyType": "HASH"}]`).
557+
If provided, the library will bypass the `DescribeTable` API call, which can
558+
reduce network latency and prevent API throttling. Defaults to None.
554559
555560
Raises
556561
------
@@ -657,7 +662,10 @@ def read_items( # noqa: PLR0912
657662
# Extract key schema
658663
dynamodb_client = _utils.client(service_name="dynamodb", session=boto3_session)
659664
serializer = TypeSerializer()
660-
table_key_schema = dynamodb_client.describe_table(TableName=table_name)["Table"]["KeySchema"]
665+
if key_schema:
666+
table_key_schema = key_schema
667+
else:
668+
table_key_schema = dynamodb_client.describe_table(TableName=table_name)["Table"]["KeySchema"]
661669

662670
# Detect sort key, if any
663671
if len(table_key_schema) == 1:

awswrangler/dynamodb/_read.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def read_items(
6868
use_threads: bool | int = ...,
6969
boto3_session: boto3.Session | None = ...,
7070
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
71+
key_schema: list[dict[str, str]] | None = ...,
7172
) -> pd.DataFrame: ...
7273
@overload
7374
def read_items(
@@ -90,6 +91,7 @@ def read_items(
9091
use_threads: bool | int = ...,
9192
boto3_session: boto3.Session | None = ...,
9293
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
94+
key_schema: list[dict[str, str]] | None = ...,
9395
) -> Iterator[pd.DataFrame]: ...
9496
@overload
9597
def read_items(
@@ -112,6 +114,7 @@ def read_items(
112114
use_threads: bool | int = ...,
113115
boto3_session: boto3.Session | None = ...,
114116
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
117+
key_schema: list[dict[str, str]] | None = ...,
115118
) -> _ItemsListType: ...
116119
@overload
117120
def read_items(
@@ -134,6 +137,7 @@ def read_items(
134137
use_threads: bool | int = ...,
135138
boto3_session: boto3.Session | None = ...,
136139
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
140+
key_schema: list[dict[str, str]] | None = ...,
137141
) -> Iterator[_ItemsListType]: ...
138142
@overload
139143
def read_items(
@@ -156,6 +160,7 @@ def read_items(
156160
use_threads: bool | int = ...,
157161
boto3_session: boto3.Session | None = ...,
158162
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
163+
key_schema: list[dict[str, str]] | None = ...,
159164
) -> pd.DataFrame | _ItemsListType: ...
160165
@overload
161166
def read_items(
@@ -178,6 +183,7 @@ def read_items(
178183
use_threads: bool | int = ...,
179184
boto3_session: boto3.Session | None = ...,
180185
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
186+
key_schema: list[dict[str, str]] | None = ...,
181187
) -> Iterator[pd.DataFrame] | Iterator[_ItemsListType]: ...
182188
@overload
183189
def read_items(
@@ -200,6 +206,7 @@ def read_items(
200206
use_threads: bool | int = ...,
201207
boto3_session: boto3.Session | None = ...,
202208
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
209+
key_schema: list[dict[str, str]] | None = ...,
203210
) -> pd.DataFrame | Iterator[pd.DataFrame]: ...
204211
@overload
205212
def read_items(
@@ -222,6 +229,7 @@ def read_items(
222229
use_threads: bool | int = ...,
223230
boto3_session: boto3.Session | None = ...,
224231
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
232+
key_schema: list[dict[str, str]] | None = ...,
225233
) -> _ItemsListType | Iterator[_ItemsListType]: ...
226234
@overload
227235
def read_items(
@@ -244,4 +252,5 @@ def read_items(
244252
use_threads: bool | int = ...,
245253
boto3_session: boto3.Session | None = ...,
246254
pyarrow_additional_kwargs: dict[str, Any] | None = ...,
255+
key_schema: list[dict[str, str]] | None = ...,
247256
) -> pd.DataFrame | Iterator[pd.DataFrame] | _ItemsListType | Iterator[_ItemsListType]: ...

awswrangler/neptune/_neptune.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def to_property_graph(
170170
... )
171171
"""
172172
# check if ~id and ~label column exist and if not throw error
173-
g = gremlin.traversal().withGraph(gremlin.Graph())
173+
g = gremlin.Graph().traversal()
174174
is_edge_df = False
175175
is_update_df = True
176176
if "~id" in df.columns:
@@ -203,6 +203,24 @@ def to_property_graph(
203203
return _run_gremlin_insert(client, g)
204204

205205

206+
# SPARQL 1.1 IRIREF grammar: '<' ([^<>"{}|^`\]-[#x00-#x20])* '>'
207+
# A cell value spliced between '<' and '>' must contain only the characters allowed
208+
# inside the IRIREF token. Anything else can close the token and inject arbitrary
209+
# SPARQL UPDATE syntax (DELETE / DROP / LOAD / ...).
210+
_IRIREF_INNER_RE = re.compile(r"^[^\x00-\x20<>\"{}|^`\\]*$")
211+
212+
213+
def _validate_iriref_cell(value: Any, column: str, row_index: int) -> str:
214+
text = str(value)
215+
if not _IRIREF_INNER_RE.match(text):
216+
raise exceptions.InvalidArgumentValue(
217+
f"Value in column {column!r} at row index {row_index} is not a valid IRI: "
218+
f"{text!r}. Cells written by `to_rdf_graph` must conform to the SPARQL "
219+
'IRIREF grammar (no whitespace, control characters, or any of <>"{}|^`\\).'
220+
)
221+
return text
222+
223+
206224
@_utils.check_optional_dependency(sparql, "SPARQLWrapper")
207225
def to_rdf_graph(
208226
client: NeptuneClient,
@@ -267,14 +285,18 @@ def to_rdf_graph(
267285
query = ""
268286
# Loop through items in the DF
269287
for i, (_, row) in enumerate(df.iterrows()):
288+
subject = _validate_iriref_cell(row[subject_column], subject_column, i)
289+
predicate = _validate_iriref_cell(row[predicate_column], predicate_column, i)
290+
obj = _validate_iriref_cell(row[object_column], object_column, i)
270291
# build up a query
271292
if is_quads:
272-
insert = f"""INSERT DATA {{ GRAPH <{row[graph_column]}> {{<{row[subject_column]}>
273-
<{str(row[predicate_column])}> <{row[object_column]}> . }} }}; """
293+
graph = _validate_iriref_cell(row[graph_column], graph_column, i)
294+
insert = f"""INSERT DATA {{ GRAPH <{graph}> {{<{subject}>
295+
<{predicate}> <{obj}> . }} }}; """
274296
query = query + insert
275297
else:
276-
insert = f"""INSERT DATA {{ <{row[subject_column]}> <{str(row[predicate_column])}>
277-
<{row[object_column]}> . }}; """
298+
insert = f"""INSERT DATA {{ <{subject}> <{predicate}>
299+
<{obj}> . }}; """
278300
query = query + insert
279301
# run the query
280302
if i > 0 and i % batch_size == 0:

awswrangler/redshift/_write.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ def copy( # noqa: PLR0913
574574
precombine_key: str | None = None,
575575
use_column_names: bool = False,
576576
add_new_columns: bool = False,
577+
pyarrow_additional_kwargs: dict[str, str] | None = None,
577578
) -> None:
578579
"""Load Pandas DataFrame as a Table on Amazon Redshift using parquet files on S3 as stage.
579580
@@ -687,6 +688,9 @@ def copy( # noqa: PLR0913
687688
inserted into the database columns `col1` and `col3`.
688689
add_new_columns
689690
If True, it automatically adds the new DataFrame columns into the target table.
691+
pyarrow_additional_kwargs
692+
Forwarded to pyarrow.
693+
e.g. pyarrow_additional_kwargs={'coerce_timestamps': 'us', 'allow_truncated_timestamps': False}
690694
691695
Examples
692696
--------
@@ -715,6 +719,7 @@ def copy( # noqa: PLR0913
715719
s3.to_parquet(
716720
df=df,
717721
path=path,
722+
pyarrow_additional_kwargs=pyarrow_additional_kwargs or {},
718723
index=index,
719724
dataset=True,
720725
mode="append",

tests/unit/test_moto.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -760,14 +760,3 @@ def test_extract_ctas_manifest_paths_cross_bucket_raises(moto_s3_client: "S3Clie
760760

761761
with pytest.raises(InvalidArgumentValue, match="unexpected bucket"):
762762
_extract_ctas_manifest_paths(path=f"s3://bucket/{manifest_key}")
763-
764-
765-
def test_csv_pandas_mode_append(moto_s3_client: "S3Client") -> None:
766-
path = "s3://bucket/test_append.csv"
767-
df1 = pd.DataFrame({"col": [1, 2, 3]})
768-
df2 = pd.DataFrame({"col": [4, 5, 6]})
769-
wr.s3.to_csv(df=df1, path=path, index=False)
770-
wr.s3.to_csv(df=df2, path=path, index=False, pandas_mode="a", header=False)
771-
result = wr.s3.read_csv(path=path)
772-
assert len(result) == 6
773-
assert list(result["col"]) == [1, 2, 3, 4, 5, 6]

0 commit comments

Comments
 (0)