-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implemented Local and Remote Stub Sources (#18)
* feat(stubs): Initial Setup for stubs.source Module * feat(utils): Url Utility Functions Added Url related utility functions * test(stubs): Initial Tests for source module * feat: Resolve Path to both Local and Remote Source Added ready method to resolve path to a local or remote stub. In the case of a remote, the source is first downloaded/unpacked into a temporary directory.
- Loading branch information
Showing
8 changed files
with
279 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,14 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
"""Module for stub handling.""" | ||
""" | ||
micropy.stubs | ||
~~~~~~~~~~~~~~ | ||
This module contains all functionality relating | ||
to stub files/frozen modules and their usage in MicropyCli | ||
""" | ||
|
||
from . import source | ||
from .stubs import StubManager | ||
|
||
__all__ = ['StubManager'] | ||
__all__ = ['StubManager', 'source'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
micropy.stubs.source | ||
~~~~~~~~~~~~~~ | ||
This module contains abstractions for handling stub sources | ||
and their location. | ||
""" | ||
|
||
|
||
import io | ||
import shutil | ||
import tarfile | ||
import tempfile | ||
from contextlib import contextmanager | ||
from functools import partial | ||
from pathlib import Path | ||
|
||
import requests | ||
|
||
from micropy import utils | ||
from micropy.logger import Log | ||
|
||
|
||
class StubSource: | ||
"""Abstract Base Class for Stub Sources""" | ||
|
||
def __init__(self, location): | ||
self.location = location | ||
_name = self.__class__.__name__ | ||
self.log = Log.add_logger(_name) | ||
|
||
@contextmanager | ||
def ready(self, path=None, teardown=None): | ||
"""Yields prepared Stub Source | ||
Allows StubSource subclasses to have a preperation | ||
method before providing a local path to itself. | ||
Args: | ||
path (str, optional): path to stub source. | ||
Defaults to location. | ||
teardown (func, optional): callback to execute on exit. | ||
Defaults to None. | ||
Yields: | ||
Resolved PathLike object to stub source | ||
""" | ||
_path = path or self.location | ||
path = Path(_path).resolve() | ||
yield path | ||
if teardown: | ||
teardown() | ||
|
||
|
||
class LocalStubSource(StubSource): | ||
"""Stub Source Subclass for local locations | ||
Args: | ||
path (str): Path to Stub Source | ||
Returns: | ||
obj: Instance of LocalStubSource | ||
""" | ||
|
||
def __init__(self, path): | ||
location = utils.ensure_existing_dir(path) | ||
return super().__init__(location) | ||
|
||
|
||
class RemoteStubSource(StubSource): | ||
"""Stub Source for remote locations | ||
Args: | ||
url (str): URL to Stub Source | ||
Returns: | ||
obj: Instance of RemoteStubSource | ||
""" | ||
|
||
def __init__(self, url): | ||
location = utils.ensure_valid_url(url) | ||
return super().__init__(location) | ||
|
||
def _unpack_archive(self, file_bytes, path): | ||
"""Unpack archive from bytes buffer | ||
Args: | ||
file_bytes (bytes): Byte array to extract from | ||
Must be from tarfile with gzip compression | ||
path (str): path to extract file to | ||
Returns: | ||
path: path extracted to | ||
""" | ||
tar_bytes_obj = io.BytesIO(file_bytes) | ||
with tarfile.open(fileobj=tar_bytes_obj, mode="r:gz") as tar: | ||
tar.extractall(path) | ||
return path | ||
|
||
def ready(self): | ||
"""Retrieves and unpacks source | ||
Prepares remote stub resource by downloading and | ||
unpacking it into a temporary directory. | ||
This directory is removed on exit of the superclass | ||
context manager | ||
Returns: | ||
callable: StubSource.ready parent method | ||
""" | ||
tmp_dir = tempfile.mkdtemp() | ||
tmp_path = Path(tmp_dir) | ||
filename = utils.get_url_filename(self.location).split(".tar.gz")[0] | ||
outpath = tmp_path / filename | ||
resp = requests.get(self.location) | ||
source_path = self._unpack_archive(resp.content, outpath) | ||
teardown = partial(shutil.rmtree, tmp_path) | ||
return super().ready(path=source_path, teardown=teardown) | ||
|
||
|
||
def get_source(location, **kwargs): | ||
"""Factory for StubSource Instance | ||
Args: | ||
location (str): PathLike object or valid URL | ||
Returns: | ||
obj: Either Local or Remote StubSource Instance | ||
""" | ||
if utils.is_url(location): | ||
return RemoteStubSource(location, **kwargs) | ||
return LocalStubSource(location, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import pytest | ||
|
||
from micropy.stubs import source | ||
|
||
|
||
@pytest.yield_fixture | ||
def test_archive(shared_datadir): | ||
archive = shared_datadir / 'archive_test_stub.tar.gz' | ||
file_obj = archive.open('rb') | ||
file_bytes = file_obj.read() | ||
yield file_bytes | ||
file_obj.close() | ||
|
||
|
||
def test_get_source(shared_datadir, test_urls): | ||
"""should return correct subclass""" | ||
test_path = shared_datadir / 'esp8266_test_stub' | ||
local_stub = source.get_source(test_path) | ||
assert isinstance(local_stub, source.LocalStubSource) | ||
remote_stub = source.get_source(test_urls['valid']) | ||
assert isinstance(remote_stub, source.RemoteStubSource) | ||
|
||
|
||
def test_source_ready(shared_datadir, test_urls, tmp_path, mocker, | ||
test_archive): | ||
"""should prepare and resolve stub""" | ||
# Test LocalStub ready | ||
test_path = shared_datadir / 'esp8266_test_stub' | ||
local_stub = source.get_source(test_path) | ||
expected_path = local_stub.location.resolve() | ||
with local_stub.ready() as source_path: | ||
assert source_path == expected_path | ||
|
||
# Setup RemoteStub | ||
test_parent = tmp_path / 'tmpdir' | ||
test_parent.mkdir() | ||
expected_path = (test_parent / 'archive_test_stub').resolve() | ||
mocker.patch.object(source.utils, "ensure_valid_url", | ||
return_value=test_urls['download']) | ||
mocker.patch.object(source.tempfile, "mkdtemp", return_value=test_parent) | ||
get_mock = mocker.patch.object(source.requests, "get") | ||
content_mock_val = mocker.PropertyMock(return_value=test_archive) | ||
type(get_mock.return_value).content = content_mock_val | ||
# Test Remote Stub | ||
remote_stub = source.get_source(test_urls['download']) | ||
with remote_stub.ready() as source_path: | ||
print(list(source_path.parent.iterdir())) | ||
assert source_path.exists() | ||
assert str(source_path) == str(expected_path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters