python-postman 0.3.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. python_postman-0.6.0/PKG-INFO +52 -0
  2. python_postman-0.6.0/README.md +28 -0
  3. python_postman-0.6.0/pyproject.toml +59 -0
  4. python_postman-0.6.0/src/python_postman/body.py +33 -0
  5. python_postman-0.6.0/src/python_postman/config.py +110 -0
  6. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/environment.py +1 -1
  7. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/modules/http.py +78 -18
  8. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/utils/cli.py +23 -3
  9. python_postman-0.6.0/src/python_postman/utils/load_dotenvc.py +101 -0
  10. python-postman-0.3.0/.gitignore +0 -131
  11. python-postman-0.3.0/PKG-INFO +0 -73
  12. python-postman-0.3.0/README.md +0 -60
  13. python-postman-0.3.0/examples/collections/Coinmarketcap.postman_collection.json +0 -180
  14. python-postman-0.3.0/examples/collections/PokeAPI.postman_collection.json +0 -66
  15. python-postman-0.3.0/examples/models/coinmarketcap_example.py +0 -63
  16. python-postman-0.3.0/examples/models/pokeapi_example.py +0 -66
  17. python-postman-0.3.0/pypostman/body.py +0 -8
  18. python-postman-0.3.0/pypostman/config.py +0 -108
  19. python-postman-0.3.0/pypostman/utils/load_dotenvc.py +0 -99
  20. python-postman-0.3.0/pyproject.toml +0 -23
  21. python-postman-0.3.0/python_postman.egg-info/PKG-INFO +0 -73
  22. python-postman-0.3.0/python_postman.egg-info/SOURCES.txt +0 -34
  23. python-postman-0.3.0/python_postman.egg-info/dependency_links.txt +0 -1
  24. python-postman-0.3.0/python_postman.egg-info/entry_points.txt +0 -2
  25. python-postman-0.3.0/python_postman.egg-info/requires.txt +0 -16
  26. python-postman-0.3.0/python_postman.egg-info/top_level.txt +0 -1
  27. python-postman-0.3.0/setup.cfg +0 -4
  28. {python-postman-0.3.0 → python_postman-0.6.0}/LICENSE +0 -0
  29. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/__init__.py +0 -0
  30. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/__main__.py +0 -0
  31. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/auth.py +0 -0
  32. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/collection.py +0 -0
  33. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/event.py +0 -0
  34. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/header.py +0 -0
  35. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/item.py +0 -0
  36. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/modules/file.py +0 -0
  37. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/modules/logger.py +0 -0
  38. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/postman.py +0 -0
  39. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/request.py +0 -0
  40. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/template.py +0 -0
  41. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/url.py +0 -0
  42. {python-postman-0.3.0/pypostman → python_postman-0.6.0/src/python_postman}/variable.py +0 -0
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-postman
3
+ Version: 0.6.0
4
+ Summary: Pypostman allows users to parse postman environments and postman collections.
5
+ Author: Data Engineering Team
6
+ Author-email: DataEngineering@loves.com
7
+ Requires-Python: >=3.8.1,<4.0.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: boto3 (>=1.34.67,<2.0.0)
14
+ Requires-Dist: cryptography (>=42.0.5,<43.0.0)
15
+ Requires-Dist: logbook (>=1.7.0.post0,<2.0.0)
16
+ Requires-Dist: pendulum (>=3.0.0,<4.0.0)
17
+ Requires-Dist: pydantic (>=2.6.4,<3.0.0)
18
+ Requires-Dist: pytz (>=2024.1,<2025.0)
19
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
20
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
21
+ Requires-Dist: urllib3 (>=1.25.4,<2.0.0)
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Python Postman
25
+
26
+ **pypostman** is a command-line interface that allows to `automate` multiple api calls from postman collections, additionally it also allow you to `compress` and `save` the response to a local directory or to an AWS S3 bucket.
27
+ Thereby allowing you to manage your api calls using postman, then `automate` and `process` their response using python.
28
+
29
+ Example:
30
+
31
+ ```
32
+ https://github.com/yudiell/energy-apis
33
+ ```
34
+
35
+ ### Included Modules
36
+ - http.py
37
+ - logger.py
38
+
39
+ ## Installation
40
+ See the example.
41
+
42
+ ### Python >= 3.8.1
43
+
44
+ Pypi:
45
+ ```
46
+ pip install python-postman
47
+ ```
48
+
49
+ ## How to Use It
50
+
51
+ See example
52
+
@@ -0,0 +1,28 @@
1
+ # Python Postman
2
+
3
+ **pypostman** is a command-line interface that allows to `automate` multiple api calls from postman collections, additionally it also allow you to `compress` and `save` the response to a local directory or to an AWS S3 bucket.
4
+ Thereby allowing you to manage your api calls using postman, then `automate` and `process` their response using python.
5
+
6
+ Example:
7
+
8
+ ```
9
+ https://github.com/yudiell/energy-apis
10
+ ```
11
+
12
+ ### Included Modules
13
+ - http.py
14
+ - logger.py
15
+
16
+ ## Installation
17
+ See the example.
18
+
19
+ ### Python >= 3.8.1
20
+
21
+ Pypi:
22
+ ```
23
+ pip install python-postman
24
+ ```
25
+
26
+ ## How to Use It
27
+
28
+ See example
@@ -0,0 +1,59 @@
1
+ [tool.poetry]
2
+ name = "python-postman"
3
+ version = "0.6.0"
4
+ description = "Pypostman allows users to parse postman environments and postman collections."
5
+ authors = ["Data Engineering Team <DataEngineering@loves.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.isort]
9
+ profile = "black"
10
+
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.8.1"
14
+ requests = "^2.32.3"
15
+ pyyaml = "^6.0.1"
16
+ pydantic = "^2.6.4"
17
+ cryptography = "^42.0.5"
18
+ urllib3 = "^1.25.4"
19
+ logbook = "^1.7.0.post0"
20
+ pendulum = "^3.0.0"
21
+ boto3 = "^1.34.67"
22
+ pytz = "^2024.1"
23
+
24
+
25
+ [tool.poetry.group.lint.dependencies]
26
+ flake8-annotations = "^3.0.1"
27
+ flake8-builtins = "^2.1.0"
28
+ flake8-docstrings = "^1.7.0"
29
+ flake8-eradicate = "^1.5.0"
30
+ flake8-future-annotations = "^1.1.0"
31
+ flake8-isort = "^6.0.0"
32
+ flake8-new-union-types = "^0.4.1"
33
+ flake8-pep585 = "^0.1.7"
34
+ flake8-secure-coding-standard = "^1.4.0"
35
+ flake8-type-checking = "^2.4.1"
36
+ flake8-use-pathlib = "^0.3.0"
37
+ flake8-variables-names = "^0.0.6"
38
+ pep8-naming = "^0.13.3"
39
+ black = "^24.3.0"
40
+ isort = "^5.12.0"
41
+ pip-audit = "^2.6.1"
42
+ mypy = "^1.9.0"
43
+
44
+
45
+ [tool.poetry.group.test.dependencies]
46
+ pytest = "^7.4.0"
47
+
48
+
49
+ [tool.poetry.group.dev.dependencies]
50
+ pre-commit = "^3.3.3"
51
+
52
+
53
+ [build-system]
54
+ requires = ["poetry-core"]
55
+ build-backend = "poetry.core.masonry.api"
56
+
57
+
58
+ [tool.poetry.scripts]
59
+ pypostman = "pypostman.__main__:main"
@@ -0,0 +1,33 @@
1
+ from typing import Dict
2
+ from .config import Body
3
+
4
+
5
+ class Body:
6
+ def __init__(self, body: Body) -> None:
7
+ self.mode = body.mode
8
+ self.raw = body.raw
9
+ self.options = body.options
10
+ self.formdata = body.formdata
11
+ self.urlencoded = body.urlencoded
12
+
13
+ @property
14
+ def urlencoded_as_dict(self) -> Dict[str, str]:
15
+ if not self.urlencoded:
16
+ return None
17
+
18
+ body = {}
19
+ for option in self.urlencoded:
20
+ if not option.get("disabled", False):
21
+ body[option["key"]] = option["value"]
22
+ return body
23
+
24
+ @property
25
+ def formdata_as_dict(self) -> Dict[str, str]:
26
+ if not self.formdata:
27
+ return None
28
+
29
+ body = {}
30
+ for option in self.formdata:
31
+ if not option.get("disabled", False):
32
+ body[option["key"]] = option["value"]
33
+ return body
@@ -0,0 +1,110 @@
1
+ from typing import List, Dict, Union, Optional
2
+ from pydantic import Field, BaseModel
3
+
4
+
5
+ class Info(BaseModel):
6
+ postman_id: str = Field(None, alias="_postman_id")
7
+ name: str
8
+ postman_schema: str = Field(None, alias="schema")
9
+ exporter_id: str = Field(None, alias="_exporter_id")
10
+
11
+
12
+ # Variable Related Objects
13
+ class Variables(BaseModel):
14
+ key: Optional[str] = None
15
+ value: Optional[str] = None
16
+ type: Optional[str] = None
17
+ disabled: Optional[bool] = None
18
+
19
+
20
+ # Auth Related Objects
21
+ class AuthValues(BaseModel):
22
+ key: str
23
+ value: str
24
+ type: str
25
+ disabled: Optional[bool] = None
26
+
27
+
28
+ class Auth(BaseModel):
29
+ type: str
30
+ noauth: Optional[List[AuthValues]] = None
31
+ basic: Optional[List[AuthValues]] = None
32
+ apikey: Optional[List[AuthValues]] = None
33
+ bearer: Optional[List[AuthValues]] = None
34
+
35
+
36
+ # Collection and Request Related Objects
37
+ class Script(BaseModel):
38
+ type: Optional[str] = None
39
+ exec: List[str]
40
+
41
+
42
+ class Event(BaseModel):
43
+ listen: Optional[str] = None
44
+ script: Optional[Script] = None
45
+
46
+
47
+ # Request Related Objects
48
+ class Header(BaseModel):
49
+ key: Optional[str] = None
50
+ value: Optional[str] = None
51
+ description: Optional[str] = None
52
+ disabled: Optional[bool] = None
53
+
54
+
55
+ class Variable(BaseModel):
56
+ key: Optional[str] = None
57
+ value: Optional[str] = None
58
+ description: Optional[str] = None
59
+
60
+
61
+ class Param(BaseModel):
62
+ key: Optional[str] = None
63
+ value: Optional[str] = None
64
+ description: Optional[str] = None
65
+ disabled: Optional[bool] = None
66
+
67
+
68
+ class Body(BaseModel):
69
+ mode: Optional[str] = None
70
+ raw: Optional[str] = None
71
+ formdata: Optional[List[Dict[str, Union[str, bool]]]] = None
72
+ urlencoded: Optional[List[Dict[str, Union[str, bool]]]] = None
73
+ options: Optional[Dict[str, Dict[str, Union[str, bool]]]] = None
74
+
75
+
76
+ class Url(BaseModel):
77
+ raw: Optional[str] = None
78
+ protocol: Optional[str] = None
79
+ host: Optional[List[str]] = None
80
+ path: Optional[List[str]] = None
81
+ variable: Optional[List[Variable]] = None
82
+ query: Optional[List[Param]] = None
83
+
84
+
85
+ class Request(BaseModel):
86
+ auth: Optional[Auth] = None
87
+ method: str
88
+ headers: List[Header] = Field(None, alias="header")
89
+ url: Optional[Url] = None
90
+ body: Optional[Body] = None
91
+
92
+
93
+ # Collection Related Objects
94
+ class Item(BaseModel):
95
+ name: str
96
+ item: Optional[List["Item"]] = None
97
+ events: Optional[List[Event]] = Field(None, alias="event")
98
+ request: Optional[Request] = None
99
+
100
+ @property
101
+ def type(self):
102
+ return "request" if self.request else "folder"
103
+
104
+
105
+ class Config(BaseModel):
106
+ info: Optional[Info] = None
107
+ items: Optional[List[Item]] = Field(None, alias="item")
108
+ variables: Optional[List[Variables]] = Field(None, alias="variable")
109
+ events: Optional[List[Event]] = Field(None, alias="event")
110
+ auth: Optional[Auth] = None
@@ -10,7 +10,7 @@ from .template import CustomTemplate
10
10
  class Variable(BaseModel):
11
11
  key: str
12
12
  value: str
13
- type: Optional[str]
13
+ type: Optional[str] = None
14
14
  enabled: bool
15
15
 
16
16
 
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import json
3
+ import re
3
4
  from requests import Session, Response
4
5
  from urllib3 import Timeout
5
6
 
@@ -21,46 +22,48 @@ class Request(Session):
21
22
  self.timeout: Timeout = timeout
22
23
  self.stream: bool = stream
23
24
  self.url: str = self._request.url.base_url
25
+ self.body = None
26
+ self.prepare_cookies = None
24
27
 
25
- def set_params(self, params: dict):
28
+ def set_headers(self, headers: dict):
26
29
  """
27
- Set URL parameters on the request object.
30
+ Set the headers of the request.
28
31
 
29
32
  Args:
30
- params (dict): Parameters to set on the request object.
33
+ headers (dict): The headers to set.
31
34
 
32
35
  Returns:
33
36
  None
34
37
  """
35
- if self._request.url.params:
36
- text = json.dumps(self._request.url.params)
37
- template: str = CustomTemplate(text).safe_substitute(params)
38
- params = {
38
+ if self._request.headers:
39
+ text = json.dumps(self._request.headers.as_dict)
40
+ template: str = CustomTemplate(text).safe_substitute(headers)
41
+ headers = {
39
42
  key: value
40
43
  for key, value in json.loads(template).items()
41
44
  if "${" not in value
42
45
  }
43
- self.params = params
46
+ self.headers = headers
44
47
 
45
- def set_headers(self, headers: dict):
48
+ def set_params(self, params: dict):
46
49
  """
47
- Set the headers of the request.
50
+ Set URL parameters on the request object.
48
51
 
49
52
  Args:
50
- headers (dict): The headers to set.
53
+ params (dict): Parameters to set on the request object.
51
54
 
52
55
  Returns:
53
56
  None
54
57
  """
55
- if self._request.headers:
56
- text = json.dumps(self._request.headers.as_dict)
57
- template: str = CustomTemplate(text).safe_substitute(headers)
58
- headers = {
58
+ if self._request.url.params:
59
+ text = json.dumps(self._request.url.params)
60
+ template: str = CustomTemplate(text).safe_substitute(params)
61
+ params = {
59
62
  key: value
60
63
  for key, value in json.loads(template).items()
61
64
  if "${" not in value
62
65
  }
63
- self.headers = headers
66
+ self.params = params
64
67
 
65
68
  def set_path_vars(self, path_variables: dict):
66
69
  """
@@ -77,6 +80,62 @@ class Request(Session):
77
80
  path: str = CustomTemplate(request_url).safe_substitute(path_variables)
78
81
  self.url = path
79
82
 
83
+ def set_body(self, body: dict, with_quuotes: bool = True):
84
+ """
85
+ Set body payload.
86
+
87
+ Args:
88
+ body (dict): Parameters to set on the request object.
89
+ with_quotes (bool) default=True: Add/remove quotes on body parameters.
90
+ Returns:
91
+ None
92
+ """
93
+ # The pattern looks for ${...} that's not surrounded by quotes
94
+ pattern = r'(?<!")(\$\{[^}]+\})(?!")'
95
+ # Replacement pattern that adds quotes around the matched pattern
96
+ if with_quuotes:
97
+ replacement = r'"\1"'
98
+ else:
99
+ replacement = r"\1"
100
+ raw = (
101
+ re.sub(pattern, replacement, self._request.body.raw)
102
+ if self._request.body.raw
103
+ else None
104
+ )
105
+
106
+ formdata = (
107
+ json.dumps(self._request.body.formdata_as_dict)
108
+ if self._request.body.formdata_as_dict
109
+ else None
110
+ )
111
+
112
+ urlencoded = (
113
+ json.dumps(self._request.body.urlencoded_as_dict)
114
+ if self._request.body.urlencoded_as_dict
115
+ else None
116
+ )
117
+
118
+ options_list = [
119
+ formdata,
120
+ urlencoded,
121
+ ]
122
+ options = next(
123
+ (option for option in options_list if option is not None),
124
+ ModuleNotFoundError,
125
+ )
126
+ if self._request.body.formdata or self._request.body.urlencoded:
127
+ text = options
128
+ template: str = CustomTemplate(text).safe_substitute(body)
129
+ items = {
130
+ key: value
131
+ for key, value in json.loads(template).items()
132
+ if "${" not in value
133
+ }
134
+ self.body = items
135
+ else:
136
+ substitute_body: str = CustomTemplate(raw).safe_substitute(body)
137
+ self.body = substitute_body
138
+
80
139
  def substitute_bearer_token(self) -> None:
81
140
  if self._request.auth and self._request.auth.type == "bearer":
82
141
  self._request.auth.http_auth.token = CustomTemplate(
@@ -100,13 +159,13 @@ class Request(Session):
100
159
  url = self.url
101
160
  headers = self._request.headers.as_dict
102
161
  params = self.params
103
- data = request.body.raw if request.body else None
162
+ data = self.body
104
163
  timeout = self.timeout
105
164
  stream = self.stream
106
165
  auth = request.auth.http_auth
166
+ prepare_cookies = self.prepare_cookies
107
167
 
108
168
  self.log.request(url=url)
109
-
110
169
  response = session.request(
111
170
  auth=auth,
112
171
  method=method,
@@ -116,6 +175,7 @@ class Request(Session):
116
175
  data=data,
117
176
  stream=stream,
118
177
  timeout=timeout,
178
+ cookies=prepare_cookies,
119
179
  )
120
180
 
121
181
  if response.ok:
@@ -7,7 +7,7 @@ class Cli:
7
7
 
8
8
  @property
9
9
  def envs(self):
10
- envs: list = ["temp", "dev", "prod"]
10
+ envs: list = ["temp", "dev", "prod", "qa"]
11
11
  return envs
12
12
 
13
13
  def parse_arguments(self):
@@ -28,13 +28,33 @@ class Cli:
28
28
  required=False,
29
29
  help="Tag that determines which tagged flow runs, select from the flows corresponding model flow.",
30
30
  )
31
+
32
+ parser.add_argument(
33
+ "--select",
34
+ metavar="SELECT",
35
+ type=str,
36
+ nargs="*",
37
+ required=False,
38
+ help="Select allows you to select multiple tags, ex: --select tag:tag1 tag:tag2 tag:tag3",
39
+ default=[],
40
+ )
41
+
42
+ parser.add_argument(
43
+ "--args",
44
+ metavar="ARGS",
45
+ type=str,
46
+ nargs="*",
47
+ required=False,
48
+ help="Additional arguments to pass to the flow, ex: --args arg1 arg2 arg3",
49
+ default=[],
50
+ )
51
+
31
52
  parser.add_argument(
32
53
  "-r",
33
54
  "--refresh",
34
55
  type=str,
35
56
  required=False,
36
- help="",
37
- choices=["full"],
57
+ help="Add a date to refresh the data.",
38
58
  )
39
59
  # get args
40
60
  args = parser.parse_args()
@@ -0,0 +1,101 @@
1
+ """
2
+ This script provides functionalities for encrypting a file and decrypting an AES encrypted file.
3
+ The decrypted file's contents are then loaded into the environment using the dotenv library.
4
+ """
5
+
6
+ import os
7
+ import argparse
8
+ import pyAesCrypt
9
+ from getpass import getpass
10
+ from dotenv import load_dotenv
11
+
12
+ from pypostman.modules.logger import Log
13
+
14
+ log = Log() # Initialize the logger
15
+
16
+
17
+ def parse_arguments(description, arguments):
18
+ """
19
+ Parses command line arguments.
20
+
21
+ Parameters:
22
+ description (str): Description of the command line program.
23
+ arguments (list): A list of tuples, where each tuple is an argument in the format (short_option, long_option, description).
24
+
25
+ Returns:
26
+ argparse.Namespace: The parsed command line arguments.
27
+ """
28
+ parser = argparse.ArgumentParser(description=description)
29
+ for argument in arguments:
30
+ parser.add_argument(
31
+ argument[0], argument[1], type=str, required=True, help=argument[2]
32
+ )
33
+ return parser.parse_args()
34
+
35
+
36
+ def encrypt_file():
37
+ """
38
+ Encrypts a file with AES encryption. The password and file path are provided as command line arguments.
39
+ """
40
+ args = parse_arguments(
41
+ "Encrypt a file",
42
+ [
43
+ ("-p", "--password", "The password to encrypt the file"),
44
+ ("-f", "--file", "The path to the file to encrypt"),
45
+ ],
46
+ )
47
+ filepath = args.file
48
+ password = args.password
49
+ bufferSize = 64 * 1024 # Encryption/decryption buffer size
50
+ confirm_password = getpass("Please confirm your password: ")
51
+
52
+ if password != confirm_password:
53
+ log.error("Passwords do not match!")
54
+ return
55
+
56
+ encrypted_filepath = filepath + ".aes" # Generate encrypted file path
57
+ pyAesCrypt.encryptFile(
58
+ filepath, encrypted_filepath, password, bufferSize
59
+ ) # Encrypt the file
60
+
61
+ try:
62
+ log.info(
63
+ f"Encrypted file: {encrypted_filepath}"
64
+ ) # Log the path of the encrypted file
65
+ except ValueError as ve:
66
+ log.error(ve)
67
+
68
+
69
+ def decrypt_and_loadenv(password: str, filepath: str, remove_decrypted: bool = True):
70
+ """
71
+ Decrypts an AES encrypted file and loads its contents to the environment using the dotenv library.
72
+
73
+ Parameters:
74
+ password (str): The password to decrypt the AES encrypted file.
75
+ filepath (str): The path to the AES encrypted file that needs to be decrypted.
76
+ remove_decrypted (bool): A flag that, if True, removes the decrypted file after its contents are loaded to the environment.
77
+ Default value is True. If False, the decrypted file will remain in the file system,
78
+ and its path will be logged.
79
+ """
80
+ bufferSize = 64 * 1024 # Encryption/decryption buffer size
81
+ decrypted_filepath = filepath.replace(".aes", "") # Generate decrypted file path
82
+
83
+ try:
84
+ pyAesCrypt.decryptFile(
85
+ filepath, decrypted_filepath, password, bufferSize
86
+ ) # Decrypt the file
87
+ load_dotenv(
88
+ dotenv_path=decrypted_filepath
89
+ ) # Load the decrypted content to environment
90
+ except Exception as e:
91
+ log.error(f"An error occurred during decryption: {str(e)}")
92
+ return
93
+
94
+ if remove_decrypted:
95
+ os.remove(decrypted_filepath) # Remove the decrypted file
96
+ else:
97
+ log.info(decrypted_filepath) # Log the path of the decrypted file
98
+
99
+
100
+ if __name__ == "__main__":
101
+ encrypt_file()