Skip to content

Commit fdec7f7

Browse files
committed
Merge remote-tracking branch 'origin/master'
# Conflicts: # pyproject.toml
2 parents 7c06fff + 30714bf commit fdec7f7

3 files changed

Lines changed: 108 additions & 9 deletions

File tree

docs/usage.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
# PyFusion #
1+
# Usage
22

3-
PyFusion is the Python SDK for the Fusion platform API.
43

5-
## Installation
4+
## Import Fusion
65

7-
```bash
8-
pip install pyfusion
9-
```
6+
```
7+
from fusion import Fusion
8+
```
9+
## Fusion Object
10+
```
11+
fusion = Fusion()
12+
```

fusion/fusion_filesystem.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from urllib.parse import quote, urljoin
99

1010
import pandas as pd
11+
from async_retrying import retry
1112
from fsspec.callbacks import _DEFAULT_CALLBACK
1213
from fsspec.implementations.http import HTTPFile, HTTPFileSystem, sync, sync_wrapper
1314
from fsspec.utils import nullcontext
@@ -418,6 +419,98 @@ def _construct_headers(
418419

419420
return headers, headers_chunk_lst
420421

422+
def _cloud_copy(
423+
self,
424+
lpath,
425+
rpath,
426+
chunk_size=5 * 2**20,
427+
callback=_DEFAULT_CALLBACK,
428+
method="put",
429+
dt_from=None,
430+
dt_to=None,
431+
dt_created=None,
432+
):
433+
async def _get_operation_id(session) -> dict:
434+
async with session.post(
435+
rpath + "/operationType/upload", **self.kwargs
436+
) as r:
437+
await self._async_raise_not_found_for_status(
438+
r, rpath + "/operationType/upload"
439+
)
440+
return await r.json()
441+
442+
async def _finish_operation(session, operation_id, kw):
443+
async with session.post(
444+
url=rpath + f"/operations/upload?operationId={operation_id}",
445+
json={"parts": resps},
446+
**kw,
447+
) as r:
448+
await self._async_raise_not_found_for_status(
449+
r, rpath + f"/operations/upload?operationId={operation_id}"
450+
)
451+
452+
def put_data(session):
453+
@retry(attempts=3)
454+
async def _meth(session, url, kw):
455+
meth = getattr(session, method)
456+
async with meth(url=url, data=chunk, **kw) as resp:
457+
await self._async_raise_not_found_for_status(resp, url)
458+
return await resp.json()
459+
460+
context = nullcontext(lpath)
461+
462+
with context as f:
463+
callback.get_size(getattr(f, "size", None))
464+
465+
chunk = f.read(chunk_size)
466+
i = 0
467+
headers_chunks = {"Content-Type": "application/octet-stream", "Digest": ""}
468+
while chunk:
469+
hash_sha256_chunk = hashlib.sha256()
470+
hash_sha256_chunk.update(chunk)
471+
hash_sha256_lst[0].update(hash_sha256_chunk.digest())
472+
headers_chunks = deepcopy(headers_chunks)
473+
headers_chunks["Digest"] = (
474+
"SHA-256=" + base64.b64encode(hash_sha256_chunk.digest()).decode()
475+
)
476+
kw = self.kwargs.copy()
477+
kw.update({"headers": headers_chunks})
478+
url = (
479+
rpath
480+
+ f"/operations/upload?operationId={operation_id}&partNumber={i+1}"
481+
)
482+
yield sync(self.loop, _meth, session, url, kw)
483+
i += 1
484+
callback.relative_update(len(chunk))
485+
chunk = f.read(chunk_size)
486+
487+
session = sync(self.loop, self.set_session)
488+
method = method.lower()
489+
if method not in ("put", "post"):
490+
raise ValueError(f"method has to be either 'post' or 'put', not {method!r}")
491+
hash_sha256 = hashlib.sha256()
492+
hash_sha256_lst = [hash_sha256]
493+
headers = {
494+
"Content-Type": "application/json",
495+
"x-jpmc-distribution-created-date": dt_created,
496+
"x-jpmc-distribution-from-date": dt_from,
497+
"x-jpmc-distribution-to-date": dt_to,
498+
"Digest": "", # to be changed to x-jpmc-digest
499+
}
500+
lpath.seek(0)
501+
kw = self.kwargs.copy()
502+
kw.update({"headers": headers})
503+
operation_id = sync(self.loop, _get_operation_id, session)["operationId"]
504+
resps = []
505+
for resp in put_data(session):
506+
resps.append(resp)
507+
508+
hash_sha256 = hash_sha256_lst[0]
509+
headers["Digest"] = "SHA-256=" + base64.b64encode(hash_sha256.digest()).decode()
510+
kw = self.kwargs.copy()
511+
kw.update({"headers": headers})
512+
sync(self.loop, _finish_operation, session, operation_id, kw)
513+
421514
def put(
422515
self,
423516
lpath,
@@ -455,11 +548,14 @@ def put(
455548
dt_to = pd.Timestamp(to_date).strftime("%Y-%m-%d")
456549

457550
dt_created = pd.Timestamp.now().strftime("%Y-%m-%d")
458-
551+
rpath = self._decorate_url(rpath)
552+
if type(lpath).__name__ in ["S3File"]:
553+
return self._cloud_copy(
554+
lpath, rpath, chunk_size, callback, method, dt_from, dt_to, dt_created
555+
)
459556
headers, chunk_headers_lst = self._construct_headers(
460557
lpath, dt_from, dt_to, dt_created, chunk_size, multipart
461558
)
462-
rpath = self._decorate_url(rpath)
463559
kwargs.update({"headers": headers})
464560
if multipart:
465561
kwargs.update({"chunk_headers_lst": chunk_headers_lst})

fusion/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ async def _refresh_fusion_token_data():
663663
if "timeout" in kwargs:
664664
timeout = aiohttp.ClientTimeout(total=kwargs["timeout"])
665665
else:
666-
timeout = aiohttp.ClientTimeout(total=30 * 60) # default 30min timeout
666+
timeout = aiohttp.ClientTimeout(total=60 * 60) # default 60min timeout
667667
session = FusionAiohttpSession(
668668
trace_configs=[trace_config], trust_env=True, timeout=timeout
669669
)

0 commit comments

Comments
 (0)