Author: Kale (kryogenic) Date: Thu Oct 10 11:16 AM 2024 +0000
- privex.helpers.cache
- Replaced
aiorediswith redis 4.2.0 (breaks Python<3.6)
- Replaced
Author: Chris (Someguy123) Date: Mon Nov 2 03:26 AM 2020 +0000
-
privex.helpers.settings
-
Added
DEFAULT_CACHE_ADAPTERwhich can be adjusted via the env varPRIVEX_CACHE_ADAPTER. This setting allows overriding the cache adapter which is used automatically by default viacached/async_cached, along with the global adapter when you callget_adapter -
Added
DEFAULT_ASYNC_CACHE_ADAPTERwhich can be adjusted via the env varPRIVEX_ASYNC_CACHE_ADAPTER. It defaults to the same value asDEFAULT_CACHE_ADAPTER, since thanks to the newimport_adapterfunction andADAPTER_MAPdictionary inprivex.helpers.cache, it's now possible to reference cache adapters by simple names such asmemcached,redis,sqlite3,memoryetc. - and these aliases point to either the synchronous or asyncio version of their related adapter depending on which context they're being passed into.
-
-
privex.helpers.cache
-
Added
MemcachedCachemodule, which contains the synchronous cache adapterMemcachedCache. This is simply a synchronous version ofAsyncMemcachedCachethat usespylibmcinstead ofaiomcache. -
Added
ADAPTER_MAPdictionary, which maps aliases such asmemcached,redis,sqlite3,memoryetc. to their respective synchronous and asyncio adapter module paths, which can be loaded using the newly addedimport_adapterfunction, or simply usingadapter_set/async_adapter_set. -
Added
import_adapterfunction, which looks up an adapter alias name such asredis/memcached, maps it to the fully qualified module path viaADAPTER_MAP, and then loads the module + extracts the class from the module. -
Adjusted
adapter_set,async_adapter_set, along withCacheWrapper+AsyncCacheWrapperso that they now use the default string cache adapter defined insettings, and can transparently handle string adapter values by passing them off toimport_adapter. -
Added new
extrasmodule-
CacheManagerMixinis a class mixin which adds various methods and settings to assist with class-scoped caching, including an adjustable class-level cache prefix.cache_prefix, a method to remove all known cache keys managed by your class.clear_all_cache_keys, and a special decorator which automatically integrates with your class.z_cacheby intercepting the first argument of a method call. -
z_cache- A special method caching decorator which is designed to integrate with classes that extend.CacheManagerMixinThis is simply a wrapper for
.r_cache- it transparently caches the output of a wrapped method/function, but unlike.r_cache, it's designed to automatically integrate with classes which extend.CacheManagerMixin, allowing it to automatically retrieve cache settings such as the cache prefix, default cache time, along with directly calling various classmethods which enable logging of newly createdcache_key's for painless cleanup of cache keys when needed, without having to manually track them, or doing the nuclear option of erasing the entire cache system's database.
-
-
-
privex.helpers.common
- Added new small helper function
auto_list, which is a small but useful function that simplifies the conversion of objects into lists, sets, tuples etc. with the option to manually force a certain conversion method, eitherlist wrappingorlist iteration
- Added new small helper function
-
privex.helpers.plugin
- Added various functions for managing memcached instances via the
pylibmclibraryconnect_memcached- create a new memcachedClientobjectget_memcached- get or create a MemcachedClientobject shared by your threadclose_memcached- close the MemcachedClientconnection and delete it from the threadstorereset_memcached- close the sharedClientthen re-create it again.configure_memcached- configure Memcached settings then automatically reset the sharedClientinstance.
- Added new
HAS_MEMCACHEDboolean value, to track whether synchronous memcached viapylibmcis available
- Added various functions for managing memcached instances via the
-
General stuff
- Added
pylibmcto extras/cache.txt - Added some missing packages to the
Pipfile- and synchronisedPipfile.lock - Added
.envto.gitignore - Created unit tests for the new
CacheManagerMixinclass andz_cachedecorator - Created unit tests for the new
MemcachedCachecache adapter - Fixed the
livecommand indocs/Makefilefor newersphinx-autobuild - Added
SqliteCacheandMemcachedCacheto the docs - Added
privex.helpers.cache.extrasandprivex.helpers.cache.post_depto the docs - Added some other missing things to the docs
- Created and updated a lot of stub files in
privex_stubs(assists IDEs like PyCharm with type hinting and function/method previews) - Possibly other various additions / fixes / improvements that I forgot to list.
- Added
Author: Chris (Someguy123) Date: Wed Oct 7 11:36 2020 +0000
-
privex.helpers.cache
-
Added
SqliteCachemodule, containing the synchronous cache adapterSqliteCachewhich uses an Sqlite3 database for persistent cache storage without the need for any extra system service (unlike the memcached / redis adapters) -
Added
asyncx.AsyncSqliteCachemodule, containing the AsyncIO cache adapterAsyncSqliteCache, which is simply an AsyncIO version ofSqliteCacheusing theaiosqlitelibrary.- NOTE: Due to the file-based nature of SQLite3, combined with the fact write operations generally result in the database being locked until the write is completed - use of the AsyncIO SQLite3 cache adapter only slightly improves performance, due to the blocking single-user nature of SQLite3.
-
Added
post_depsmodule, short for post-init dependencies. This module contains functions and classes which are known to have (or have a high risk of) recursive import conflicts - e.g. the new SQLite caching uses theprivex-dbpackage, and theprivex-dbpackage imports various things fromprivex.helperscausing a recursive import issue if we loadprivex.dbwithin a class that's auto-loaded in an__init__.pyfile.The nature of this module means that none of it's contents are auto-loaded / aliased using
__init__.pymodule constructor files. This shouldn't be a problem for most people though, as the functions/classes etc. within the module are primarily only useful for certain cache adapter classes, rather than intended for use by the users ofprivex-helpers(though there's nothing stopping you from importing things frompost_depsin your own project).sqlite_cache_set_dbfolderandsqlite_cache_set_dbnameare two module level functions that are intended for use by users. These functions allow you to quickly override theDEFAULT_DB_FOLDERand/orDEFAULT_DB_NAMEdynamically for both SqliteCacheManager and AsyncSqliteCacheManager.SqliteCacheResultis a namedtuple that represents a row returned when querying thepvcachetable within an SQLite cache database_SQManagerBaseis a mix-in class used by bothSqliteCacheManagerandAsyncSqliteCacheManager, containing code which is used by both classes.SqliteCacheManageris a child class ofSqliteWrapper, designed to provide easier interaction with an SQLite3 cache database, including automatic creation of the database file, and thepvcachetable within it. This class is intended for use byprivex.helpers.cache.SqliteCacheAsyncSqliteCacheManageris a child class ofSqliteAsyncWrapper, and is simply an AsyncIO version ofSqliteCacheManager. This class is intended for use byprivex.helpers.cache.asyncx.AsyncSqliteCache
-
-
privex.helpers.plugins
- Added
HAS_PRIVEX_DBattribute, for tracking whether theprivex-dblibrary is available for use. - Added
clean_threadstoreto__all__- seems I forgot to add it previously.
- Added
-
privex.helpers.settings
- Added
SQLITE_APP_DB_NAMEwhich can also be controlled via an env var of the same name - allowing you to adjust the base of the default DB filename for the SQLite3 cache adapters. - Added
SQLITE_APP_DB_FOLDER(can also be controlled via env) - similar to the DB_NAME attribute, controls the default base folder used by the SQLite3 cache adapters.
- Added
3.0.0 - Overhauled net module, new object cleaner for easier serialisation, improved class generation/mocking + more
Author: Chris (Someguy123) Date: Sat Sep 26 04:29 2020 +0000
-
privex.helpers.common- Added
strip_null- very simple helper function to strip both\00and white space from a string - with 2 cycles for good measure.
- Added
-
privex.helpers.types- Added
AUTO/AUTOMATIC/AUTO_DETECTEDdummy type, for use as the default value of function/method parameters, signalling to users that a parameter is auto-populated from another data source (e.g. instance/class attribute) if not specified.
- Added
-
privex.helpers.collections- Added
copy_class_simple(alternative tocopy_class) - Added
copy_funcfor copying functions, methods, and classmethods - Improved
_q_copyto handle copying functions, methods and classmethods - Added
generate_class+generate_class_kw - Added
Mocker.make_mock_module - Added
Mocker.add_mock_modules - Added
Mocker.__dir__to track the available mock attributes and modules - Added
dataclasses_mock- aMockerinstance which emulatesdataclassesas a drop-in partially functional dummy for Python 3.6 when thedataclassesbackport package isn't installed. - Various changes to
Mocker.make_mock_class- potentially breaking, see the BREAKING CHANGES section. - Added
DictObject.__dir__+OrderedDictObject.__dir__to enable proper tracking of dictionary keys as attributes
- Added
-
privex.helpers.net-
This module has now been converted into a folder-based module. Imports in
__init__.pyhave been carefully setup to ensure that existing import statements should still work as normal -
Added new
SocketWrapperandAsyncSocketWrapperclasses, which are powerful wrapper classes for working with Pythonsocket.socketobjects, including support for SSL/TLS, partial support for running socket servers, and
making basic HTTP requests -
Many, many new functions and classes! There's too many to list, and due to the conversion into a module folder instead of a singular file, it's difficult to track which functions/classes are new, and which existed before.
If you really want to know what's new, just take a look around the
privex/helpers/netmodule.
-
-
privex.helpers.converters- Added
clean_obj- which is a function that recursively "cleans" any arbitrary object, as to make it safe to convert into JSON and other common serialisation formats. It supportsdict's,list's, attrs objects, native Pythondataclass's,Decimal, and many other types of objects. - Added
clean_dict(used byclean_obj, usually no need to call it directly) - Added
clean_list(used byclean_obj, usually no need to call it directly)
- Added
-
Added
privex.helpers.mockersmodule, which contains pre-madeMockerobjects that are designed to stand-in for certain libraries / classes as partially functional dummies, if the real module(s) are unavailable for whatever reason. -
And probably some other small additions / changes
BREAKING CHANGES
-
Both
_copy_class_dictand_copy_class_slottednow check each attribute name against a blacklist (default:COPY_CLASS_BLACKLIST), and the default blacklist contains__dict__,__slots__and__weakref__, as the first 2 can't be directly copied (but we copy their contents by iteration), and weakref simply can't be deep copied (and it probably isn't a good idea to copy it anyway). -
_copy_class_dict(used bycopy_class) no longer breaks the attribute copy loop ifdeep_copy=False -
Mocker.make_mock_classnow returns a clonedMockerclass or instance by default, instead of a barebones class / instance of a barebones class.This was done simply because a Mocker class/instance is designed to handle being instantiated with any combination of constructor arguments, and have arbitrary attributes be retrieved / methods called without raising errors.
If you absolutely require a plain, simple, empty class to be generated, you may pass the parameter
simple=Trueto generate a bare class instead of a clone of Mocker (similar to the old behaviour). Unlike the old version of this method, you can now specify attributes as a dictionary to make your barebones mock class act similar to the class it's mocking. -
Many things in
privex.helpers.netsuch ascheck_host/check_host_asynchave been improved in various ways, however there may be some breaking changes with certainprivex.helpers.netfunctions/classes in certain usecases.-
Due to the high risk of bugs with certain networking functions that have been completely revamped, the older, simpler versions of various networking functions are available under
privex.helpers.net.basewith their original names.Because of the naming conflicts, to use the legacy functions/classes from
base, you must import them directly fromprivex.helpers.net.baselike so:# Option 1: import the base module itself, with an alias to prevent naming conflicts (and make it more # clear what you're referencing) from privex.helpers.net import base as netbase if netbase.check_host('google.com', 80): print('google.com is up') # Option 2: import the required legacy functions directly (optionally, you can alias them as needed) # You could also alias the newer overhauled functions while testing them in small portions # of your application. from privex.helpers.net.base import check_host from privex.helpers.net import check_host as new_check_host if check_host('www.privex.io', 443, http_test=True, use_ssl=True): print('[old check_host] https://www.privex.io is up') if new_check_host('files.privex.io', 443, http_test=True, use_ssl=True): print('[new check_host] https://files.privex.io is up')
-
Author: Chris (Someguy123) Date: Tue 17 Dec 2019
Tl;Dr; important changes
- Added asyncx functions:
loop_run,is_async_context,_awaitable_blacklisted - Added asyncx decorator
awaitable_class, and mixin classAwaitableMixin(mixin version of awaitable_class decorator) - Removed class
helpers.extras.git.Gitand replaced it with an alias toAsyncGitas@awaitable_classrendered the wrapper class obsolete - Refactored some types from
commonmodule intotypes - Refactored tests and added some new tests for some of the added functions/classes
- Various refactoring and general cleanup
New Features / Additions
-
asyncx.loop_runis similar toasyncx.run_sync, but more flexible, and doesn't use the deprecatedasyncio.coroutinefunction. It can be passed either a coroutine directly for execution, or if a function/method is passed, (doesn't have to be async) then it can attempt to extract the coroutine by calling each layer until a coroutine or non-callable is found.While there's no plan to remove
asynx.run_syncin the near future, it's strongly recommended to switch usages ofrun_synctoloop_runbecause it's better at handling different forms of awaitable objects:-
Unlike
run_sync, loop_run can handle both async function references AND coroutines -
Unlike
run_sync, loop_run can unwrap coroutines / async function references that are wrapped in a normal non-async function (e.g.@awaitablewrapper functions) -
Unlike
run_sync, loop_run can accept the optional_loopkeyword argument, allowing you to specify a custom asyncio event loop if you desire. -
Unlike
run_sync, loop_run will cleanly execute non-async functions if they're encountered, and simply return non-callable objects that were passed to it, instead of failing.
-
-
New function / class decorator
asyncx.awaitable_class- wraps a class and overrides__getattribute__to enable all async methods to be called from non-async code. Similar toasyncx.awaitable, but affects all async methods in a class, and doesn't require non-async wrapper functions to be written.-
If a non-async method or standard attribute is requested, then those are returned as normal without modification.
-
If an async method is called on the class, then it checks to see if there's a current AsyncIO context - if there is, it simply returns the coroutine for
await'ing -
If there isn't an async context, it will use
loop_runto call the method synchronously using the current event loop, and return the method's result synchronously.
-
-
New class
asyncx.AwaitableMixin- a mixin class which works the same asasyncx.awaitable_class, but as a mixin class add to your class's inheritance, instead of a decorator. -
Created the file
CHANGELOG.md- note that it doesn't contain a full changelog yet, it only goes back as far as version 2.5
Changes / Updates
-
The wrapper class
helpers.extras.git.Githas been removed, asAsyncGitnow uses the much simpler@awaitable_classdecorator to enable synchronous usage of all async methods, instead of a sub-class with individually@awaitablewrapped methods.To avoid breaking any existing code which relied on
extras.git.Git,Gitis now an alias forAsyncGit. No changes needed to be made to the Git tests intests/test_extras.py, so this change isn't believed to cause code breakage. -
A large portion of the decorator
asyncx.awaitablehas been refactored into the smaller functions:_awaitable_blacklistedandis_async_context. -
During the refactoring of
asyncx.awaitable, a bug was discovered in the blacklist scanning for sub-modules - this is now fixed (note: blacklist scanning is refactored into_awaitable_blacklisted) -
asyncx.pynow has an__all__module attribute, allowing__init__to simply import*instead of having to list each class/function etc. -
cache.asyncx.__init__-
Added a PyDoc comment at the start of the file, explaining the AsyncIO adapters and how to use them.
-
Added the attributes
HAS_ASYNC_REDIS,HAS_ASYNC_MEMORY, andHAS_ASYNC_MEMCACHEDto allow for easy availability checking of async cache adapters. -
Lowered the
ImportErrorlog level for AsyncRedisCache and AsyncMemcachedCache fromlog.exceptiondown tolog.debug
-
-
Refactored various generic / template types (e.g.
T,K,CL) fromhelpers.commonintohelpers.types
Testing
-
Refactored
tests/test_general.pyinto a foldertests/general/ -
Refactored AsyncIO related tests from
tests/test_general.pyintotests/asyncx/test_async_common.py -
Added several new AsyncIO tests to
tests/asyncx/test_async_common.py, mainly aimed at the newasyncx.awaitable_class, andasyncx.AwaitableMixin
Author: Chris (Someguy123) Date: Fri Dec 13 09:16:57 2019 +0000
New Features / Additions
-
privex.helpers.common- Added
extract_settingsfor extracting prefixed settings from modules, classes or dict's.
- Added
-
Created new
helpers.black_magicmodule for somewhat risky code that uses app introspectioncalling_function- Returns the name of the function which called your function/method.calling_module- Returns the name of the module which called your function/methodcaller_name- Get the fully qualified name of a caller in the formatsome.module.SomeClass.method
-
Created new
helpers.typesmodule for holding type aliases and new type definitions -
privex.helpers.decorators- Added
async_retrydecorator, which works similar toretry_on_error, but supports wrapping asyncio coroutines
- Added
-
privex.helpers.cache- Created new
asyncxsubmodule for AsyncIO cache adapters asyncx.base.AsyncCacheAdapteris a new base class for AsyncIO cache adapters, with all methods as corosasyncx.AsyncRedisCacheis a new AsyncIO cache adapter for Redisasyncx.AsyncMemoryCacheis a new AsyncIO cache adapter for in-memory caching (async version ofMemoryCache)asyncx.AsyncMemcachedCacheis a new AsyncIO cache adapter for MemcachedCacheAdapterhas a new methodget_or_set_async, which is an async method that supports coroutines as a value, as well as standard callable's and plain values
- Created new
-
privex.helpers.plugin- New functions for organising __STORE by thread:
_get_threadstore,_set_threadstore,clean_threadstore - New functions for managing AsyncIO Redis (aioredis) instances
get_redis_async,close_redis_asyncetc. - New functions for managing AsyncIO Memcached (aiomcache) instances
get_memcached_async,close_memcached_asyncetc.
- New functions for organising __STORE by thread:
Changes / Updates
-
Added
aioredis,hiredis, andaiomcachetoextras/cache.txt -
async-propertyis now a core requirement, since it's used by a lot of async classes -
New settings
MEMCACHED_HOSTandMEMCACHED_PORTfor AsyncMemcachedCache -
New plugin status
HAS_ASYNC_REDISfor detecting ifaioredisis available -
privex.helpers.decoratorsretry_on_errhas been slightly cleaned upretry_on_errnow supports ignoring exceptions, so you can list exceptions that cause a retry, but shouldn't increase the retry count.retry_on_errnow supports the settinginstance_match, which changes how exceptions are compared. When enabled, it will compare usingisinstance()instead of an exact type comparison.
-
privex.helpers.asyncxawaitabledecorator now detects when it's received a non-async function, and returns the result correctlyawaitablenow supports "blacklisting" functions/modules, to ensure when those functions/modules call an@awaitablefunction, that they always get a synchronous result, not a coroutine.
-
privex.helpers.cacheCacheWrappernow uses@awaitablefor most methods, allowing AsyncIO cache adapters to be used without breaking existing synchronous code which uses the cache API.CacheAdapternow has dummy__enter__and__exit__methods defined, allowing all synchronous cache adapters to be used in awithstatement, regardless of whether they actually use context management.
-
privex.helpers.pluginget_redis,close_redis,reset_redisetc. now use the new thread store system to help ensure thread safety by separating instances per thread.- Refactored
get_redis's connection opening intoconnect_redis, and now usesextract_settingsfor loading default settings
Testing
-
Added unit tests for
extract_settingstotests/test_general.py -
New folders
tests/asyncxandtests/cachefor containing flat test case modules using pytest-asyncio -
tests/asyncx/test_async_retry.pytests the new@async_retrydecorator -
tests/cache/test_async_memcached.pytests the newAsyncMemcachedCacheclass -
tests/cache/test_async_redis.pytests the newAsyncRedisCacheclass -
tests/cache/test_async_memory.pytests the newAsyncMemoryCacheclass
Additional merged commits:
- enable memcached on travis
Author: Chris (Someguy123) Date: Sat Dec 7 05:57:20 2019 +0000
- Created
helpers.extras.gitmodule, which contains a git command wrapper that works with both sync and async functions- Primarily intended for the three methods:
get_current_commit,get_current_branch, andget_current_tag, which allows python applications and libraries to identify what version they are, via git. - Includes various basic methods such as
init,checkout,branch,tag,status,logand others.
- Primarily intended for the three methods:
- Added new async helpers
aobjectallows sub-classes to haveasync __init__constructorsawaitablehelps create wrapper functions that allow async functions to work with sync code seamlessly
- Improved
byteifyandstringifywith None handling - Added new
SysCallErrorexception sniffiois now a required dependency - however it's very small and dependency free in itself (only about 30kb).- Added unit tests for the git module, including tests for both synchronous and asynchronous execution of the methods
- Re-generated some of the documentation
- Possibly other small changes
Author: Chris (Someguy123) Date: Sat Dec 7 00:06:30 2019 +0000
-
Added
call_sysfunction inhelpers.common, an easier way to run and interact with external programs -
Added
shell_quotefunction - small shim for python 3.8'sshlex.joinfor backwards compatibility -
Added
call_sys_asyncfunction inhelpers.asyncx, which is a fully async version ofcall_sys -
Added tests for
call_sysandcall_sys_async -
Improvements to
local_tests.sh-
Re-factored most of
local_tests.shintolib/lib_test.sh. lib_test.sh contains only shell functions, so it can be sourced into a bash shell for debugging local_tests.sh -
Now has a
QUIEToption, for less verbose output -
Re-factored virtualenv creation, detection and activation into
pyactivate,venv_status,is_current_venvand others -
Handle script running from within an existing virtualenv cleanly.
-
Main body of the venv creation / test running has been moved into
main_tests()in lib_test -
Now shows a summary at the end of the tests, so you can see which python versions caused tests to throw warnings and/or were skipped
-
Lots of other small improvements
-
-
Added gitignore lines for
dev_*.py, sqlite databases, and adjustedvenvlines to only affect root folder.
Author: Chris (Someguy123) Date: Thu Dec 5 02:53:08 2019 +0000
- Includes commit 12da829ff8da8b64549208ac92bb48c62f0af60c which enables 2.5.0 to function fully on Python 3.6 and 3.7 (prior to this commit some tests only worked on 3.8+)
commit 12da829ff8da8b64549208ac92bb48c62f0af60c
Date: Thu Dec 5 02:39:32 2019 +0000
Add local_tests.sh, fix get_function_params on older python
- Added `local_tests.sh` for running the unit tests on multiple python versions locally
- Added `OrderedDictObject` to collections module, since python versions before 3.8 cannot reverse a normal dict.
- Add unit tests for ordered dict object
- Adjusted `get_function_params` to use the new OrderedDictObject (fixes failing tests on older python versions)
New Features / Additions
-
privex.helpers.common-
Added
get_function_params- which extracts and filters a function/method or class constructor's parameters, and outputs them in a dictionary -
Added
construct_dict, which allows you to either construct a class, or call a function using a dictionary of keyword arguments, usingget_function_paramsto detect what arguments the class/function/method can take, including any parent classes, then filtering out any keyword arguments which would otherwise be rejected and cause a TypeError. -
Added
_filter_params, a private function used by the aforementioned functions to filter a dictionary or iterable of Parameter objects.
-
-
New module
converters, containing functions/classes designed to convert/parse one type into another-
convert_datetimeconverts both string date/time's as well as unix timestamps intodatetime.datetimeobjects usingdateutil.parser -
convert_unixtime_datetimeconverts specifically UNIX epoch timestamps (can be string, int, float, Decimal etc.) intodatetime.datetimeobjects, and is used byconvert_datetimeto handle unix timestamps. -
convert_bool_intconverts booleansTrue/Falseas well as string / int versions into integers 1 (true) and 0 (false) -
convert_int_boolis mostly an alias tois_true, but exists for convenience and semantics (if there's a convert_bool_int, why not a convert_int_bool?)
Changes / Updates
-
Shrank the rather large copyright notice in most modules down to the small copyright block, and instead of dumping the whole X11 / MIT License text in there, the licence block simply states
License: X11 / MIT. This should make the docs a bit more readable. -
Added
python-dateutilto thePipfile -
For sanity reasons,
python-dateutilhas been added to theinstall_requires(meaning it's auto-installed when you install privex-helpers). The package is relatively small and depends on justsix, weighing in around 500kb (python-dateutil = 468kb, six = 36kb).It may be removed and refactored into a setup.py extra at a later point, but for now it's small and commonly required enough that it can be a dependency.
-
Added
dateutilto the sphinx intersphinx mapping -
Possibly other small changes I forgot to include
Testing
-
Added new test case
TestInspectFunctionsto test_general, which tests the newget_function_paramsandconstruct_dictfunctions. -
Added new test module
test_converters.pywhich contains test cases for the new converters module-
TestConvertDatecovers date/time related converters such asconvert_datetimeandconvert_unixtime_datetime -
TestConvertGeneralcovers other converters that don't fit into a specific category (or would otherwise be pointless to categorize)
-
-