From 51d22ceb77fb7238af3ef51aec836c1f787732dd Mon Sep 17 00:00:00 2001
From: ByteDream A collection of useful and often repeated python methods
+ Installation + • + Examples + • + Licence +
+ + +## Installation +- Use `Python >= 3.6` + +- Install it + - via `pip` + ```bash + pip install dreamutils + ``` + - or with `git` + ```bash + git clone https://github.com/ByteDream/dreamutils.git + cd dreamutils + python setup.py install + ``` + +## Examples + +Here are examples of some useful packages + +#### XML + +A easy to use and powerful xml manipulation class +```python +import dreamutils.types.xml as xml + +my_xml = xml.new_xml() +sub_elem_id = my_xml.add(0, 'sub_elem', 'example_text') +# every new created element has an id + +my_xml.get_element(sub_elem_id).attrib = {'attrib': 'example'} +# with the id you can obtain the element later... +my_xml.add(sub_elem_id, 'sub_sub_elem') +# ... and use it to add new sub element + +# Note: the root element has always the id `0` + +print(my_xml.get_string()) +``` + +#### Sorting + +A collection of sorting algorithms (the most common I think) + +```python +from dreamutils.sort import QuickSort + +sorted = QuickSort.integer([2, 9, 4, 623, 5]) +print(sorted) +``` + +#### Net + +A file with nice internet methods + +```python +from dreamutils.net import get_ip_infos + +infos = get_ip_infos('8.8.8.8') +# if no argument is passed, information about your own ip will be returned +print(infos) +``` + +## Testing + +**The tools are (currently) only tested on linux, but they should also work on Windows and MacOS.** + +So if there are any problems feel free to open a new [issue](https://github.com/ByteDream/dreamutils/issues/new). + + +## Licence + +This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the [LICENSE](LICENCE) file for more details. \ No newline at end of file diff --git a/dreamutils/__init__.py b/dreamutils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dreamutils/_internal.py b/dreamutils/_internal.py new file mode 100644 index 0000000..94906c9 --- /dev/null +++ b/dreamutils/_internal.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 + + +class _NotSupported: + + @staticmethod + def os() -> str: + return 'Your OS is not supported' diff --git a/dreamutils/encoding.py b/dreamutils/encoding.py new file mode 100644 index 0000000..8a2894e --- /dev/null +++ b/dreamutils/encoding.py @@ -0,0 +1,6 @@ +#!/usr/bin/python3 + +ASCII = 'ascii' + +UTF_8 = 'utf-8' +UTF_16 = 'utf-16' diff --git a/dreamutils/file.py b/dreamutils/file.py new file mode 100644 index 0000000..d1d8a24 --- /dev/null +++ b/dreamutils/file.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 + +import os as _os +from typing import Generator as _Generator, List as _List, Union as _Union + +"""This file contains utils for file manipulation""" + + +def recursive_directory_data(directory: str, full_path=True) -> _Generator[str]: + """ + _Lists every subfile and subdirectory of the given directory + + Args: + directory (str): Path of directory from which you want to get the subfiles /- directories + full_path (bool, optional): If True the full path of the files gets returned. If False the relative path + + Yields: + str: The next recursive file or directory in the given directory + + Examples: + >>> print(recursive_directory_data('/home/ByteDream/NOTHENTAI')) + ['/home/ByteDream/NOTHENTAI/download_1.mp4', '/home/ByteDream/NOTHENTAI/best/', '/home/ByteDream/NOTHENTAI/best/best_1.mp4'] + + """ + if directory.endswith(_os.sep): + directory = directory[:-1] + + for path, subdirs, files in _os.walk(directory): + if full_path: + yield _os.path.join(path, _os.sep.join(subdirs)) + else: + if path.endswith(_os.sep): + path = path[:path.rfind(_os.sep)] + yield path[path.rfind(_os.sep) + 1:] + for name in files: + if full_path: + yield _os.path.join(path, name) + else: + yield _os.path.join(path.replace(directory, ''), name) + + +def replace_line(file: str, to_replace: _Union[int, str, _List[int], _List[str]], new_content: str, ignore_case=False) -> None: + """ + Replaces lines given by their number or content with new content + + Args: + file: File in which the lines are to be replaced + to_replace: Content like line numbers or line content which should be replaced + new_content: New content to replace the old one + ignore_case: If True and `to_replace` is a string or a _List of strings, the comparison is not case sensitive + + Examples: + test.txt before: + ``` + line 1 + line 2 + line 3 + line 4 + ``` + + >>> replace_line('test.txt', 'line 3', 'replaced line 3') + + test.txt after: + ``` + line 1 + line 2 + replaced line 3 + line 4 + ``` + + """ + lines = open(file, 'r').readlines() + + if isinstance(to_replace, str): + to_replace = [to_replace] + + if ignore_case: + for index, item in enumerate(to_replace): + to_replace[index] = item.lower() + + if isinstance(to_replace, int): + lines[to_replace] = new_content + elif to_replace: + if isinstance(to_replace[0], int): + for index, _ in enumerate(lines.copy()): + if index in to_replace: + lines[index] = new_content + to_replace.remove(index) + else: + for index, item in enumerate(lines.copy()): + if ignore_case: + item = item.lower() + if item in to_replace: + lines[index] = new_content + + with open(file, 'w') as file: + file.writelines(lines) + file.cl_ose() diff --git a/dreamutils/net.py b/dreamutils/net.py new file mode 100644 index 0000000..1251ec1 --- /dev/null +++ b/dreamutils/net.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 + +import socket as _socket + +from json import load as _load +from urllib.request import urlopen as _urlopen + +"""This file contains utils for networking stuff""" + + +def online(timeout=5) -> bool: + """ + Tests if your machine is connected to the internet + + Args: + timeout (optional): Timeout until False is returned if no connection can be established + + Returns: + If the pc is online (theoretically it could return False when google.com is down, but if this happens your connect status is the smallest problem) + + Examples: + >>> print(online(2)) + True + + """ + try: + host = _socket.gethostbyname('google.com') + + connection = _socket.create_connection((host, 80), timeout) + connection.close() + + return True + except: + return False + + +def get_ip_infos(ip_address: str = None) -> dict: + """ + A dict with infos about the ip address of your computer (unless you use a vpn) or a specified ip + + Args: + ip_address (optional): IP from which you want to receive the information + + Returns: + A dict filled with the ip address information + + Examples: + >>> print(get_ip_infos()()) + {'ip': '69.69.69.69', + 'hostname': 'examplehostname', + 'city': 'Suginami City', + 'region': 'Tokyo', + 'country': 'JP, + 'loc': '35.6986, 139.6367', + 'org': 'exampleprovider', + 'postal': '166-0015', + 'timezone': 'Asia/Tokyo', + 'readme': 'https://ipinfo.io/missingauth'} + + """ + if ip_address: + return _load(_urlopen('http://ipinfo.io/' + ip_address + '/json')) + else: + return _load(_urlopen('http://ipinfo.io/json')) diff --git a/dreamutils/os.py b/dreamutils/os.py new file mode 100644 index 0000000..0eb9b55 --- /dev/null +++ b/dreamutils/os.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 + +import ctypes as _ctypes +import os as _os +from enum import Enum as _Enum +from sys import platform as _platform + +"""This file contains utils for os manipulation""" + + +class Platform(_Enum): + LINUX = 'linux' + MAC = 'mac' + WINDOWS = 'windows' + OTHER = 'other' + + +def platform() -> Platform: + """ + Returns the current platform / os + + Returns: + Platform: The working platform / os + + Examples: + >>> print(platform()) + Platform.LINUX + + """ + if _platform in ['linux', 'linux2']: + return Platform.LINUX + elif _platform == 'darwin': + return Platform.MAC + elif _platform in ['win32', 'cygwin', 'msys']: + return Platform.WINDOWS + else: + return Platform.OTHER + + +def is_root() -> bool: + """ + Checks if the python script is running with root / admin rights + + Notes: + If the user who started the script is the root user on the system (e.g. pi on raspberry pi os) + the script can execute root command even if it was not started with root rights + + Returns: + If the script was started with root rights + + Examples: + >>> print(is_root()) + False + + """ + if platform().LINUX or platform().MAC: + return _os.geteuid() == 0 + elif platform().WINDOWS: + return _ctypes.windll.shell32.IsUserAdmin() != 0 diff --git a/dreamutils/python.py b/dreamutils/python.py new file mode 100644 index 0000000..5e3fc28 --- /dev/null +++ b/dreamutils/python.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 + +import ast as _ast +from types import FunctionType as _FunctionType, ModuleType as _ModuleType, MethodType as _MethodType +from typing import Dict as _Dict, List as _List, Union as _Union + +"""This file contains utils to analyze and manipulate raw python files""" + + +def class_of_method(method: _MethodType) -> _Union[None, type]: + """ + Returns the class in which the given `method` was defined + + Args: + method (method): Method from which you want to find out from which class it was declared + + Returns: + The class where the method was defined or None + + """ + method_name = method.__name__ + if method.__self__: + classes = [method.__self__.__class__] + else: + classes = [method.im_class] + while classes: + c = classes.pop() + if method_name in c.__dict__: + return c + else: + classes = list(c.__bases__) + classes + return None + + +def defined_functions_and_classes(file_or_module: _Union[str, _ModuleType], include_external_modules=False) -> _Dict[str, _Union[_FunctionType, type, _ModuleType]]: + """ + Returns all functions and classes from a given python file or python module + + Args: + file_or_module (str or ModuleType): The file or module from which you want to get the functions and classes. + include_external_modules (bool): If True the modules, functions and classes which were imported are getting returned too. + + Returns: + dict: A dict of str - function, class or module pairs with all functions, classes and modules -> (if `include_external_modules` is True). + + Notes: + If `file_or_module` is a file the returned dict values will all be None + + """ + functions_and_classes = {} + if isinstance(file_or_module, str): + with open(file_or_module, "r") as file: + parsed = _ast.parse(file.read(), filename=file_or_module) + for x in parsed.body: + if isinstance(x, (_ast.FunctionDef, _ast.ClassDef, _ModuleType)): + functions_and_classes[x.name] = None + file.close() + elif isinstance(file_or_module, _ModuleType): + for x in dir(file_or_module): + functions_and_classes[x] = getattr(file_or_module, x) + + if not include_external_modules: + for name, module_type in functions_and_classes.copy().items(): + try: + if isinstance(module_type, _ModuleType): + del functions_and_classes[name] + except: + pass + + return functions_and_classes + + +def extract_decorated_func(function: _FunctionType) -> _FunctionType: + """ + Extracts the 'core' function of a function which has decorators. + When a function with decorators is called, the decorators are called first and then the function. + This is good to see when `print(function_with_decorator)` is called, then the decorator object gets printed out instead of the function object. + + Note: + This method only works properly if every decorator calls the function normally (function_name()). + + Args: + function (FunctionType): Function which should be extracted. + + Returns: + FunctionType: The extracted function. + + """ + function_types = [] + + try: + for x in function.__closure__: + function_types.append(x.cell_contents) + except TypeError: + return function + + while len(function_types) > 0: + for func in function_types.copy(): + if isinstance(func, _FunctionType): + try: + for x in func.__closure__: + function_types.append(x.cell_contents) + except TypeError: + return func + + +def get_decorators(function: _FunctionType) -> _List[_FunctionType]: + """ + Returns all decorators of a function + + Note: + This method only works properly if every decorator calls the function normally (function_name()). + + Args: + function (FunctionType): Function from which the decorators should be extracted from. + + Returns: + list: A list of the function decorators + + """ + decorators = [] + function_types = [] + try: + for x in function.__closure__: + function_types.append(x.cell_contents) + decorators.append(function) + except TypeError: + return [] + + while len(function_types) > 0: + for func in function_types: + if isinstance(func, _FunctionType): + decorators.append(func) + try: + for z in func.__closure__: + function_types.append(z.cell_contents) + except TypeError: + pass + function_types.remove(func) + + return decorators[:-1] diff --git a/dreamutils/sort.py b/dreamutils/sort.py new file mode 100644 index 0000000..c7f651c --- /dev/null +++ b/dreamutils/sort.py @@ -0,0 +1,162 @@ +#!/usr/bin/python3 + +from typing import List as _List + +""" +This file provides different sorting algorithms + +Todo: + Add `ignore_case` argument to `Sort.string(...)` or `string_ignore_case(...)` method + +Thanks: + https://www.tutorialspoint.com/python_data_structure/python_sorting_algorithms.htm + https://www.educative.io/edpresso/how-to-implement-quicksort-in-python +""" + + +def _copy_or_not(to_sort: list, copy=True) -> list: + if copy: + return to_sort.copy() + else: + return to_sort + + +class Sort: + + @classmethod + def integer(cls, to_sort: _List[int], new_list=True) -> _List[int]: + """ + Sorts a list of integers + + Args: + to_sort: The list of integers to sort + new_list: If True the given list is copied and returned. If False the given list will be updated + + Returns: + The sorted list of integerss + + """ + return cls.object(to_sort, new_list) + + @staticmethod + def object(to_sort: list, new_list=True) -> list: + """ + Sorts a list, regardless of its type + + Args: + to_sort: The list sort + new_list: If True the given list is copied and returned. If False the given list will be updated + + Returns: + The sorted list + + """ + pass + + @classmethod + def string(cls, to_sort: _List[str], new_list=True) -> _List[str]: + """ + Sorts a list of strings + + Args: + to_sort: The list of strings to sort + new_list: If True the given list is copied and returned. If False the given list will be updated + + Returns: + The sorted list of strings + + """ + return cls.object(to_sort, new_list) + + +class BubbleSort(Sort): + """Bubble sort is a comparison-based algorithm in which each pair of adjacent elements is compared and the elements are swapped if they are not in order""" + + @staticmethod + def object(to_sort: list, new_list=True) -> list: + sorted_list = _copy_or_not(to_sort, new_list) + for num in range(len(sorted_list) - 1, 0, -1): + for i in range(num): + if sorted_list[i] > sorted_list[i + 1]: + temp = sorted_list[i] + sorted_list[i] = sorted_list[i + 1] + sorted_list[i + 1] = temp + + return sorted_list + + +class InsertionSort(Sort): + """ + Insertion sort involves finding the right place for a given element in a sorted list. + So in beginning we compare the first two elements and sort them by comparing them. + Then we pick the third element and find its proper position among the previous two sorted elements. + This way we gradually go on adding more elements to the already sorted list by putting them in their proper position + """ + + @staticmethod + def object(to_sort: list, new_list=True) -> list: + sorted_list = _copy_or_not(to_sort, new_list) + for i in range(1, len(sorted_list)): + j = i - 1 + key = sorted_list[i] + + while j >= 0 and key < sorted_list[j]: + sorted_list[j + 1] = sorted_list[j] + j -= 1 + sorted_list[j + 1] = key + + return sorted_list + + +class QuickSort(Sort): + """QuickSort is an in-place sorting algorithm with worst-case time complexity of n^2""" + + @staticmethod + def object(to_sort: list, new_list=True) -> list: + sorted_list = _copy_or_not(to_sort, new_list) + elements = len(sorted_list) + + if elements < 2: + return sorted_list + + pos = 0 + + for i in range(1, elements): + if sorted_list[i] <= sorted_list[0]: + pos += 1 + temp = sorted_list[i] + sorted_list[i] = sorted_list[pos] + sorted_list[pos] = temp + + temp = sorted_list[0] + sorted_list[0] = sorted_list[pos] + sorted_list[pos] = temp + + left = QuickSort.object(sorted_list[0:pos], False) + right = QuickSort.object(sorted_list[pos + 1:elements], False) + + sorted_list = left + [sorted_list[pos]] + right + + return sorted_list + + +class SelectionSort(Sort): + """ + In selection sort we start by finding the minimum value in a given list and move it to a sorted list. + Then we repeat the process for each of the remaining elements in the unsorted list. + The next element entering the sorted list is compared with the existing elements and placed at its correct position. + So at the end all the elements from the unsorted list are sorted + """ + + @staticmethod + def object(to_sort: list, new_list=True) -> list: + sorted_list = _copy_or_not(to_sort, new_list) + for index in range(len(sorted_list)): + min_index = index + for j in range(index + 1, len(sorted_list)): + if sorted_list[min_index] > sorted_list[j]: + min_index = j + + sorted_list[index], sorted_list[min_index] = sorted_list[min_index], sorted_list[index] + + return sorted_list diff --git a/dreamutils/types/__init__.py b/dreamutils/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dreamutils/types/dict.py b/dreamutils/types/dict.py new file mode 100644 index 0000000..cb255f4 --- /dev/null +++ b/dreamutils/types/dict.py @@ -0,0 +1,44 @@ +#!/usr/bin/python3 + + +def index(dictionary: dict, index: int): + """ + Index a dictionary + + Args: + dictionary: Dictionary to index + index: Position of value to index + + Returns: + The indexed value + + Raises: + IndexError: If the dictionary index is out of range + + """ + + try: + return list(dictionary)[index] + except IndexError: + raise IndexError('dict index out of range') # just replacing 'list' with 'dict' in the error message + + +def index_by_value(dictionary: dict, value): + """ + Index a dictionary by a value of it + + Args: + dictionary: Dictionary to index + value: Value to index + + Returns: + The indexed key + + Raises: + IndexError: If the given value is not in the dictionary + + """ + try: + return list(dictionary.keys())[list(dictionary.values()).index(value)] + except ValueError: + raise IndexError('dict value index out of range') diff --git a/dreamutils/types/string.py b/dreamutils/types/string.py new file mode 100644 index 0000000..4ed8226 --- /dev/null +++ b/dreamutils/types/string.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 + +import re as _re +from typing import Union as _Union + +"""This file contains utils for string manipulation""" + + +def remove_brackets(string: str) -> str: + """ + Removes brackets and the content between it from a string + + Args: + string: string from which the brackets should be removed + + Returns: + `string` without brackets and the content between it + + Examples: + >>> print(remove_brackets('This is [not] an example string (yuy)')) + This is an example string + + """ + finished_string = '' + square_brackets = 0 + parentheses = 0 + for brackets in string: + if brackets == '[': + square_brackets += 1 + elif brackets == '(': + parentheses += 1 + elif brackets == ']' and square_brackets > 0: + square_brackets -= 1 + elif brackets == ')' and parentheses > 0: + parentheses -= 1 + elif square_brackets == 0 and parentheses == 0: + finished_string += brackets + + return finished_string + + +def remove_space(string: str, space: _Union[str, int] = ' ', replace_with_single_space=True) -> str: + """ + Removes all white space from `string` which is equal or higher than from the argument `space` given space + + Args: + string: string from which the space should be removed + space: Minimum size of the space to start the removal of white space in `string` + replace_with_single_space: If True the space to remove is replaced with a single space instead of nothing + + Returns: + `string` without the given space and higher + + Examples: + >>> print(remove_space("This string has way to much space")) + This string has way to much space + + """ + if isinstance(space, int): + space = ' ' * space + + space_to_replace = '' + if replace_with_single_space: + space_to_replace = ' ' + + return _re.sub(space + '+', space_to_replace, string) diff --git a/dreamutils/types/xml.py b/dreamutils/types/xml.py new file mode 100644 index 0000000..6a227aa --- /dev/null +++ b/dreamutils/types/xml.py @@ -0,0 +1,254 @@ +#!/usr/bin/python3 + +import xml.etree.ElementTree as _ET +from os.path import isfile as _isfile +from random import randint as _randint +from typing import Dict as _Dict, List as _List, Union as _Union +from xml.dom import minidom as _minidom + +from .dict import index_by_value as _index_by_value +from ..encoding import UTF_8 as _UTF_8 + + +def prettify(xml: _Union[_ET.Element, str], space: _Union[str, int] = ' ') -> str: + """ + Prettifies a xml string or element + + Args: + xml: XML string or `ElementTree.Element` to prettify + space: The space before every new sub element + + Returns: + The prettified string + + Examples: + >>> print(prettify('