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

slight style changes #4

Merged
merged 8 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
90 changes: 90 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Contributing to MecSimCalc

We welcome contributions to MecSimCalc. Please follow the guidelines below.


## When contributing code to MecSimCalc
When contributing code to MecSimCalc, make sure to:
- Format your code using the black code formatter
- Test your code (or add tests if you are adding a new feature)
- Python 3.6+ is supported
- Include a docstring for each function you add
- Document your code in the README.md file

## How to test your changes
Before submitting a pull request, please make sure that your changes do not break the existing code. You can run the tests by executing the following command in the root directory of the repository:

(You may need to install pytest first by running `pip install pytest`)
```bash
pip install .
```
```bash
pytest .
```

## Code quality
Please make sure that your code follows the PEP 8 style guide and naming conventions. You can correct your fomatting by running the following command in the root directory of the repository:

(You may need to install black first by running `pip install black`)

```bash
black .
```

## Docstrings
Please include a docstring for each function you add. Also include type hints and return type hints.

Docstrings should follow the [numpydoc format](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html). But make sure to include the function with the `>>>` symbol. Here is an example of a docstring:

(Docstrings are shown when users hover over the function. This helps users understand what the function does and how to use it without having to look at the source code.)

```python
def my_function(arg1 : int, arg2 : int) -> int:
"""
>>> def my_function(arg1 : int, arg2 : int) -> int:

This is a brief description of the function. This function adds two numbers together.

Parameters
----------
arg1 : int
This is a description of arg1.
arg2 : int
This is a description of arg2.

Returns
-------
int
This is a description of the return value.

Examples
--------
>>> my_function(1, 2)
3
"""
return arg1 + arg2
```


## Release process
When you are ready to release a new version of MecSimCalc, please follow the steps below:

1. Update the version number in the `setup.py` file
2. Update all references to the version number in the code and documentation before creating a new release (e.g., in the README.md file, update the links to the source code and documentation to point to the new version before creating a new release)
3. Create a new release on GitHub with the updated version number as the tag name

**How to update the links in the README.md file:**
Imagine you want to update your code to version 1.1.2. You would update the links in the README.md file as follows:

Original:
```markdown
...href="https://github.com/MecSimCalc/MecSimCalc-utils/blob/**v0.1.5**/mecsimcalc/general_utils.py#L7C1-L56C61"...
```

Updated:
```markdown
...href="https://github.com/MecSimCalc/MecSimCalc-utils/blob/**v1.1.2**/mecsimcalc/general_utils.py#L7C1-L56C61"...
```

**IMPORTANT: MODIFY THE VERSION BEFORE CREATING A NEW RELEASE**

1 change: 0 additions & 1 deletion PyTests/test_images.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import sys
import os
import pytest
import base64
import mimetypes
from PIL import Image
Expand Down
40 changes: 21 additions & 19 deletions PyTests/test_quiz.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


class TestAppendToGoogleSheet(unittest.TestCase):
@patch('mecsimcalc.quiz_utils.requests')
@patch("mecsimcalc.quiz_utils.requests")
def test_append_to_google_sheet(self, mock_post):
# mock response
mock_response = MagicMock()
Expand All @@ -33,26 +33,28 @@ def test_append_to_google_sheet(self, mock_post):
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/xxx.com",
"universe_domain": "googleapis.com"
"universe_domain": "googleapis.com",
}
spreadsheet_id = 'dummy_spreadsheet_id'
spreadsheet_id = "dummy_spreadsheet_id"
values = [["dummy_data"]]
range_name = 'Sheet1!A1'
range_name = "Sheet1!A1"

# execute the function
response = append_to_google_sheet(service_account_info, spreadsheet_id, values, range_name, False)
response = append_to_google_sheet(
service_account_info, spreadsheet_id, values, range_name, False
)
assert isinstance(response, dict)


class TestSendEmail(unittest.TestCase):
@patch('mecsimcalc.quiz_utils.smtplib.SMTP_SSL')
@patch("mecsimcalc.quiz_utils.smtplib.SMTP_SSL")
def test_send_email_success(self, mock_smtp_ssl):
# Setup test data
sender_email = '[email protected]'
receiver_email = '[email protected]'
subject = 'Test Subject'
app_password = 'app-specific-password'
values = [('Data1', 'Data2'), ('Data3', 'Data4')]
sender_email = "[email protected]"
receiver_email = "[email protected]"
subject = "Test Subject"
app_password = "app-specific-password"
values = [("Data1", "Data2"), ("Data3", "Data4")]

# Configure the mock SMTP server
mock_server = MagicMock()
Expand All @@ -63,7 +65,7 @@ def test_send_email_success(self, mock_smtp_ssl):

# Assertions
assert res == True
mock_smtp_ssl.assert_called_once_with('smtp.gmail.com', 465)
mock_smtp_ssl.assert_called_once_with("smtp.gmail.com", 465)
mock_server.login.assert_called_once_with(sender_email, app_password)
mock_server.sendmail.assert_called_once()
args, _ = mock_server.sendmail.call_args
Expand All @@ -75,14 +77,14 @@ def test_send_email_success(self, mock_smtp_ssl):
for value in values:
self.assertIn(", ".join(value), email_body)

@patch('mecsimcalc.quiz_utils.smtplib.SMTP_SSL')
@patch("mecsimcalc.quiz_utils.smtplib.SMTP_SSL")
def test_send_email_failure(self, mock_smtp_ssl):
# Setup test data with same parameters as success test
sender_email = '[email protected]'
receiver_email = '[email protected]'
subject = 'Test Subject'
app_password = 'app-specific-password'
values = [('Data1', 'Data2'), ('Data3', 'Data4')]
sender_email = "[email protected]"
receiver_email = "[email protected]"
subject = "Test Subject"
app_password = "app-specific-password"
values = [("Data1", "Data2"), ("Data3", "Data4")]

# Configure the mock SMTP server to raise an exception
mock_smtp_ssl.return_value.__enter__.side_effect = Exception("SMTP Error")
Expand All @@ -92,4 +94,4 @@ def test_send_email_failure(self, mock_smtp_ssl):

# Assertions
assert res == False
mock_smtp_ssl.assert_called_once_with('smtp.gmail.com', 465)
mock_smtp_ssl.assert_called_once_with("smtp.gmail.com", 465)
1 change: 0 additions & 1 deletion PyTests/test_spreadsheet.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import sys
import os
import pytest
import base64
import mimetypes
import io
Expand Down
1 change: 0 additions & 1 deletion PyTests/test_text.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import sys
import os
import pytest

# caution: path[0] is reserved for script path (or '' in REPL)
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
Expand Down
2 changes: 1 addition & 1 deletion mecsimcalc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@
"metadata_to_filetype",
"file_to_PIL",
"append_to_google_sheet",
"send_gmail"
"send_gmail",
]
4 changes: 2 additions & 2 deletions mecsimcalc/general_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def input_to_file(
Returns
-------
Union[io.BytesIO, Tuple[io.BytesIO, str]]
If `metadata` is False, returns an `io.BytesIO` object containing the decoded file data.
If `metadata` is True, returns a tuple containing the `io.BytesIO` object and a string representing the metadata.
* If `metadata` is False, returns an `io.BytesIO` object containing the decoded file data.
* If `metadata` is True, returns a tuple containing the `io.BytesIO` object and a string representing the metadata.

Raises
------
Expand Down
8 changes: 4 additions & 4 deletions mecsimcalc/image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def input_to_PIL(
Returns
-------
Union[PIL.Image.Image, Tuple[PIL.Image.Image, str]]
If `get_file_type` is False, returns a `PIL.Image.Image` object created from the decoded file data.
If `get_file_type` is True, returns a tuple containing the `PIL.Image.Image` object and a string representing the file type.
* If `get_file_type` is False, returns a `PIL.Image.Image` object created from the decoded file data.
* If `get_file_type` is True, returns a tuple containing the `PIL.Image.Image` object and a string representing the file type.

Examples
--------
Expand Down Expand Up @@ -153,8 +153,8 @@ def print_image(
Returns
-------
Union[str, Tuple[str, str]]
If `download` is False, returns an HTML string containing the image.
If `download` is True, returns a tuple containing the HTML string of the image and the HTML string of the download link.
* If `download` is False, returns an HTML string containing the image.
* If `download` is True, returns a tuple containing the HTML string of the image and the HTML string of the download link.

Examples
--------
Expand Down
6 changes: 4 additions & 2 deletions mecsimcalc/plotting_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def print_plot(
Returns
-------
Union[str, Tuple[str, str]]
If `download` is False, returns the HTML image as a string.
If `download` is True, returns a tuple consisting of the HTML image as a string and the download link as a string.
* If `download` is False, returns the HTML image as a string.
* If `download` is True, returns a tuple consisting of the HTML image as a string and the download link as a string.


Examples
Expand All @@ -70,12 +70,14 @@ def print_plot(
if isinstance(plot_obj, plt.Axes):
plot_obj = plot_obj.get_figure()

# Save the plot to a buffer
buffer = io.BytesIO()
plot_obj.savefig(buffer, format="png", dpi=dpi)

if hasattr(plot_obj, "close"):
plot_obj.close()

# generate image
encoded_image = (
f"data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}"
)
Expand Down
70 changes: 33 additions & 37 deletions mecsimcalc/quiz_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
import logging



def append_to_google_sheet(
service_account_info: dict,
spreadsheet_id: str,
values: list,
range_name: str = 'Sheet1!A1',
include_timestamp: bool = True
service_account_info: dict,
spreadsheet_id: str,
values: list,
range_name: str = "Sheet1!A1",
include_timestamp: bool = True,
) -> dict:
"""
>>> append_to_google_sheet(
Expand Down Expand Up @@ -69,29 +68,29 @@ def _get_access_token(service_account_info: dict):
exp = iat + 3600 # Token valid for 1 hour
# JWT payload
payload = {
'iss': service_account_info['client_email'],
'scope': 'https://www.googleapis.com/auth/spreadsheets',
'aud': 'https://oauth2.googleapis.com/token',
'iat': iat,
'exp': exp
"iss": service_account_info["client_email"],
"scope": "https://www.googleapis.com/auth/spreadsheets",
"aud": "https://oauth2.googleapis.com/token",
"iat": iat,
"exp": exp,
}
# Generate JWT
additional_headers = {'kid': service_account_info['private_key_id']}
additional_headers = {"kid": service_account_info["private_key_id"]}
signed_jwt = jwt.encode(
payload,
service_account_info['private_key'],
algorithm='RS256',
headers=additional_headers
service_account_info["private_key"],
algorithm="RS256",
headers=additional_headers,
)
# Exchange JWT for access token
params = {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': signed_jwt
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": signed_jwt,
}
response = requests.post('https://oauth2.googleapis.com/token', data=params)
response = requests.post("https://oauth2.googleapis.com/token", data=params)
response.raise_for_status() # Raises HTTPError, if one occurred
response_data = response.json()
return response_data['access_token']
return response_data["access_token"]
except Exception as e:
print(f"Error getting access token: {e}")
return None
Expand All @@ -106,12 +105,12 @@ def _get_access_token(service_account_info: dict):
values = [row + [current_timestamp] for row in values]

try:
url = f'https://sheets.googleapis.com/v4/spreadsheets/{spreadsheet_id}/values/{range_name}:append?valueInputOption=RAW&insertDataOption=INSERT_ROWS'
url = f"https://sheets.googleapis.com/v4/spreadsheets/{spreadsheet_id}/values/{range_name}:append?valueInputOption=RAW&insertDataOption=INSERT_ROWS"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
}
body = {'values': values}
body = {"values": values}
response = requests.post(url, headers=headers, json=body)
response.raise_for_status() # Raises HTTPError, if one occurred
return response.json()
Expand All @@ -120,13 +119,12 @@ def _get_access_token(service_account_info: dict):
return {"error": "Failed to append to Google Sheet"}



def send_gmail(
sender_email: str,
receiver_email: str,
subject: str,
app_password: str,
values: list
sender_email: str,
receiver_email: str,
subject: str,
app_password: str,
values: list,
) -> bool:
"""
>>> send_gmail(
Expand Down Expand Up @@ -157,30 +155,28 @@ def send_gmail(
Returns
-------
bool
Returns True if the email was sent successfully, otherwise False. Success or failure is logged using the logging
module.
Returns True if the email was sent successfully, otherwise False.

Examples
--------
>>> values = [["John Doe", "123456", 10, 2, 5.00, "This is a test message."]]
>>> msc.send_gmail("[email protected]", "[email protected]", "Test Email", "your_app_password", values)
True

"""

# Initialize email message
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = sender_email
message["To"] = receiver_email

body = ""
for value in values:
body += ", ".join(str(v) for v in value) + "\n"

# Construct the email body
body = "\n".join(", ".join(str(v) for v in value) for value in values)
message.attach(MIMEText(body, "plain"))

try:
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
# Send the email
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login(sender_email, app_password)
server.sendmail(sender_email, receiver_email, message.as_string())
logging.info("Email sent successfully!")
Expand Down
Loading
Loading