ruf-common 1.0.1__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.
- ruf_common-1.0.1/LICENSE +21 -0
- ruf_common-1.0.1/PKG-INFO +102 -0
- ruf_common-1.0.1/README.md +65 -0
- ruf_common-1.0.1/pyproject.toml +68 -0
- ruf_common-1.0.1/ruf_common/__init__.py +27 -0
- ruf_common-1.0.1/ruf_common/aws.py +206 -0
- ruf_common-1.0.1/ruf_common/country_code_converter.py +313 -0
- ruf_common-1.0.1/ruf_common/data.py +417 -0
- ruf_common-1.0.1/ruf_common/database.py +484 -0
- ruf_common-1.0.1/ruf_common/database_sqlite3.py +490 -0
- ruf_common-1.0.1/ruf_common/helper.py +614 -0
- ruf_common-1.0.1/ruf_common/html_to_markdown.py +215 -0
- ruf_common-1.0.1/ruf_common/lfs.py +276 -0
- ruf_common-1.0.1/ruf_common/logging.py +99 -0
- ruf_common-1.0.1/ruf_common/network.py +95 -0
- ruf_common-1.0.1/ruf_common/stats.py +39 -0
- ruf_common-1.0.1/ruf_common/timezone_lookup.py +138 -0
- ruf_common-1.0.1/ruf_common/xml_formatter.py +517 -0
- ruf_common-1.0.1/ruf_common.egg-info/PKG-INFO +102 -0
- ruf_common-1.0.1/ruf_common.egg-info/SOURCES.txt +25 -0
- ruf_common-1.0.1/ruf_common.egg-info/dependency_links.txt +1 -0
- ruf_common-1.0.1/ruf_common.egg-info/requires.txt +18 -0
- ruf_common-1.0.1/ruf_common.egg-info/top_level.txt +1 -0
- ruf_common-1.0.1/setup.cfg +4 -0
- ruf_common-1.0.1/tests/test_data.py +18 -0
- ruf_common-1.0.1/tests/test_helper.py +18 -0
- ruf_common-1.0.1/tests/test_imports.py +72 -0
ruf_common-1.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Brian Ruf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ruf-common
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Common Python utilities
|
|
5
|
+
Author-email: Brian Ruf <Brian@RufRisk.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/brian-ruf/ruf-common-python
|
|
8
|
+
Project-URL: Repository, https://github.com/brian-ruf/ruf-common-python.git
|
|
9
|
+
Project-URL: Documentation, https://github.com/brian-ruf/ruf-common-python#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/brian-ruf/ruf-common-python/issues
|
|
11
|
+
Keywords: utilities,common,helpers
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: loguru>=0.7.3
|
|
20
|
+
Requires-Dist: elementpath>=4.7.0
|
|
21
|
+
Requires-Dist: pytz>=2025.2
|
|
22
|
+
Requires-Dist: tzlocal>=5.3.1
|
|
23
|
+
Requires-Dist: geopy>=2.4.1
|
|
24
|
+
Requires-Dist: timezonefinder>=8.1.0
|
|
25
|
+
Requires-Dist: aiohttp>=3.12.15
|
|
26
|
+
Requires-Dist: boto3
|
|
27
|
+
Requires-Dist: requests>=2.31.0
|
|
28
|
+
Requires-Dist: pycountry>=22.3.5
|
|
29
|
+
Requires-Dist: html2text>=2020.1.16
|
|
30
|
+
Requires-Dist: packaging>=23.1
|
|
31
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# COMMON PYTHON MODULES
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
This is a collection of python modules I have created and use in several of my projects. This is just a convenient way for me to keep them in sync across projects. The repo is public so that it can be easily referenced by people using my public projects.
|
|
42
|
+
|
|
43
|
+
Feedback welcome in the form of a [GitHub issue](https://github.com/brian-ruf/ruf-common-python/issues). While I will try to address issues in a timely matter, I only intend to invest in feature requests that align with my project work. Feel free to contribute backward compatible enhancements.
|
|
44
|
+
|
|
45
|
+
## Dependencies
|
|
46
|
+
|
|
47
|
+
Collectively, these modules rely on the following external libraries:
|
|
48
|
+
|
|
49
|
+
- loguru
|
|
50
|
+
- elementpath
|
|
51
|
+
- pytz
|
|
52
|
+
- tzlocal
|
|
53
|
+
- geopy
|
|
54
|
+
- timezonefinder
|
|
55
|
+
- aiohttp
|
|
56
|
+
- boto3
|
|
57
|
+
- requests
|
|
58
|
+
- pycountry
|
|
59
|
+
- html2text
|
|
60
|
+
- packaging
|
|
61
|
+
- pyyaml
|
|
62
|
+
|
|
63
|
+
## Setup
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
To use this submodule in your GitHub repository:
|
|
68
|
+
|
|
69
|
+
1. With your repository's `./src` folder as the default location, issue the following command:
|
|
70
|
+
```
|
|
71
|
+
git submodule add https://github.com/brian-ruf/common-python.git common
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
2. Import the library into your python modules:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from common import * # to import all
|
|
78
|
+
|
|
79
|
+
# OR
|
|
80
|
+
|
|
81
|
+
from common import misc # import only one of the modules
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Modules
|
|
85
|
+
|
|
86
|
+
The following modules are exposed to your application via the above instructions:
|
|
87
|
+
|
|
88
|
+
- `aws.py`: Functions for interacting with AWS services
|
|
89
|
+
- `country_code_converter.py`: Functions for converting between country code formats
|
|
90
|
+
- `data.py`: Functions for managing and manipulating XML, JSON and YAML content
|
|
91
|
+
- `database.py`: Functions for interacting with a database. These functions operate the same for all supported databases
|
|
92
|
+
- `helper.py`: Various helper functions
|
|
93
|
+
- `html_to_markdown.py`: Functions for converting HTML content to Markdown
|
|
94
|
+
- `lfs.py`: Functions for interacting with the local file system (LFS)
|
|
95
|
+
- `logging.py`: Logging configuration and utilities
|
|
96
|
+
- `network.py`: Functions for network operations
|
|
97
|
+
- `stats.py`: Statistical helper functions
|
|
98
|
+
- `timezone_lookup.py`: Functions for timezone lookups based on location
|
|
99
|
+
- `xml_formatter.py`: Functions for formatting XML content
|
|
100
|
+
|
|
101
|
+
The following additional modules are present and support the above, but are not directly exposed:
|
|
102
|
+
- `database_sqlite3.py`: Any database-specific interactions are collected in a single file for that database
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# COMMON PYTHON MODULES
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This is a collection of python modules I have created and use in several of my projects. This is just a convenient way for me to keep them in sync across projects. The repo is public so that it can be easily referenced by people using my public projects.
|
|
5
|
+
|
|
6
|
+
Feedback welcome in the form of a [GitHub issue](https://github.com/brian-ruf/ruf-common-python/issues). While I will try to address issues in a timely matter, I only intend to invest in feature requests that align with my project work. Feel free to contribute backward compatible enhancements.
|
|
7
|
+
|
|
8
|
+
## Dependencies
|
|
9
|
+
|
|
10
|
+
Collectively, these modules rely on the following external libraries:
|
|
11
|
+
|
|
12
|
+
- loguru
|
|
13
|
+
- elementpath
|
|
14
|
+
- pytz
|
|
15
|
+
- tzlocal
|
|
16
|
+
- geopy
|
|
17
|
+
- timezonefinder
|
|
18
|
+
- aiohttp
|
|
19
|
+
- boto3
|
|
20
|
+
- requests
|
|
21
|
+
- pycountry
|
|
22
|
+
- html2text
|
|
23
|
+
- packaging
|
|
24
|
+
- pyyaml
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
To use this submodule in your GitHub repository:
|
|
31
|
+
|
|
32
|
+
1. With your repository's `./src` folder as the default location, issue the following command:
|
|
33
|
+
```
|
|
34
|
+
git submodule add https://github.com/brian-ruf/common-python.git common
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
2. Import the library into your python modules:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from common import * # to import all
|
|
41
|
+
|
|
42
|
+
# OR
|
|
43
|
+
|
|
44
|
+
from common import misc # import only one of the modules
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Modules
|
|
48
|
+
|
|
49
|
+
The following modules are exposed to your application via the above instructions:
|
|
50
|
+
|
|
51
|
+
- `aws.py`: Functions for interacting with AWS services
|
|
52
|
+
- `country_code_converter.py`: Functions for converting between country code formats
|
|
53
|
+
- `data.py`: Functions for managing and manipulating XML, JSON and YAML content
|
|
54
|
+
- `database.py`: Functions for interacting with a database. These functions operate the same for all supported databases
|
|
55
|
+
- `helper.py`: Various helper functions
|
|
56
|
+
- `html_to_markdown.py`: Functions for converting HTML content to Markdown
|
|
57
|
+
- `lfs.py`: Functions for interacting with the local file system (LFS)
|
|
58
|
+
- `logging.py`: Logging configuration and utilities
|
|
59
|
+
- `network.py`: Functions for network operations
|
|
60
|
+
- `stats.py`: Statistical helper functions
|
|
61
|
+
- `timezone_lookup.py`: Functions for timezone lookups based on location
|
|
62
|
+
- `xml_formatter.py`: Functions for formatting XML content
|
|
63
|
+
|
|
64
|
+
The following additional modules are present and support the above, but are not directly exposed:
|
|
65
|
+
- `database_sqlite3.py`: Any database-specific interactions are collected in a single file for that database
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ruf-common"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Common Python utilities"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Brian Ruf", email = "Brian@RufRisk.com" }]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
keywords = ["utilities", "common", "helpers"]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"loguru>=0.7.3",
|
|
22
|
+
"elementpath>=4.7.0",
|
|
23
|
+
"pytz>=2025.2",
|
|
24
|
+
"tzlocal>=5.3.1",
|
|
25
|
+
"geopy>=2.4.1",
|
|
26
|
+
"timezonefinder>=8.1.0",
|
|
27
|
+
"aiohttp>=3.12.15",
|
|
28
|
+
"boto3",
|
|
29
|
+
"requests>=2.31.0",
|
|
30
|
+
"pycountry>=22.3.5",
|
|
31
|
+
"html2text>=2020.1.16",
|
|
32
|
+
"packaging>=23.1",
|
|
33
|
+
"pyyaml>=6.0.2"
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/brian-ruf/ruf-common-python"
|
|
38
|
+
Repository = "https://github.com/brian-ruf/ruf-common-python.git"
|
|
39
|
+
Documentation = "https://github.com/brian-ruf/ruf-common-python#readme"
|
|
40
|
+
Issues = "https://github.com/brian-ruf/ruf-common-python/issues"
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=7.0.0",
|
|
45
|
+
"pytest-cov>=4.0.0",
|
|
46
|
+
"pytest-asyncio>=0.21.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["."]
|
|
51
|
+
include = ["ruf_common*"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = ["test_*.py"]
|
|
56
|
+
python_classes = ["Test*"]
|
|
57
|
+
python_functions = ["test_*"]
|
|
58
|
+
addopts = "-v --tb=short"
|
|
59
|
+
|
|
60
|
+
[tool.coverage.run]
|
|
61
|
+
source = ["ruf_common"]
|
|
62
|
+
omit = ["tests/*", "*/__pycache__/*"]
|
|
63
|
+
|
|
64
|
+
[tool.coverage.report]
|
|
65
|
+
exclude_lines = [
|
|
66
|
+
"pragma: no cover",
|
|
67
|
+
"if __name__ == .__main__.:",
|
|
68
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from . import data
|
|
2
|
+
from . import database
|
|
3
|
+
from . import lfs
|
|
4
|
+
from . import helper
|
|
5
|
+
from . import network
|
|
6
|
+
from . import aws
|
|
7
|
+
from . import logging
|
|
8
|
+
from . import stats
|
|
9
|
+
from . import country_code_converter
|
|
10
|
+
from . import html_to_markdown
|
|
11
|
+
from . import timezone_lookup
|
|
12
|
+
from . import xml_formatter
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"data",
|
|
16
|
+
"database",
|
|
17
|
+
"lfs",
|
|
18
|
+
"helper",
|
|
19
|
+
"network",
|
|
20
|
+
"aws",
|
|
21
|
+
"logging",
|
|
22
|
+
"stats",
|
|
23
|
+
"country_code_converter",
|
|
24
|
+
"html_to_markdown",
|
|
25
|
+
"timezone_lookup",
|
|
26
|
+
"xml_formatter"
|
|
27
|
+
]
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS S3 interaction functions
|
|
3
|
+
"""
|
|
4
|
+
# import time
|
|
5
|
+
# import json
|
|
6
|
+
# import os
|
|
7
|
+
# import sys
|
|
8
|
+
# import urllib.request
|
|
9
|
+
# import resource # used to monitor memory
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
import boto3 # Library: boto3 -- for interacting with AWS S3 buckets
|
|
13
|
+
from botocore.exceptions import ClientError
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
S3_BUCKETS = {}
|
|
17
|
+
S3_CLIENT: Any = None
|
|
18
|
+
S3_RESOURCE: Any = None
|
|
19
|
+
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# --- INTERACT WITH AN S3 BUCKET ---
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# - s3_connection (aws_region, aws_key_id, aws_key, OPTIONAL use_client) -> Boolean
|
|
24
|
+
# - s3_open_bucket (bucket_name, aws_region, aws_key_id, aws_key) -> Boolean
|
|
25
|
+
# - s3_chkdir (bucket_name, path) -> Boolean
|
|
26
|
+
# - s3_chkfile (bucket_name, path) -> Boolean
|
|
27
|
+
# - s3_mkdir (bucket_name, path) -> Boolean
|
|
28
|
+
# - s3_get_file (bucket_name, file_name) -> Boolean, String
|
|
29
|
+
# - s3_put_file (bucket_name, file_name, content)-> Boolean
|
|
30
|
+
# - s3_rm_file (bucket_name, file)
|
|
31
|
+
# =============================================================================
|
|
32
|
+
|
|
33
|
+
# Establishes a connection to the S3 service
|
|
34
|
+
# Returns True if successful (S3 service is available/reachable in the specified region and access key is accepted as valid).False otherwise.
|
|
35
|
+
def s3_connection(aws_region, aws_key_id, aws_key, use_client=False):
|
|
36
|
+
global S3_CLIENT, S3_RESOURCE
|
|
37
|
+
status = False
|
|
38
|
+
s3 = None
|
|
39
|
+
try:
|
|
40
|
+
if S3_CLIENT is None:
|
|
41
|
+
s3 = boto3.client(
|
|
42
|
+
service_name="s3",
|
|
43
|
+
region_name=aws_region,
|
|
44
|
+
aws_access_key_id=aws_key_id,
|
|
45
|
+
aws_secret_access_key=aws_key
|
|
46
|
+
)
|
|
47
|
+
S3_CLIENT = s3
|
|
48
|
+
else:
|
|
49
|
+
# s3 = S3_CLIENT # cache it for reuse
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
if S3_RESOURCE is None:
|
|
53
|
+
s3 = boto3.resource(
|
|
54
|
+
service_name="s3",
|
|
55
|
+
region_name=aws_region,
|
|
56
|
+
aws_access_key_id=aws_key_id,
|
|
57
|
+
aws_secret_access_key=aws_key
|
|
58
|
+
)
|
|
59
|
+
S3_RESOURCE = s3 # cache it for reuse
|
|
60
|
+
else:
|
|
61
|
+
# s3 = S3_CLIENT # cache it for reuse
|
|
62
|
+
pass
|
|
63
|
+
status = True
|
|
64
|
+
except Exception as error:
|
|
65
|
+
logger.error(f"Unable to connect to AWS S3 service in {aws_region}. Possible invalid key or blocked communication. ({type(error).__name__}) {str(error)}")
|
|
66
|
+
return status
|
|
67
|
+
|
|
68
|
+
# Opens a connect to an S3 bucket
|
|
69
|
+
# Returns True if successful (bucket is available and access is granted).False otherwise.
|
|
70
|
+
# Once a bucket is open, its object is cached until the application halts
|
|
71
|
+
def s3_open_bucket(bucket_name, aws_region, aws_key_id, aws_key):
|
|
72
|
+
global S3_RESOURCE, S3_BUCKETS
|
|
73
|
+
status = False
|
|
74
|
+
if bucket_name not in S3_BUCKETS:
|
|
75
|
+
status = s3_connection(aws_region, aws_key_id, aws_key)
|
|
76
|
+
if status:
|
|
77
|
+
try:
|
|
78
|
+
status = False
|
|
79
|
+
if S3_RESOURCE is not None:
|
|
80
|
+
s3_bucket = S3_RESOURCE.Bucket(bucket_name)
|
|
81
|
+
status = True
|
|
82
|
+
S3_BUCKETS[bucket_name] = s3_bucket # Cache the Resource connection to the Bucket
|
|
83
|
+
except ClientError as error:
|
|
84
|
+
logger.warning(f"Unable to connect to {bucket_name}: {error}")
|
|
85
|
+
except Exception as error:
|
|
86
|
+
logger.error(f"{bucket_name} S3 bucket not found or no access. ({type(error).__name__}) {str(error)}") # .message)
|
|
87
|
+
|
|
88
|
+
return status
|
|
89
|
+
|
|
90
|
+
# Checks for the existence of a folder or file on an S3 bucket
|
|
91
|
+
# Returns True if the folder is found
|
|
92
|
+
# Returns False if the folder's existence could not be determined or
|
|
93
|
+
# if there was an error creating the folder.
|
|
94
|
+
def s3_chkdir(bucket_name, path):
|
|
95
|
+
global S3_BUCKETS
|
|
96
|
+
status = False
|
|
97
|
+
# status, s3 = s3_connection(aws_region, aws_key_id, aws_key, True)
|
|
98
|
+
if bucket_name in S3_BUCKETS:
|
|
99
|
+
try:
|
|
100
|
+
for obj in S3_BUCKETS[bucket_name].objects.all():
|
|
101
|
+
if path == obj.key or path + "/" == obj.key:
|
|
102
|
+
# logger.info(path + " found in S3 Bucket")
|
|
103
|
+
status = True
|
|
104
|
+
break
|
|
105
|
+
if not status:
|
|
106
|
+
# logger.info(path + " NOT found in S3 Bucket.")
|
|
107
|
+
pass
|
|
108
|
+
except Exception as error:
|
|
109
|
+
if type(error).__name__ == "RequestTimeTooSkewed":
|
|
110
|
+
logger.error("Local host's time is too far out of sync with AWS time. ** !! This is a common problem with WSL after the local host wakes from sleep. Fix time in the local time and try again.")
|
|
111
|
+
else:
|
|
112
|
+
logger.error(f"Error checking folder on S3 bucket. ({type(error).__name__}) {str(error)}")
|
|
113
|
+
else:
|
|
114
|
+
logger.error("Attempt to check directory on S3 bucket before opening S3 bucket.")
|
|
115
|
+
|
|
116
|
+
return status
|
|
117
|
+
|
|
118
|
+
# Creates a folder on an S3 bucket
|
|
119
|
+
# Returns True if the folder already exists or was created successfully
|
|
120
|
+
# Returns False if the folder's existence could not be determined or
|
|
121
|
+
# if there was an error creating the folder.
|
|
122
|
+
def s3_mkdir(bucket_name, path):
|
|
123
|
+
global S3_BUCKETS
|
|
124
|
+
status = False
|
|
125
|
+
# status, s3 = s3_connection(aws_region, aws_key_id, aws_key, True)
|
|
126
|
+
if bucket_name in S3_BUCKETS:
|
|
127
|
+
status = s3_chkdir(bucket_name, path)
|
|
128
|
+
if not status:
|
|
129
|
+
try:
|
|
130
|
+
S3_BUCKETS[bucket_name].put_object(Key=path + "/")
|
|
131
|
+
status = True
|
|
132
|
+
except Exception as error:
|
|
133
|
+
logger.error(f"Problem on S3 creating {path} ({type(error).__name__}) {str(error)}")
|
|
134
|
+
else:
|
|
135
|
+
logger.error("Attempt to make directory on S3 bucket before opening S3 bucket.")
|
|
136
|
+
|
|
137
|
+
return status
|
|
138
|
+
|
|
139
|
+
# Gets a file from a named S3 bucket
|
|
140
|
+
# Returns a boolean (true if successful, false if not) and actual file content
|
|
141
|
+
def s3_get_file(bucket_name, file_name):
|
|
142
|
+
global S3_CLIENT
|
|
143
|
+
status = False
|
|
144
|
+
ret_value = ""
|
|
145
|
+
if S3_CLIENT is not None:
|
|
146
|
+
try:
|
|
147
|
+
content_object = S3_CLIENT.get_object(Bucket=bucket_name, Key=file_name)
|
|
148
|
+
ret_value = content_object['Body'].read().decode('utf-8')
|
|
149
|
+
status = True
|
|
150
|
+
except ClientError as e:
|
|
151
|
+
ret_value = ""
|
|
152
|
+
error_code = e.response["Error"]["Code"]
|
|
153
|
+
if error_code == "AccessDenied":
|
|
154
|
+
logger.error("Access Denied fetching " + file_name)
|
|
155
|
+
else: # error_code == "InvalidLocationConstraint":
|
|
156
|
+
logger.error(f"{e.response['Error']['Code']} fetching {file_name}: {e.response['Error']['Message']}")
|
|
157
|
+
except Exception as error:
|
|
158
|
+
ret_value = ""
|
|
159
|
+
logger.error(f"Problem fetching {file_name} in S3 bucket {bucket_name} ({type(error).__name__}) {str(error)}")
|
|
160
|
+
else:
|
|
161
|
+
logger.error("Attempt to get file on S3 bucket before opening S3 bucket.")
|
|
162
|
+
|
|
163
|
+
return status, ret_value
|
|
164
|
+
|
|
165
|
+
# Saves a file to a named S3 bucket
|
|
166
|
+
# Returns true if successful, false if not
|
|
167
|
+
def s3_put_file(bucket_name, file_name, content):
|
|
168
|
+
global S3_CLIENT
|
|
169
|
+
status = False
|
|
170
|
+
if S3_CLIENT is not None:
|
|
171
|
+
try:
|
|
172
|
+
S3_CLIENT.put_object(Bucket=bucket_name, Key=file_name, Body=content, ContentEncoding="utf-8")
|
|
173
|
+
status = True
|
|
174
|
+
except ClientError as e:
|
|
175
|
+
# ['Error']['Code'] e.g. 'EntityAlreadyExists' or 'ValidationError'
|
|
176
|
+
# ['ResponseMetadata']['HTTPStatusCode'] e.g. 400
|
|
177
|
+
# ['ResponseMetadata']['RequestId'] e.g. 'd2b06652-88d7-11e5-99d0-812348583a35'
|
|
178
|
+
# ['Error']['Message'] e.g. "An error occurred (EntityAlreadyExists) ..."
|
|
179
|
+
# ['Error']['Type'] e.g. 'Sender'
|
|
180
|
+
error_code = e.response["Error"]["Code"]
|
|
181
|
+
if error_code == "AccessDenied":
|
|
182
|
+
logger.error("Access Denied saving " + file_name)
|
|
183
|
+
else: # error_code == "InvalidLocationConstraint":
|
|
184
|
+
logger.error(f"{e.response['Error']['Code']} saving {file_name}: {e.response['Error']['Message']}")
|
|
185
|
+
except Exception as error:
|
|
186
|
+
logger.error(f"Problem saving {file_name} in S3 bucket {bucket_name} ({type(error).__name__}) {str(error)}")
|
|
187
|
+
return status
|
|
188
|
+
|
|
189
|
+
# Removes a file from an S3 bucket
|
|
190
|
+
# Returns true if successful, false if not
|
|
191
|
+
def s3_rm_file(bucket_name, file):
|
|
192
|
+
logger.error("S3 Remove File not implemented (common.py/s3_rm_file)")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# =============================================================================
|
|
198
|
+
# --- MAIN: Only runs if the module is executed stand-alone. ---
|
|
199
|
+
# =============================================================================
|
|
200
|
+
if __name__ == '__main__':
|
|
201
|
+
# Execute when the module is not initialized from an import statement.
|
|
202
|
+
logger.info("--- START ---")
|
|
203
|
+
|
|
204
|
+
logger.info("This contains functions common the OSCAL services capability. This module does nothing when run individually.")
|
|
205
|
+
|
|
206
|
+
logger.info("--- END ---")
|