Skip to content

Commit 6b9e4b2

Browse files
authored
MRG: Merge pull request #121 from octue/release/0.1.10
Release: 0.1.10
2 parents aa00826 + 1d9d8df commit 6b9e4b2

File tree

11 files changed

+114
-94
lines changed

11 files changed

+114
-94
lines changed

.github/workflows/python-ci.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,17 @@ jobs:
3232
uses: actions/setup-python@v2
3333
with:
3434
python-version: ${{ matrix.python }}
35-
- name: Install Tox and any other packages
36-
run: pip install tox
37-
- name: Run Tox
35+
- name: Install package
36+
run: pip install -r requirements-dev.txt
37+
- name: Run tests
3838
env:
3939
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GCP_SERVICE_ACCOUNT }}
4040
TEST_PROJECT_NAME: ${{ secrets.TEST_PROJECT_NAME }}
4141
TEST_BUCKET_NAME: ${{ secrets.TEST_BUCKET_NAME }}
42-
run: tox -e py
42+
run: |
43+
coverage run --source octue -m unittest discover
44+
coverage report --show-missing
45+
coverage xml
4346
- name: Upload coverage to Codecov
4447
uses: codecov/codecov-action@v1
4548
with:

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ pyenv virtualenv 3.8 myenv # Makes a virtual environment for you to
6161
pyend activate myenv # Activates the virtual environment so you don't screw up other installations
6262
pip install -r requirements-dev.txt # Installs the testing and code formatting utilities
6363
pre-commit install # Installs the pre-commit code formatting hooks in the git repo
64-
tox # Runs the tests with coverage. NB you can also just set up pycharm or vscode to run these.
6564
```
6665

6766
- Adopt a Test Driven Development approach to implementing new features or fixing bugs.

octue/resources/datafile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def from_cloud(cls, project_name, bucket_name, datafile_path, timestamp=None):
113113
:return Datafile:
114114
"""
115115
metadata = GoogleCloudStorageClient(project_name).get_metadata(bucket_name, datafile_path)
116-
custom_metadata = metadata["metadata"]
116+
custom_metadata = metadata.get("metadata") or {}
117117

118118
datafile = cls(
119119
timestamp=custom_metadata.get("timestamp", timestamp),

octue/utils/cloud/emulators.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import os
2+
import socket
3+
from contextlib import closing
4+
from gcp_storage_emulator.server import create_server
5+
6+
7+
class GoogleCloudStorageEmulator:
8+
"""A local emulator for Google Cloud Storage
9+
10+
:param str host:
11+
:param int port:
12+
:param str default_bucket:
13+
:return None:
14+
"""
15+
16+
def __init__(self, host="localhost", port=9090, in_memory=True, default_bucket=os.environ["TEST_BUCKET_NAME"]):
17+
self._server = create_server(host, port, in_memory=in_memory, default_bucket=default_bucket)
18+
19+
def __enter__(self):
20+
self.start()
21+
return self
22+
23+
def __exit__(self, exc_type, exc_val, exc_tb):
24+
self.stop()
25+
26+
def start(self):
27+
"""Start the emulator. Do nothing if it's already started on the given host and port.
28+
29+
:return None:
30+
"""
31+
try:
32+
self._server.start()
33+
except RuntimeError:
34+
pass
35+
36+
def stop(self):
37+
"""Stop the emulator.
38+
39+
:return None:
40+
"""
41+
self._server.stop()
42+
43+
44+
def get_free_tcp_port():
45+
"""Get a free TCP port.
46+
47+
:return int:
48+
"""
49+
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
50+
s.bind(("", 0))
51+
_, port = s.getsockname()
52+
return port
53+
54+
55+
class GoogleCloudStorageEmulatorTestResultModifier:
56+
"""A class providing `startTestRun` and `endTestRun` methods for use by a `unittest.TestResult` that start up a
57+
Google Cloud Storage emulator on a free port before the tests run and stop it after they've all run.
58+
59+
:param str host:
60+
:param bool in_memory:
61+
:param str default_bucket_name:
62+
:return None:
63+
"""
64+
65+
STORAGE_EMULATOR_HOST_ENVIRONMENT_VARIABLE_NAME = "STORAGE_EMULATOR_HOST"
66+
67+
def __init__(self, host="localhost", in_memory=True, default_bucket_name=os.environ["TEST_BUCKET_NAME"]):
68+
port = get_free_tcp_port()
69+
self.storage_emulator_host = f"http://{host}:{port}"
70+
71+
self.storage_emulator = GoogleCloudStorageEmulator(
72+
host=host, port=port, in_memory=in_memory, default_bucket=default_bucket_name
73+
)
74+
75+
def startTestRun(self):
76+
"""Start the Google Cloud Storage emulator before starting the test run.
77+
78+
:param unittest.TestResult test_result:
79+
:return None:
80+
"""
81+
os.environ[self.STORAGE_EMULATOR_HOST_ENVIRONMENT_VARIABLE_NAME] = self.storage_emulator_host
82+
self.storage_emulator.start()
83+
84+
def stopTestRun(self):
85+
"""Stop the Google Cloud Storage emulator before starting the test run.
86+
87+
:param unittest.TestResult test_result:
88+
:return None:
89+
"""
90+
self.storage_emulator.stop()
91+
del os.environ[self.STORAGE_EMULATOR_HOST_ENVIRONMENT_VARIABLE_NAME]

octue/utils/cloud/storage/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def get_metadata(self, bucket_name, path_in_bucket, timeout=_DEFAULT_TIMEOUT):
103103
bucket = self.client.get_bucket(bucket_or_name=bucket_name)
104104
metadata = bucket.get_blob(blob_name=self._strip_leading_slash(path_in_bucket), timeout=timeout)._properties
105105

106-
if metadata["metadata"] is not None:
106+
if metadata.get("metadata") is not None:
107107
metadata["metadata"] = {key: json.loads(value) for key, value in metadata["metadata"].items()}
108108

109109
return metadata

requirements-dev.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11

22
# Testing
33
# ------------------------------------------------------------------------------
4-
tox
54
pluggy
65
gcp-storage-emulator>=2021.2.17
76

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
setup(
1919
name="octue",
20-
version="0.1.9",
20+
version="0.1.10",
2121
py_modules=["cli"],
2222
install_requires=[
2323
"blake3>=0.1.8",
@@ -27,7 +27,7 @@
2727
"google-cloud-pubsub>=2.2.0",
2828
"google-cloud-storage>=1.35.1",
2929
"twined==0.0.16",
30-
], # Dev note: you also need to bump twined in tox.ini
30+
],
3131
url="https://www.github.com/octue/octue-sdk-python",
3232
license="MIT",
3333
author="Thomas Clark (github: thclark)",

tests/__init__.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
11
import os
22
import unittest
33

4-
from tests.emulators import GoogleCloudStorageEmulator
4+
from octue.utils.cloud.emulators import GoogleCloudStorageEmulatorTestResultModifier
55

66

77
TESTS_DIR = os.path.dirname(__file__)
8-
storage_emulator = GoogleCloudStorageEmulator()
98

109

11-
def startTestRun(instance):
12-
"""Start the test run, running any code in this function first.
13-
14-
:param unittest.TestResult instance:
15-
:return None:
16-
"""
17-
storage_emulator.start()
18-
19-
20-
def stopTestRun(instance):
21-
"""Finish the test run, running any code in this function first.
22-
23-
:param unittest.TestResult instance:
24-
:return None:
25-
"""
26-
storage_emulator.stop()
27-
28-
29-
setattr(unittest.TestResult, "startTestRun", startTestRun)
30-
setattr(unittest.TestResult, "stopTestRun", stopTestRun)
10+
test_result_modifier = GoogleCloudStorageEmulatorTestResultModifier()
11+
setattr(unittest.TestResult, "startTestRun", test_result_modifier.startTestRun)
12+
setattr(unittest.TestResult, "stopTestRun", test_result_modifier.stopTestRun)

tests/emulators.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

tests/utils/cloud/storage/test_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010

1111

1212
class TestUploadFileToGoogleCloud(BaseTestCase):
13-
PROJECT_NAME = os.environ["TEST_PROJECT_NAME"]
14-
TEST_BUCKET_NAME = os.environ["TEST_BUCKET_NAME"]
15-
FILENAME = "my_file.txt"
16-
storage_client = GoogleCloudStorageClient(project_name=PROJECT_NAME, credentials=OCTUE_MANAGED_CREDENTIALS)
13+
@classmethod
14+
def setUpClass(cls):
15+
cls.PROJECT_NAME = os.environ["TEST_PROJECT_NAME"]
16+
cls.TEST_BUCKET_NAME = os.environ["TEST_BUCKET_NAME"]
17+
cls.FILENAME = "my_file.txt"
18+
cls.storage_client = GoogleCloudStorageClient(
19+
project_name=cls.PROJECT_NAME, credentials=OCTUE_MANAGED_CREDENTIALS
20+
)
1721

1822
def test_upload_and_download_file(self):
1923
"""Test that a file can be uploaded to Google Cloud storage and downloaded again."""

0 commit comments

Comments
 (0)