Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(framework:skip) Add Flower File Storage interface and disk based implementation #3619

Merged
merged 27 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2d3398a
feat(framework:skip) Add Flower File Storage interface and disk based…
tanertopal Jun 16, 2024
8f4d7c2
feat(framework:skip) Add Flower File Storage interface and disk based…
tanertopal Jun 16, 2024
b06d6cd
Merge branch 'add_first_ffs_impl' of github.com:adap/flower into add_…
tanertopal Jun 16, 2024
5f57946
Fix type errors
tanertopal Jun 16, 2024
adeeb3c
Fix
tanertopal Jun 16, 2024
bf4ff86
Merge branch 'main' into add_first_ffs_impl
tanertopal Jun 16, 2024
6af4ae5
Merge branch 'main' into add_first_ffs_impl
danieljanes Jun 17, 2024
ab76f27
Fix docstring
charlesbvll Jul 19, 2024
d565c87
Sort _all_
charlesbvll Jul 19, 2024
fb0de76
Merge branch 'main' into add_first_ffs_impl
charlesbvll Jul 19, 2024
4345891
Use Pathlib instead of os.path
charlesbvll Jul 19, 2024
d570b26
Add makedir
charlesbvll Jul 30, 2024
8c213f0
Fix makedir
charlesbvll Jul 30, 2024
c6d752d
Merge branch 'main' into add_first_ffs_impl
charlesbvll Aug 7, 2024
3912389
Merge branch 'main' into add_first_ffs_impl
danieljanes Aug 8, 2024
cfba12b
Merge branch 'main' into add_first_ffs_impl
charlesbvll Aug 8, 2024
09479e1
Merge branch 'main' into add_first_ffs_impl
charlesbvll Aug 11, 2024
8264956
Fix ffs
charlesbvll Aug 11, 2024
fa8ab39
Improve docstrings
charlesbvll Aug 11, 2024
bd3d1bf
Merge branch 'main' into add_first_ffs_impl
danieljanes Aug 12, 2024
04e89dc
Update src/py/flwr/server/superlink/ffs/disk_ffs.py
danieljanes Aug 12, 2024
24ddd6d
Remove ffs factory
charlesbvll Aug 12, 2024
f359ad0
Merge branch 'add_first_ffs_impl' of https://github.com/adap/flower i…
charlesbvll Aug 12, 2024
35c2bd1
Update src/py/flwr/server/superlink/ffs/disk_ffs.py
danieljanes Aug 12, 2024
8ef14fb
Update src/py/flwr/server/superlink/ffs/disk_ffs.py
danieljanes Aug 12, 2024
5fe921c
Update src/py/flwr/server/superlink/ffs/ffs.py
danieljanes Aug 12, 2024
6185464
Update src/py/flwr/server/superlink/ffs/disk_ffs.py
danieljanes Aug 12, 2024
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
24 changes: 24 additions & 0 deletions src/py/flwr/server/superlink/ffs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Flower File Storage for large objects."""


from .disk_ffs import DiskFfs as DiskFfs
from .ffs import Ffs as Ffs

__all__ = [
"DiskFfs",
"Ffs",
]
103 changes: 103 additions & 0 deletions src/py/flwr/server/superlink/ffs/disk_ffs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Disk based Flower File Storage."""

import hashlib
import json
from pathlib import Path
from typing import Dict, List, Tuple

from flwr.server.superlink.ffs.ffs import Ffs


class DiskFfs(Ffs): # pylint: disable=R0904
"""Disk based Flower File Storage interface for large objects."""
danieljanes marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, base_dir: str) -> None:
danieljanes marked this conversation as resolved.
Show resolved Hide resolved
"""Create a new DiskFfs instance.

Parameters
----------
base_dir : str
The base directory to store the objects.
"""
self.base_dir = Path(base_dir)

def put(self, content: bytes, meta: Dict[str, str]) -> str:
"""Store bytes and metadata and returns sha256hex hash of data as str.
danieljanes marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
content : bytes
The content to be stored.
meta : Dict[str, str]
The metadata to be stored.

Returns
-------
str
danieljanes marked this conversation as resolved.
Show resolved Hide resolved
The key (sha256hex hash) of the content.
"""
content_hash = hashlib.sha256(content).hexdigest()

self.base_dir.mkdir(exist_ok=True, parents=True)
(self.base_dir / content_hash).write_bytes(content)
(self.base_dir / f"{content_hash}.META").write_text(json.dumps(meta))

return content_hash

def get(self, key: str) -> Tuple[bytes, Dict[str, str]]:
"""Return tuple containing the object content and metadata.

Parameters
----------
key : str
The sha256hex hash of the object to be retrieved.

Returns
-------
Tuple[bytes, Dict[str, str]]
A tuple containing the object content and metadata.
"""
content = (self.base_dir / key).read_bytes()
meta = json.loads((self.base_dir / f"{key}.META").read_text())

return content, meta

def delete(self, key: str) -> None:
"""Delete object with hash.

Parameters
----------
key : str
The sha256hex hash of the object to be deleted.
"""
(self.base_dir / key).unlink()
(self.base_dir / f"{key}.META").unlink()

def list(self) -> List[str]:
"""List all keys.

Can be used to list all keys in the storage and e.g. to clean up
the storage with the delete method.
danieljanes marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
List[str]
A list of all available keys.
"""
return [
item.name for item in self.base_dir.iterdir() if not item.suffix == ".META"
]
79 changes: 79 additions & 0 deletions src/py/flwr/server/superlink/ffs/ffs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Abstract base class for Flower File Storage interface."""


import abc
from typing import Dict, List, Tuple


class Ffs(abc.ABC): # pylint: disable=R0904
"""Abstract Flower File Storage interface for large objects."""

@abc.abstractmethod
def put(self, content: bytes, meta: Dict[str, str]) -> str:
"""Store bytes and metadata and return sha256hex hash of data as str.

Parameters
----------
content : bytes
The content to be stored.
meta : Dict[str, str]
The metadata to be stored.

Returns
-------
str
danieljanes marked this conversation as resolved.
Show resolved Hide resolved
The key (sha256hex hash) of the content.
"""

@abc.abstractmethod
def get(self, key: str) -> Tuple[bytes, Dict[str, str]]:
"""Return tuple containing the object content and metadata.

Parameters
----------
key : str
The key (sha256hex hash) of the object to be retrieved.

Returns
-------
Tuple[bytes, Dict[str, str]]
A tuple containing the object content and metadata.
"""

@abc.abstractmethod
def delete(self, key: str) -> None:
"""Delete object with hash.

Parameters
----------
key : str
The key (sha256hex hash) of the object to be deleted.
"""

@abc.abstractmethod
def list(self) -> List[str]:
"""List keys of all stored objects.

Return all available keys in this `Ffs` instance.
This can be combined with, for example,
the `delete` method to delete objects.

Returns
-------
List[str]
A list of all available keys.
"""
45 changes: 45 additions & 0 deletions src/py/flwr/server/superlink/ffs/ffs_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Factory class that creates Ffs instances."""


from logging import DEBUG
from typing import Optional

from flwr.common.logger import log

from .disk_ffs import DiskFfs
from .ffs import Ffs


class FfsFactory:
charlesbvll marked this conversation as resolved.
Show resolved Hide resolved
"""Factory class that creates Ffs instances.

Parameters
----------
base_dir : str
The base directory to store the objects.
"""

def __init__(self, base_dir: str) -> None:
self.base_dir = base_dir
self.ffs_instance: Optional[Ffs] = None

def ffs(self) -> Ffs:
"""Return a Ffs instance and create it, if necessary."""
# SqliteState
ffs = DiskFfs(self.base_dir)
log(DEBUG, "Using Disk Flower File System")
return ffs
Loading
Loading