Package: notalib
notalibCollection of small Python utility functions and classes. Each one was created because I needed it and it didn't exist or I didn't like the existing implementations. 100% of code is used in real-world projects.(And one day, the documentation is going to be actually good. In the meanwhile, don't hesitate to ask if something's not clear.)InstallpipinstallnotalibOr withpoetry:poetryaddnotalibCompatibility promiseVersion numbers follow the semver rules. Minor releases are backwards-compatible. It should be safe to update, say, from 1.2 to 1.4.Major releases are not backwards-compatible. If you upgrade, say, from 1.4 to 2.0, read the release notes to see what's changed.Maintenance & bugfixesWhile I try to fix bugs, add new features, and review any PRs when I have time, there're no promises and no set timeframes, even if a bug is critical. That's a project I do in my free time, free of charge.If that's not enough for you or you have an urgent request, there are paid maintenance options (bugfixing, features, expedite PR review, 24h security responses). Contact me for prices:m1kc@yandex.ruAlso feel free to just send me money:MasterCard: 5559 4925 7484 0297PayPal:paypal.me/thisism1kcDonations are always appreciated, even if you send 10$.Constants includednotalib.utf.BOM: contains stringb'\xEF\xBB\xBF'(UTF-8 little endian byte order mark).Utils includednotalib.array.as_chunks :fire:Iterates over your array in chunks of at most N elements.fromnotalib.arrayimportas_chunksarr=[1,2,3,4,5]forchunkinas_chunks(arr,2):print(chunk)# [1,2]# [3,4]# [5]notalib.array.ensure_iterable :fire:Keeps iterable things like lists intact, turns single values into single-element lists. Useful for functions that can accept both.ensure_iterable([1,2,3])# --> [1,2,3]ensure_iterable((1,2,3))# --> (1,2,3)ensure_iterable(1)# --> [1]ensure_iterable('smth')# --> ['smth']defmy_function(one_or_multiple_args):forarginensure_iterable(one_or_multiple_args):...my_function(['log','smog'])my_function('dog')notalib.combinator.Combinator :fire:notalib.date.parse_monthnotalib.date.parse_datenotalib.date.normalize_date :fire:Re-formats a date, parsing it as any of theinput_formatsand outputting it asoutput_format.This function uses Arrow date formats. SeeArrow docsfor details.Args:
	s: The source date in one of the input_formats to be converted to target format.
	input_formats: Source date representation formats.
	output_format: The format in which the date will be output.
	allow_empty: if true, `None` input will produce `None` output, otherwise a ValueError will be thrown.

Example:
	>>> normalize_date('12.07.2023', ('D.M.YYYY', 'DD.MM.YYYY'), 'YYYY-MM-DD', False)
	'2023-07-12'

Returns:
	Converted date string from any of the input formats to the specified output format.notalib.date.get_week_numberRemoved in 2.0.0. Useget_weekinstead. If you want the "old" week numbering, use get_week withWeekNumbering.MATCH_YEARand add 1 to week number.notalib.date.get_weekReturns named tuple with week number for the given date. Accepts Python dates and Arrow timestamps.Optional argumentmodetells what to do if the week started in previous year:WeekNumbering.NORMAL (default): consider it the last week of the previous yearWeekNumbering.MATCH_YEAR: consider it 0-th week of current yearfromnotalib.dateimportget_week,WeekNumberingfromdatetimeimportdatedate1,date2=date(2022,12,31),date(2023,1,1)get_week(date1,WeekNumbering.NORMAL)# Week(week=52, year=2022)get_week(date1,WeekNumbering.MATCH_YEAR)# Week(week=52, year=2022)get_week(date2,WeekNumbering.NORMAL)# Week(week=52, year=2022)get_week(date2,WeekNumbering.MATCH_YEAR)# Week(week=0, year=2023)notalib.dict.deep_merge:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Merges two dicts.Modifies input. Usecopy.deepcopyto create a deep copy of the original dictionary if you need it.Accepts three arguments:original dict (also target)second dictoverwrite=True if you want to overwrite matching paths, overwrite=False to raise an exception on path conflicts (default)notalib.dict.find_fieldnotalib.dict.find_valuenotalib.dict.normalize_dict :fire:notalib.dict.filter_dictFilters a dictionary, removing any keys except for the ones you choose.fromnotalib.dictimportfilter_dictsrc={'Some':"BODY",'once':"told me",'the world':"is gonna roll me",}filter_dict(src,["Some","once"])# {'Some': 'BODY', 'once': 'told me'}filter_dict(src,[])# {}notalib.format.format_long_listnotalib.timedelta.convert_timedelta:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Converts standard timedelta object to specified formats.Allowed formats: 's', 'ms'.fromnotalib.timedeltaimportconvert_timedeltafromdatetimeimporttimedeltatd=timedelta(seconds=1,milliseconds=23)convert_timedelta(td,'s')# 1.023convert_timedelta(td,'ms')# 1023notalib.hypertext.strip_tags :fire:notalib.hypertext.TablePrinter :fire:Prints an HTML table, row by row, from the given data, using attrs or dictionary keys as columns.Two ways to use it:Call header() / entry() / footer() manuallyfromnotalib.hypertextimportTablePrintert=TablePrinter(['a','b'])t.header()# '<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody>'t.entry({'a':1,'b':2})# '<tr><td>1</td><td>2</td></tr>\n't.entry({'a':11,'b':22})# '<tr><td>11</td><td>22</td></tr>\n't.footer()# '</tbody></table>'Pass an iterable to iterator_over()fromnotalib.hypertextimportTablePrintert=TablePrinter(['a','b'])list(t.iterator_over([{'a':11,'b':22}]))# ['<table><thead><tr><th>a</th><th>b</th></tr></thead><tbody>',#  '<tr><td>11</td><td>22</td></tr>\n',#  '</tbody></table>']notalib.polosa.polosa :fire: :fire: :fire: :fire: :fire:18023/2000000   294.8/sec   Processing transaction ID#84378473 (2020-01-04)The CLI progress indicator you've always dreamt of: shows current and total if available, measures current speed, can show your comments for each element, makes sure not to slow down your terminal with frequent updates.See this short demo.Cheat sheet## Basic usagewithpolosa()asp:p.tick()# 467344   201.2/sec## Specify total number of elements:withpolosa(total=1337)asp:# 26/1337   1.2/sec## Print something useful about every element:p.tick(caption=my_order.time_created)# 1723910/2000000   319231.2/sec   2020-01-01 15:37:00notalib.range.Rangenotalib.time.Timing :fire:Measures time spent on executing your code. Killer feature: it can be used as a reusable context.timing=Timing()...withtiming:do_something()# That's it, do something with the measurementlog(f'Operation took{timing.result}sec')If you just want to print measurements into console, there's a shorthand:timing=Timing(auto_print=True)...withtiming:do_something()notalib.trendsetter.Trendsetter :fire:notalib.git.get_current_commit:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Returns short description and hash of last commit of current branch.
If function is called outside git repo or there are no commits in the history,Nonewill be returned.fromnotalib.gitimportget_current_commitcommit=get_current_commit()# Commit(hash='db0e5c1de83f233abef823fd92490727f4ee9d50', short_description='Add timedelta module with convert_timedelta function')notalib.git.get_last_tag:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Returns last tag label and hash. If function is called outside git repo or there are no tags in the history,Nonewill be returned.fromnotalib.gitimportget_last_tagtag=get_last_tag()# Tag(hash='c4b6e06f57ab4773e2881d286804d4e3141b5195', label='v1.4.0')Tools for Pandasnotalib.pandas.pandasplus.row_to_dictnotalib.pandas.pandasplus.replace_null_objectsReplaces all types of null values in a DataFrame with the given value.df=pd.DataFrame({'A':[pd.NA,pd.NaT,'SomeVal',None]})new_df=replace_null_objects(df,"Hello, notalib!")new_df#                  A# 0  Hello, notalib!# 1  Hello, notalib!# 2          SomeVal# 3  Hello, notalib!Tools for Djangonotalib.django.auth.StaticBackendnotalib.django.auth.SettingsBackendnotalib.django.xauthEndpoints for easier authentication in APIs. Requires Django REST framework.Provides endpoints:/xauth/check— returns code 200 if client is authenticated (or global permissions are set to AllowAny), 403 if not/xauth/auth-post— authenticates a client; accepts two POST parametersusernameandpassword; returns code 200 on success and 403 on failureHow to use:Make sure Django REST framework is installed.Add'notalib.django_xauth'to INSTALLED_APPS.Runmanage.py migrate django_xauth(doesn't actually change your DB).Add something like this to your urls.py:path('xauth/', include('notalib.django_xauth.urls')),notalib.django.colorlog.ColorFormatternotalib.django.filterset :fire:notalib.django.formplus.MonthFieldnotalib.django.formplus.ChoiceWithDefaultnotalib.django.formplus.IntegerArrayFieldnotalib.django.formplus.StringArrayFieldnotalib.django.formplus.MonthArrayFieldnotalib.django.http.HttpResponseSeeOtherSpec-compliant HTTP 303 See Other redirect (Django only provides deprecated 301 and 302).notalib.django.http.HttpResponseTemporaryRedirectSpec-compliant HTTP 307 Temporary Redirect (Django only provides deprecated 301 and 302).notalib.django.request_time_middleware.RequestTimeLoggingMiddlewarenotalib.django.stream.stream_jsonStream all elements of iterable object as JSON array using the StreamingHttpResponse class. Unlike DRF's Response class, it can handle arrays of any size.classSomeViewSet(...):...deflist(self,request,*args,**kwargs):...returnstream_json(data)Django/ClickhouseDeprecated since 2.2.0.Click to expandRequired packages:clickhouse-sqlalchemyRequired two django.settings variables:CLICKHOUSE_URL— database URLCLICKHOUSE_PROFILE— dump all queries and their timing to console, true/falsenotalib.django.clickhouse.base.get_connectionnotalib.django.clickhouse.base.get_database_namenotalib.django.clickhouse.base.QueryA wrapper for SQLAlchemy'sselectwith some useful postprocessing options.execute— no postprocessingexecute_val— returns single valueexecute_list— returns single column as listexecute_kv— returns dict, first column becomes keys, second column becomes valuesexecute_na— returns number of affected rowsUsage example:q=Query(select([SomeTable.c.notalib]))q.execute_list()# ["Example", "OOOOO", "my", "defence", ...]notalib.django.clickhouse.mutations.get_mutations_in_progress_countReturns the number of mutations in progress for the specified table.get_mutations_in_progress_count("SOME_DATABASE","SOME_TABLE_IN_DATABASE")# 5notalib.django.clickhouse.wait.wait_result :fire:Waits until all mutations for the given table are complete.# blah blah blah, ALTER TABLE ... UPDATE ...wait_result("SOME_DATABASE","SOME_TABLE_IN_DATABASE")# UPDATE complete, continueTools for Tablib:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Required packages:tablibnotalib.tablib.shortcuts.load_dataset:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Allows to simplify the loading of datasets, even custom ones.fromnotalib.tablib.shortcutsimportload_datasetwithopen("report.xlsx",mode='rb')asfile:ds=load_dataset(file)### Or you can use custom Dataset class and formatfromtablibimportDatasetclassMyDataset(Dataset):passwithopen("report.csv",mode='rb')asfile:ds=load_dataset(file,'csv',MyDataset)notalib.tablib.dataset.ExtendedDataset:warning: Experimental API. Subject to change. Don't use in production. You've been warned.Extended tablib.Dataset class, which adds useful data processing methods.notalib.tablib.dataset.ExtendedDataset.drop_duplicatesRemoves all duplicate rows from theExtendedDatasetobject while maintaining the original order.notalib.tablib.dataset.ExtendedDataset.drop_emptyRemoves rows with empty data in specified columns.notalib.tablib.dataset.ExtendedDataset.drop_empty_rowsRemoves rows in which all values are empty.notalib.tablib.dataset.ExtendedDataset.apply_to_columnApplies the function to the values in the specified column.notalib.tablib.dataset.ExtendedDataset.replace_empty_objectsReplaces empty values with a new one.notalib.tablib.dataset.ExtendedDataset.set_used_columnsReturns a new dataset based on the specified header labels.notalib.tablib.dataset.ExtendedDataset.get_headers_mapReturns a list of Boolean objects based on a given set of header labels.notalib.tablib.dataset.ExtendedDataset.get_header_indexCalculates the index of the header by its label.notalib.tablib.dataset.ExtendedDataset.groupbySets tags to rows and returns list of groups for filtering.notalib.tablib.dataset.ExtendedDataset.rename_headersRenames header labels.