pathpilot 0.3.3__tar.gz → 0.3.4__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.
- {pathpilot-0.3.3 → pathpilot-0.3.4}/PKG-INFO +3 -3
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/__init__.py +1 -1
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_excel.py +161 -130
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_sqlite.py +25 -12
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_zip.py +36 -21
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/base.py +5 -3
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_folder/backup.py +2 -2
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_folder/base.py +59 -40
- pathpilot-0.3.4/pathpilot/exceptions.py +7 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/utils.py +2 -1
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pyproject.toml +3 -3
- pathpilot-0.3.3/pathpilot/exceptions.py +0 -6
- {pathpilot-0.3.3 → pathpilot-0.3.4}/LICENSE +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/README.md +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/__init__.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_csv.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_pickle.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/_text.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_file/utils.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_folder/__init__.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/_folder/utils.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/core.py +0 -0
- {pathpilot-0.3.3 → pathpilot-0.3.4}/pathpilot/decorators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pathpilot
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Library that facilitates file and folder manipulation in Python.
|
|
5
5
|
Home-page: https://github.com/zteinck/pathpilot
|
|
6
6
|
License: MIT
|
|
@@ -16,9 +16,9 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Requires-Dist: XlsxWriter
|
|
18
18
|
Requires-Dist: cachegrab (>=0.2.2)
|
|
19
|
-
Requires-Dist: clockwork (>=0.
|
|
19
|
+
Requires-Dist: clockwork (>=0.4.0)
|
|
20
20
|
Requires-Dist: numpy
|
|
21
|
-
Requires-Dist: oddments (>=0.
|
|
21
|
+
Requires-Dist: oddments (>=0.4.0)
|
|
22
22
|
Requires-Dist: pandas
|
|
23
23
|
Project-URL: Repository, https://github.com/zteinck/pathpilot
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
@@ -30,22 +30,22 @@ class ExcelFile(FileBase):
|
|
|
30
30
|
active_sheet : xlsxwriter.worksheet.Worksheet
|
|
31
31
|
The active worksheet
|
|
32
32
|
sheet_cache : dict
|
|
33
|
-
Dictionary where the keys are worksheet names and values are the
|
|
34
|
-
objects. It is important to note that the keys are the
|
|
35
|
-
passed by the user as opposed to the actual name
|
|
36
|
-
file and worksheet.name attribute because the
|
|
37
|
-
character limit and/or will have a page
|
|
38
|
-
argument. This setup allows the user
|
|
39
|
-
name.
|
|
33
|
+
Dictionary where the keys are worksheet names and values are the
|
|
34
|
+
worksheet objects. It is important to note that the keys are the
|
|
35
|
+
original sheet names passed by the user as opposed to the actual name
|
|
36
|
+
that appears in the excel file and worksheet.name attribute because the
|
|
37
|
+
latter is truncated by the 31 character limit and/or will have a page
|
|
38
|
+
prefix added per the 'number_tabs' argument. This setup allows the user
|
|
39
|
+
to access the sheet using the original name.
|
|
40
40
|
formats : dict
|
|
41
|
-
|
|
42
|
-
containing format parameters. For Example, {{'bold':
|
|
43
|
-
are intended to be frequently used formats that
|
|
44
|
-
and as building blocks for more complex
|
|
45
|
-
information.)
|
|
41
|
+
Keys are strings representing names of formats and values are
|
|
42
|
+
dictionaries containing format parameters. For Example, {{'bold':
|
|
43
|
+
{'bold': True}}. These are intended to be frequently used formats that
|
|
44
|
+
can be both used individually and as building blocks for more complex
|
|
45
|
+
formats (see self.add_format for more information.)
|
|
46
46
|
format_cache : dict
|
|
47
|
-
keys are hashes of sorted dictionaries containing format parameters
|
|
48
|
-
values are xlsxwriter.format.Format. This is done so that only one
|
|
47
|
+
keys are hashes of sorted dictionaries containing format parameters
|
|
48
|
+
and values are xlsxwriter.format.Format. This is done so that only one
|
|
49
49
|
format instance will be created per unique format used.
|
|
50
50
|
number_tabs : bool
|
|
51
51
|
if True, tabs in the workbook will have a number prefix. For example,
|
|
@@ -53,7 +53,8 @@ class ExcelFile(FileBase):
|
|
|
53
53
|
page : int
|
|
54
54
|
keeps track of the current tab number
|
|
55
55
|
verbose : bool
|
|
56
|
-
if True, information is printed when a worksheet is created or written
|
|
56
|
+
if True, information is printed when a worksheet is created or written
|
|
57
|
+
to.
|
|
57
58
|
|
|
58
59
|
Note:
|
|
59
60
|
--------------------
|
|
@@ -299,9 +300,18 @@ class ExcelFile(FileBase):
|
|
|
299
300
|
'header': {'bold': True,'bottom': True, 'text_wrap': True},
|
|
300
301
|
|
|
301
302
|
# Conditional
|
|
302
|
-
'conditional_red': {
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
'conditional_red': {
|
|
304
|
+
'bg_color': '#FFC7CE',
|
|
305
|
+
'font_color': '#9C0006'
|
|
306
|
+
},
|
|
307
|
+
'conditional_green': {
|
|
308
|
+
'bg_color': '#C6EFCE',
|
|
309
|
+
'font_color': '#006100'
|
|
310
|
+
},
|
|
311
|
+
'conditional_yellow': {
|
|
312
|
+
'bg_color': '#FFEB9C',
|
|
313
|
+
'font_color': '#9C6500'
|
|
314
|
+
},
|
|
305
315
|
}
|
|
306
316
|
|
|
307
317
|
return format_presets
|
|
@@ -316,8 +326,8 @@ class ExcelFile(FileBase):
|
|
|
316
326
|
Description
|
|
317
327
|
------------
|
|
318
328
|
Called when an attribute is referenced that does not exist. In this
|
|
319
|
-
case, attributes that do not exist will be assumed to be active
|
|
320
|
-
object attributes (e.g. '.hide_gridlines').
|
|
329
|
+
case, attributes that do not exist will be assumed to be active
|
|
330
|
+
worksheet object attributes (e.g. '.hide_gridlines').
|
|
321
331
|
|
|
322
332
|
Parameters
|
|
323
333
|
------------
|
|
@@ -387,11 +397,12 @@ class ExcelFile(FileBase):
|
|
|
387
397
|
Parameters
|
|
388
398
|
------------
|
|
389
399
|
args : pd.DataFrame | list | dict
|
|
390
|
-
• if list, each dataframe in the list is written to a separate
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
400
|
+
• if list, each dataframe in the list is written to a separate
|
|
401
|
+
worksheet.
|
|
402
|
+
• if dict, the keys are considered sheet names and the values
|
|
403
|
+
should be DataFrames. (i.e. {'My Sheet 1': df1, 'My Sheet 2':
|
|
404
|
+
df2}). You may not pass a sheets argument when passing a
|
|
405
|
+
dictionary as the args argument.
|
|
395
406
|
sheets : list | str | None
|
|
396
407
|
Mist of sheet names. Must be None when 'args' is a dictionary.
|
|
397
408
|
kwargs : dict
|
|
@@ -407,15 +418,16 @@ class ExcelFile(FileBase):
|
|
|
407
418
|
if isinstance(args, dict):
|
|
408
419
|
if sheets is not None:
|
|
409
420
|
raise ValueError(
|
|
410
|
-
"'sheets' argument must be None
|
|
421
|
+
"'sheets' argument must be None "
|
|
422
|
+
"when a dictionary is passed"
|
|
411
423
|
)
|
|
412
424
|
sheets = list(args.keys())
|
|
413
425
|
args = list(args.values())
|
|
414
426
|
else:
|
|
415
|
-
args = odd.
|
|
427
|
+
args = odd.ensure_list(args)
|
|
416
428
|
|
|
417
429
|
if sheets is not None:
|
|
418
|
-
sheets = odd.
|
|
430
|
+
sheets = odd.ensure_list(sheets)
|
|
419
431
|
if len(args) != len(sheets):
|
|
420
432
|
raise ValueError(
|
|
421
433
|
f"length of 'args' ({len(args)}) != length of 'sheets' "
|
|
@@ -482,44 +494,44 @@ class ExcelFile(FileBase):
|
|
|
482
494
|
------------
|
|
483
495
|
start_cell : str | tuple
|
|
484
496
|
Individual cell range to be used as the starting position for the
|
|
485
|
-
fill. May be a standard excel range (e.g. 'A2') or a tuple of
|
|
486
|
-
two (column, row) (e.g. (1, 2) | ('A', 2)).
|
|
497
|
+
fill. May be a standard excel range (e.g. 'A2') or a tuple of
|
|
498
|
+
length two (column, row) (e.g. (1, 2) | ('A', 2)).
|
|
487
499
|
data : 1D list | 2D list | single value | pd.DataFrame | pd.Series
|
|
488
|
-
Data to be written to excel worksheet starting in start_cell.
|
|
489
|
-
|
|
490
|
-
For example, if start_cell = 'A1' and data = [[1, 2, 3], ['a', 'b',
|
|
491
|
-
then row 1 and 2 will be filled with 1, 2, 3 and 'a', 'b',
|
|
492
|
-
|
|
500
|
+
Data to be written to excel worksheet starting in start_cell. Each
|
|
501
|
+
sub-list is treated as row data and written in sequential order.
|
|
502
|
+
For example, if start_cell = 'A1' and data = [[1, 2, 3], ['a', 'b',
|
|
503
|
+
'c']], then row 1 and 2 will be filled with 1, 2, 3 and 'a', 'b',
|
|
504
|
+
'c', respectively.
|
|
505
|
+
formatting : str | dict | list(1D) | tuple(1D) | None
|
|
493
506
|
Excel formatting to be applied to data. Supported types include:
|
|
494
|
-
• str ➜ Strings are interpreted as self.formats dictionary
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
507
|
+
• str ➜ Strings are interpreted as self.formats dictionary
|
|
508
|
+
keys. Passing a single string (e.g. formatting='bold')
|
|
509
|
+
causes the corresponding format to be be universally
|
|
510
|
+
applied to all data cells.
|
|
498
511
|
• dict ➜ Dictionaries are interpreted as format parameters.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
to all data cells.
|
|
512
|
+
Passing a single dictionary (e.g. formatting = {'bold':
|
|
513
|
+
True, 'num_format': '#,##0'}) causes the corresponding
|
|
514
|
+
format to be be universally applied to all data cells.
|
|
503
515
|
• list | tuple ➜ formats included in a list/tuuple are applied
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
remaining columns.
|
|
516
|
+
to columns in sequential order. If the length of the list
|
|
517
|
+
or tuple is shorter than the number of columns then no
|
|
518
|
+
format is applied to the remaining columns.
|
|
508
519
|
• None ➜ no formatting is applied.
|
|
509
520
|
inverse : bool
|
|
510
|
-
If True, the 2D data is inverted such that each sub-list is
|
|
511
|
-
as column data as opposed to row data under the default
|
|
512
|
-
|
|
521
|
+
If True, the 2D data is inverted such that each sub-list is
|
|
522
|
+
treated as column data as opposed to row data under the default
|
|
523
|
+
behavior. For example:
|
|
524
|
+
[[1, 2, 3], ['a', 'b', 'c']] ➜ [[1, 'a'], [2, 'b'], [3, 'c']]
|
|
513
525
|
repeat : int
|
|
514
526
|
If 'data' argument is a single data element then it will be repeated
|
|
515
527
|
or duplicated this number of times.
|
|
516
528
|
sheet : str
|
|
517
|
-
If None, self.active_sheet is utilized. If self.active_sheet has
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
If not None, self.active_sheet will be assigned to the passed
|
|
521
|
-
|
|
522
|
-
|
|
529
|
+
If None, self.active_sheet is utilized. If self.active_sheet has
|
|
530
|
+
not yet been assigned, it will be assigned to a newly created
|
|
531
|
+
blank worksheet named 'Sheet1'.
|
|
532
|
+
If not None, self.active_sheet will be assigned to the passed
|
|
533
|
+
sheet name. If the passed name does not correspond to an
|
|
534
|
+
existing sheet, it will be created.
|
|
523
535
|
outer_border : bool
|
|
524
536
|
If True, data is encased in an outer border.
|
|
525
537
|
|
|
@@ -666,60 +678,60 @@ class ExcelFile(FileBase):
|
|
|
666
678
|
data_format : ↑
|
|
667
679
|
Special cases:
|
|
668
680
|
• 'auto' ➜ formatting is automatically applied to numeric, percent,
|
|
669
|
-
|
|
681
|
+
and date fields.
|
|
670
682
|
• dict ➜ if the dictionary keys are only comprised of 'df' index or
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
683
|
+
column names then the values are treated like format parameters
|
|
684
|
+
and formatting is only applied to those columns included in the
|
|
685
|
+
keys. If not all the key values are column names then the
|
|
686
|
+
dictionary receives the default treatment outlined in the
|
|
687
|
+
self.write documentation. (e.g.{'Price': 'commas', 'Total':
|
|
688
|
+
{'bold': True}})
|
|
677
689
|
Other cases:
|
|
678
690
|
see self.write 'formatting' argument documentation.
|
|
679
691
|
column_widths : 'auto' | list | tuple | dict
|
|
680
|
-
• 'auto' ➜ xlsxwriter does not support auto-fitting column widths
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
692
|
+
• 'auto' ➜ xlsxwriter does not support auto-fitting column widths
|
|
693
|
+
so this attempts replicate it by setting the column width
|
|
694
|
+
according to the length of the values in each column (up to a
|
|
695
|
+
certain limit).
|
|
684
696
|
• list | tuple ➜ widths are applied to columns in sequential order.
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
697
|
+
If the length of the list/tuple is shorter than the
|
|
698
|
+
number of columns then the width is not set on the
|
|
699
|
+
remaining columns.
|
|
688
700
|
• dict ➜ dictionary where keys are DataFrame column names and values
|
|
689
|
-
|
|
690
|
-
|
|
701
|
+
are column widths. Any column names excluded from the dictionary
|
|
702
|
+
will not have their widths set.
|
|
691
703
|
date_format : None | str | dict
|
|
692
704
|
Defines how date-like columns are parsed when data_format='auto'
|
|
693
705
|
(e.g. '%Y-%m-%d'). Options include:
|
|
694
706
|
• None ➜ format is inferred.
|
|
695
707
|
• str ➜ used for all date-like columns.
|
|
696
708
|
• dict ➜ dictionary where keys are DataFrame column names and values
|
|
697
|
-
|
|
698
|
-
|
|
709
|
+
are formats. Any column names excluded from the dictionary
|
|
710
|
+
default to None.
|
|
699
711
|
normalize : bool
|
|
700
712
|
if True, any date columns where the hours, minutes, seconds,
|
|
701
|
-
|
|
702
|
-
|
|
713
|
+
microseconds are all set to zero (midnight) will be converted
|
|
714
|
+
from a datetime to date.
|
|
703
715
|
autofilter : bool
|
|
704
716
|
if True, a filter will be applied to the column headers.
|
|
705
717
|
raise_on_empty : bool
|
|
706
718
|
if True and the 'df' argument is empty, an exception will be raised.
|
|
707
719
|
if False, the 'df' columns will be written to an otherwise empty
|
|
708
|
-
|
|
720
|
+
worksheet.
|
|
709
721
|
total_row : bool
|
|
710
|
-
if True, a row is added at the bottom reflecting the sum of each
|
|
711
|
-
|
|
722
|
+
if True, a row is added at the bottom reflecting the sum of each
|
|
723
|
+
numeric column.
|
|
712
724
|
total_row_format : ^
|
|
713
|
-
format applied if 'total_row' is True. If None and data_format=
|
|
714
|
-
then the same formatting will be applied to the total row
|
|
715
|
-
and a top border.
|
|
725
|
+
format applied if 'total_row' is True. If None and data_format =
|
|
726
|
+
'auto', then the same formatting will be applied to the total row
|
|
727
|
+
plus bold and a top border.
|
|
716
728
|
total_column : bool
|
|
717
|
-
if True, a column is added at the end reflecting the sum of all
|
|
718
|
-
values in each row.
|
|
729
|
+
if True, a column is added at the end reflecting the sum of all
|
|
730
|
+
numeric values in each row.
|
|
719
731
|
total_column_format : ^
|
|
720
|
-
format applied if 'total_column' is True. If None and data_format=
|
|
721
|
-
then the same formatting will be applied to the total column
|
|
722
|
-
a left border.
|
|
732
|
+
format applied if 'total_column' is True. If None and data_format =
|
|
733
|
+
'auto', then the same formatting will be applied to the total column
|
|
734
|
+
plus bold and a left border.
|
|
723
735
|
|
|
724
736
|
Returns
|
|
725
737
|
------------
|
|
@@ -730,7 +742,7 @@ class ExcelFile(FileBase):
|
|
|
730
742
|
|
|
731
743
|
odd.validate_value(
|
|
732
744
|
value=date_format,
|
|
733
|
-
|
|
745
|
+
name='date_format',
|
|
734
746
|
types=(str, dict),
|
|
735
747
|
none_ok=True
|
|
736
748
|
)
|
|
@@ -740,7 +752,7 @@ class ExcelFile(FileBase):
|
|
|
740
752
|
raise NotImplementedError
|
|
741
753
|
|
|
742
754
|
# Reset index
|
|
743
|
-
if odd.
|
|
755
|
+
if odd.has_named_index(df):
|
|
744
756
|
df.reset_index(inplace=True)
|
|
745
757
|
|
|
746
758
|
# Check if empty
|
|
@@ -758,7 +770,7 @@ class ExcelFile(FileBase):
|
|
|
758
770
|
total_column = False
|
|
759
771
|
|
|
760
772
|
# Check for duplicate column names
|
|
761
|
-
odd.
|
|
773
|
+
odd.verify_unique(df, column_names=True)
|
|
762
774
|
|
|
763
775
|
# Add a total column to dataframe
|
|
764
776
|
if total_column:
|
|
@@ -880,11 +892,14 @@ class ExcelFile(FileBase):
|
|
|
880
892
|
if fmt is None:
|
|
881
893
|
datelike_columns.remove(k)
|
|
882
894
|
else:
|
|
883
|
-
df[k] = df[k].apply(odd.ignore_nan(
|
|
884
|
-
datetime.datetime.strptime(x, fmt)
|
|
895
|
+
df[k] = df[k].apply(odd.ignore_nan(
|
|
896
|
+
lambda x: datetime.datetime.strptime(x, fmt)
|
|
885
897
|
))
|
|
886
898
|
datetime_columns.add(k)
|
|
887
|
-
elif all(
|
|
899
|
+
elif all(
|
|
900
|
+
issubclass(x, datetime.datetime)
|
|
901
|
+
for x in types
|
|
902
|
+
):
|
|
888
903
|
datetime_columns.add(k)
|
|
889
904
|
else:
|
|
890
905
|
datelike_columns.remove(k)
|
|
@@ -936,7 +951,7 @@ class ExcelFile(FileBase):
|
|
|
936
951
|
|
|
937
952
|
if total_column_format == 'auto':
|
|
938
953
|
data_format[total_column_name] = \
|
|
939
|
-
self.add_format(odd.
|
|
954
|
+
self.add_format(odd.ensure_list(
|
|
940
955
|
data_format.pop(total_column_name, [])
|
|
941
956
|
) + ['bold','left'])
|
|
942
957
|
else:
|
|
@@ -964,8 +979,12 @@ class ExcelFile(FileBase):
|
|
|
964
979
|
last_row = first_row + len(df) - 1
|
|
965
980
|
for k in df.columns:
|
|
966
981
|
if k in numeric_columns:
|
|
967
|
-
col = self.get_column_letter(
|
|
968
|
-
|
|
982
|
+
col = self.get_column_letter(
|
|
983
|
+
start_col + df.columns.get_loc(k)
|
|
984
|
+
)
|
|
985
|
+
total_row.append(
|
|
986
|
+
f'=SUM({col}{first_row}:{col}{last_row})'
|
|
987
|
+
)
|
|
969
988
|
else:
|
|
970
989
|
total_row.append(None)
|
|
971
990
|
|
|
@@ -973,8 +992,10 @@ class ExcelFile(FileBase):
|
|
|
973
992
|
total_row_format = []
|
|
974
993
|
for k in df.columns:
|
|
975
994
|
fmt = ['bold','top']
|
|
976
|
-
if total_column and k == total_column_name:
|
|
977
|
-
|
|
995
|
+
if total_column and k == total_column_name:
|
|
996
|
+
fmt.append('left')
|
|
997
|
+
if k in data_format:
|
|
998
|
+
fmt.append(data_format[k])
|
|
978
999
|
total_row_format.append(fmt)
|
|
979
1000
|
|
|
980
1001
|
if total_row_format is None and data_format is not None:
|
|
@@ -1044,20 +1065,22 @@ class ExcelFile(FileBase):
|
|
|
1044
1065
|
Parameters
|
|
1045
1066
|
------------
|
|
1046
1067
|
start_cell : str | tuple | list
|
|
1047
|
-
Individual cell range to be used as the starting position for the
|
|
1048
|
-
May be a standard excel range (e.g. 'A2') or a tuple of
|
|
1049
|
-
(column, row) (e.g. (1, 2) | ('A', 2)). Similar to the
|
|
1050
|
-
column names may include placeholders (e.g.
|
|
1068
|
+
Individual cell range to be used as the starting position for the
|
|
1069
|
+
fill. May be a standard excel range (e.g. 'A2') or a tuple of
|
|
1070
|
+
length two (column, row) (e.g. (1, 2) | ('A', 2)). Similar to the
|
|
1071
|
+
formula argument, column names may include placeholders (e.g.
|
|
1072
|
+
'{Price}2' | ('{Price}', 2)).
|
|
1051
1073
|
formula : str
|
|
1052
|
-
Excel formula to be written to the start cell and used as a fill
|
|
1053
|
-
Column names may include placeholders with header names
|
|
1054
|
-
argument is passed (e.g. '=A1+B1-{Price}1'.
|
|
1055
|
-
robust to changes in header
|
|
1074
|
+
Excel formula to be written to the start cell and used as a fill
|
|
1075
|
+
template. Column names may include placeholders with header names
|
|
1076
|
+
if 'headers' argument is passed (e.g. '=A1+B1-{Price}1'.
|
|
1077
|
+
Placeholders make the formula robust to changes in header
|
|
1078
|
+
positioning.
|
|
1056
1079
|
limit : int
|
|
1057
1080
|
Number of rows or columns to fill.
|
|
1058
1081
|
headers : list
|
|
1059
|
-
List of column header names. Required argument when placeholders
|
|
1060
|
-
used in formula or start_cell.
|
|
1082
|
+
List of column header names. Required argument when placeholders
|
|
1083
|
+
are used in formula or start_cell.
|
|
1061
1084
|
formatting : str
|
|
1062
1085
|
see write() argument of the same name.
|
|
1063
1086
|
down : bool
|
|
@@ -1086,7 +1109,8 @@ class ExcelFile(FileBase):
|
|
|
1086
1109
|
|
|
1087
1110
|
components = list(set(re.findall('([a-zA-Z]+)(\d+)', formula)))
|
|
1088
1111
|
cols, rows = zip(*components)
|
|
1089
|
-
cols
|
|
1112
|
+
cols = [self.get_column_index(x) + 1 for x in cols]
|
|
1113
|
+
rows = [int(x) for x in rows]
|
|
1090
1114
|
|
|
1091
1115
|
build_counter = lambda x: \
|
|
1092
1116
|
OrderedDict((i, k) for i, k in enumerate(sorted(list(set(x)))))
|
|
@@ -1104,7 +1128,10 @@ class ExcelFile(FileBase):
|
|
|
1104
1128
|
for x in range(limit):
|
|
1105
1129
|
format_args = list(counter.values())
|
|
1106
1130
|
if not down:
|
|
1107
|
-
format_args = [
|
|
1131
|
+
format_args = [
|
|
1132
|
+
self.get_column_letter(c - 1)
|
|
1133
|
+
for c in format_args
|
|
1134
|
+
]
|
|
1108
1135
|
data.append(formula.format(*format_args))
|
|
1109
1136
|
for k in counter:
|
|
1110
1137
|
counter[k] += 1
|
|
@@ -1130,19 +1157,17 @@ class ExcelFile(FileBase):
|
|
|
1130
1157
|
------------
|
|
1131
1158
|
name : str | tuple | list
|
|
1132
1159
|
• str ➜ name of new format.
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
{'bold': True, 'num_format': '#,##0'}
|
|
1145
|
-
|
|
1160
|
+
If 'fmt' is None, if the name is already in self.formats then
|
|
1161
|
+
no action is taken. If the name does not already exist then
|
|
1162
|
+
the name's components (delimited by underscores ('_')) will
|
|
1163
|
+
be combined into a new format (e.g.'bold_commas').
|
|
1164
|
+
if 'fmt' is not None, if the name conflicts with an existing
|
|
1165
|
+
format name, the existing format will be overwritten.
|
|
1166
|
+
• tuple | list ➜ If 'fmt' is None, components will be combined
|
|
1167
|
+
into a new format. For example, self.add_format(name=
|
|
1168
|
+
['bold','commas'], fmt=None) would add the following entry to
|
|
1169
|
+
self.formats: {'bold_commas': {'bold': True, 'num_format':
|
|
1170
|
+
'#,##0'}
|
|
1146
1171
|
fmt : dict | None
|
|
1147
1172
|
• dict ➜ see https://xlsxwriter.readthedocs.io/format.html
|
|
1148
1173
|
• None ➜ format will be constructed based on the components
|
|
@@ -1150,23 +1175,29 @@ class ExcelFile(FileBase):
|
|
|
1150
1175
|
Returns
|
|
1151
1176
|
------------
|
|
1152
1177
|
out : str
|
|
1153
|
-
|
|
1178
|
+
Name of format (in other words, the self.formats dictionary key
|
|
1179
|
+
value)
|
|
1154
1180
|
'''
|
|
1155
1181
|
if fmt is None:
|
|
1156
1182
|
if isinstance(name, str):
|
|
1157
|
-
if name in self.formats:
|
|
1183
|
+
if name in self.formats:
|
|
1184
|
+
return name
|
|
1158
1185
|
name = name.split('_')
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1186
|
+
|
|
1187
|
+
odd.validate_value(
|
|
1188
|
+
value=name,
|
|
1189
|
+
name=name,
|
|
1190
|
+
types=(tuple, list)
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1163
1193
|
fmt = dict()
|
|
1164
1194
|
for k in name: fmt.update(self.formats[k])
|
|
1165
1195
|
name = '_'.join(odd.natural_sort(name))
|
|
1166
1196
|
else:
|
|
1167
1197
|
if not isinstance(name, str):
|
|
1168
1198
|
raise TypeError(
|
|
1169
|
-
"'name' argument must be a string
|
|
1199
|
+
"'name' argument must be a string "
|
|
1200
|
+
"if 'fmt' is not None, not: "
|
|
1170
1201
|
f"{type(fmt).__name__}"
|
|
1171
1202
|
)
|
|
1172
1203
|
|
|
@@ -35,8 +35,8 @@ class SQLiteFile(FileBase):
|
|
|
35
35
|
def update_query(tbl_name, update_cols, where_cols, where_logic=''):
|
|
36
36
|
sql = 'UPDATE {0} SET {1} WHERE {2} {3}'.format(
|
|
37
37
|
tbl_name,
|
|
38
|
-
', '.join('[%s] = ?' % x for x in odd.
|
|
39
|
-
' AND '.join('[%s] = ?' % x for x in odd.
|
|
38
|
+
', '.join('[%s] = ?' % x for x in odd.ensure_list(update_cols)),
|
|
39
|
+
' AND '.join('[%s] = ?' % x for x in odd.ensure_list(where_cols)),
|
|
40
40
|
where_logic
|
|
41
41
|
)
|
|
42
42
|
return sql
|
|
@@ -46,8 +46,8 @@ class SQLiteFile(FileBase):
|
|
|
46
46
|
def select_query(tbl_name, select_cols, where_cols, where_logic=''):
|
|
47
47
|
sql = 'SELECT {1} FROM {0} WHERE {2} {3}'.format(
|
|
48
48
|
tbl_name,
|
|
49
|
-
', '.join('[%s]' % x for x in odd.
|
|
50
|
-
' AND '.join('[%s] = ?' % x for x in odd.
|
|
49
|
+
', '.join('[%s]' % x for x in odd.ensure_list(select_cols)),
|
|
50
|
+
' AND '.join('[%s] = ?' % x for x in odd.ensure_list(where_cols)),
|
|
51
51
|
where_logic
|
|
52
52
|
)
|
|
53
53
|
return sql
|
|
@@ -57,7 +57,7 @@ class SQLiteFile(FileBase):
|
|
|
57
57
|
def delete_query(tbl_name, where_cols, where_logic=''):
|
|
58
58
|
sql = 'DELETE FROM {0} WHERE {1} {2}'.format(
|
|
59
59
|
tbl_name,
|
|
60
|
-
' AND '.join('[%s] = ?' % x for x in odd.
|
|
60
|
+
' AND '.join('[%s] = ?' % x for x in odd.ensure_list(where_cols)),
|
|
61
61
|
where_logic
|
|
62
62
|
)
|
|
63
63
|
return sql
|
|
@@ -151,8 +151,11 @@ class SQLiteFile(FileBase):
|
|
|
151
151
|
def format_payload(self, tbl_name, col_names, payload):
|
|
152
152
|
if isinstance(payload, tuple): payload = [payload]
|
|
153
153
|
mapping = self.data_types(tbl_name)
|
|
154
|
-
payload = [
|
|
155
|
-
|
|
154
|
+
payload = [
|
|
155
|
+
tuple([mapping[col](cell) if pd.notnull(cell) else None
|
|
156
|
+
for col, cell in zip(col_names, row)])
|
|
157
|
+
for row in payload
|
|
158
|
+
]
|
|
156
159
|
return payload
|
|
157
160
|
|
|
158
161
|
|
|
@@ -187,7 +190,7 @@ class SQLiteFile(FileBase):
|
|
|
187
190
|
df.reset_index(inplace=True)
|
|
188
191
|
|
|
189
192
|
df.rename(columns={x: x.lower() for x in df.columns}, inplace=True)
|
|
190
|
-
col_names = [x for x in odd.
|
|
193
|
+
col_names = [x for x in odd.lower_iterable(self.columns(tbl_name))
|
|
191
194
|
if x in set(list(df.columns))]
|
|
192
195
|
df = df[col_names]
|
|
193
196
|
|
|
@@ -197,9 +200,14 @@ class SQLiteFile(FileBase):
|
|
|
197
200
|
)
|
|
198
201
|
|
|
199
202
|
if where_cols:
|
|
200
|
-
where_cols = odd.
|
|
203
|
+
where_cols = odd.lower_iterable(odd.ensure_list(where_cols))
|
|
201
204
|
col_names = [x for x in col_names if x not in where_cols]
|
|
202
|
-
sql = self.update_query(
|
|
205
|
+
sql = self.update_query(
|
|
206
|
+
tbl_name,
|
|
207
|
+
col_names,
|
|
208
|
+
where_cols,
|
|
209
|
+
where_logic
|
|
210
|
+
)
|
|
203
211
|
col_names.extend(where_cols)
|
|
204
212
|
df = df[col_names]
|
|
205
213
|
else:
|
|
@@ -216,12 +224,17 @@ class SQLiteFile(FileBase):
|
|
|
216
224
|
def copy_as_temp(self, target_name, temp_name=None, index=None):
|
|
217
225
|
''' creates temporary table based on permanent table '''
|
|
218
226
|
temp_name = temp_name or target_name
|
|
219
|
-
|
|
227
|
+
|
|
228
|
+
sql = f'CREATE TEMP TABLE {temp_name} AS ' + \
|
|
229
|
+
f'SELECT * FROM {target_name} LIMIT 0;'
|
|
230
|
+
|
|
220
231
|
self.c.execute(sql)
|
|
221
232
|
self.conn.commit()
|
|
222
233
|
|
|
223
234
|
if index:
|
|
224
|
-
sql = f"CREATE INDEX temp.idx ON {temp_name}(
|
|
235
|
+
sql = f"CREATE INDEX temp.idx ON {temp_name}(%s)" % \
|
|
236
|
+
', '.join(odd.ensure_list(index))
|
|
237
|
+
|
|
225
238
|
self.c.execute(sql)
|
|
226
239
|
self.conn.commit()
|
|
227
240
|
|
|
@@ -48,24 +48,27 @@ class ZipFile(FileBase):
|
|
|
48
48
|
Parameters
|
|
49
49
|
------------
|
|
50
50
|
payload : str | list | tuple | FileBase | Folder
|
|
51
|
-
|
|
52
|
-
of
|
|
53
|
-
zipped
|
|
51
|
+
String representing a single file or folder or a list or tuple
|
|
52
|
+
comprised of string, FileBase, or Folder objects representing
|
|
53
|
+
files and folders to be zipped.
|
|
54
54
|
delete_original : bool
|
|
55
55
|
If True, zipped files/folders are deleted after being zipped
|
|
56
56
|
filter_func : callable
|
|
57
|
-
|
|
58
|
-
type FileBase). If filter_func returns True for a given file it
|
|
59
|
-
be included in the zip process otherwise excluded (e.g.
|
|
60
|
-
files that are not of file extention .py lambda x:
|
|
57
|
+
Function applied to every file in the folder (argument will be of
|
|
58
|
+
type FileBase). If filter_func returns True for a given file it
|
|
59
|
+
will be included in the zip process otherwise excluded (e.g.
|
|
60
|
+
filters out files that are not of file extention .py lambda x:
|
|
61
|
+
x.ext == 'py').
|
|
61
62
|
include_folders : bool
|
|
62
|
-
|
|
63
|
-
mirroring the name of the folder in which the unzipped version
|
|
64
|
-
|
|
63
|
+
If True, each object being zipped will be placed in a folder
|
|
64
|
+
mirroring the name of the folder in which the unzipped version
|
|
65
|
+
resides.
|
|
66
|
+
Note: This argument cannot be true when files_only is True.
|
|
65
67
|
files_only : bool
|
|
66
|
-
if True, zip folder will only include individual files, folders
|
|
67
|
-
disregarded. For example, if you pass a folder that contains
|
|
68
|
-
all the individual files will be zipped and subfolders
|
|
68
|
+
if True, zip folder will only include individual files, folders
|
|
69
|
+
are disregarded. For example, if you pass a folder that contains
|
|
70
|
+
subfolders, all the individual files will be zipped and subfolders
|
|
71
|
+
are discarded.
|
|
69
72
|
This is the default behavior when 'payload' contains files. This
|
|
70
73
|
argument cannot be True when include_folder is True.
|
|
71
74
|
verbose : bool
|
|
@@ -83,14 +86,19 @@ class ZipFile(FileBase):
|
|
|
83
86
|
'C:/Folder C/'
|
|
84
87
|
]
|
|
85
88
|
|
|
86
|
-
'Folder C' contains 'File 3.txt' and a subfolder 'Folder D' with a
|
|
87
|
-
'File 4.txt'.
|
|
89
|
+
'Folder C' contains 'File 3.txt' and a subfolder 'Folder D' with a
|
|
90
|
+
file called 'File 4.txt'.
|
|
88
91
|
|
|
89
92
|
• Default (include_folders=False, files_only=False)
|
|
90
|
-
Under default behavior the only time a folder will be included in
|
|
91
|
-
folder is when a passed folder contains subfolders
|
|
93
|
+
Under default behavior the only time a folder will be included in
|
|
94
|
+
the zip folder is when a passed folder contains subfolders
|
|
92
95
|
|
|
93
|
-
Output: [
|
|
96
|
+
Output: [
|
|
97
|
+
'File 1.txt',
|
|
98
|
+
'File 2.txt',
|
|
99
|
+
'File 3.txt',
|
|
100
|
+
'Folder D/File 4.txt'
|
|
101
|
+
]
|
|
94
102
|
|
|
95
103
|
• include_folders=True
|
|
96
104
|
Every file will retain the folder in which it resides
|
|
@@ -155,7 +163,8 @@ class ZipFile(FileBase):
|
|
|
155
163
|
|
|
156
164
|
if include_folders and files_only:
|
|
157
165
|
raise ValueError(
|
|
158
|
-
"'include_folders' and 'files_only'
|
|
166
|
+
"'include_folders' and 'files_only' "
|
|
167
|
+
"arguments cannot both be True."
|
|
159
168
|
)
|
|
160
169
|
|
|
161
170
|
if not isinstance(payload, (list, tuple)):
|
|
@@ -174,8 +183,14 @@ class ZipFile(FileBase):
|
|
|
174
183
|
folder_can_be_deleted = True
|
|
175
184
|
|
|
176
185
|
for f in obj.walk():
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
p = f.directory[:-1]\
|
|
187
|
+
.replace(obj.parent.path, '')\
|
|
188
|
+
.split('/')
|
|
189
|
+
|
|
190
|
+
zipped = zip_file(f, p)
|
|
191
|
+
|
|
192
|
+
if not zipped:
|
|
193
|
+
folder_can_be_deleted = False
|
|
179
194
|
|
|
180
195
|
if delete_original and folder_can_be_deleted:
|
|
181
196
|
shutil.rmtree(obj.path)
|
|
@@ -383,7 +383,9 @@ class FileBase(object):
|
|
|
383
383
|
def trifurcate_and_fill(self, f):
|
|
384
384
|
''' trifurcates file and fills gaps with instance attributes '''
|
|
385
385
|
folder, name, ext = trifurcate(f, default_folder=False)
|
|
386
|
-
f = (folder or self.directory) +
|
|
386
|
+
f = (folder or self.directory) + \
|
|
387
|
+
(name or self.name) + '.' + \
|
|
388
|
+
(ext or self.ext)
|
|
387
389
|
return self.spawn(f)
|
|
388
390
|
|
|
389
391
|
|
|
@@ -447,8 +449,8 @@ class FileBase(object):
|
|
|
447
449
|
|
|
448
450
|
|
|
449
451
|
def swap(self, **kwargs):
|
|
450
|
-
'''
|
|
451
|
-
|
|
452
|
+
''' returns a new file instance with different attribute(s) (e.g.
|
|
453
|
+
you have a csv file and want an xlsx file of the same name) '''
|
|
452
454
|
alias_map = {'folder': 'directory', 'ext': 'extension'}
|
|
453
455
|
kwargs = {alias_map.get(k, k): v for k, v in kwargs.items()}
|
|
454
456
|
directory, name, extension = [
|
|
@@ -32,8 +32,8 @@ def backup_folder(
|
|
|
32
32
|
shallow : bool
|
|
33
33
|
filecmp.cmp(f1, f2, shallow=True) shallow argument.
|
|
34
34
|
"If shallow is true and the os.stat() signatures (file type, size, and
|
|
35
|
-
modification time) of both files are identical, the files are taken to
|
|
36
|
-
equal."
|
|
35
|
+
modification time) of both files are identical, the files are taken to
|
|
36
|
+
be equal."
|
|
37
37
|
https://docs.python.org/3/library/filecmp.html
|
|
38
38
|
verbose : bool
|
|
39
39
|
if True, all folders and files that were backed up or overwritten are
|
|
@@ -87,13 +87,15 @@ class Folder(object):
|
|
|
87
87
|
'''
|
|
88
88
|
Description
|
|
89
89
|
--------------------
|
|
90
|
-
Class that enables operations on all contents of a folder
|
|
91
|
-
including obtaining metadata, filtering, deleting,
|
|
92
|
-
encompasses both files and folders but you can
|
|
93
|
-
using the Files and Folders subclasses,
|
|
90
|
+
Class that enables operations on all contents of a folder
|
|
91
|
+
collectively, including obtaining metadata, filtering, deleting,
|
|
92
|
+
and more. This class encompasses both files and folders but you can
|
|
93
|
+
access them separetly by using the Files and Folders subclasses,
|
|
94
|
+
respectively.
|
|
94
95
|
|
|
95
|
-
Note: This class and its subclasses are intended to be accessed
|
|
96
|
-
by using the 'contents', 'folders', and 'files' Folder
|
|
96
|
+
Note: This class and its subclasses are intended to be accessed
|
|
97
|
+
indirectly by using the 'contents', 'folders', and 'files' Folder
|
|
98
|
+
cached properties.
|
|
97
99
|
|
|
98
100
|
Class Attributes
|
|
99
101
|
--------------------
|
|
@@ -205,7 +207,8 @@ class Folder(object):
|
|
|
205
207
|
Example:
|
|
206
208
|
------------
|
|
207
209
|
Consider a folder that contains multiple files that include a
|
|
208
|
-
YYYY-MM-DD timestamp in the name such as
|
|
210
|
+
YYYY-MM-DD timestamp in the name such as
|
|
211
|
+
'2024-06-23 Budget.xlsx'.
|
|
209
212
|
In this case you might pass date_pattern=r'\d{4}\-\d{2}\-\d{2}'
|
|
210
213
|
|
|
211
214
|
date_format : str
|
|
@@ -214,35 +217,37 @@ class Folder(object):
|
|
|
214
217
|
• 'date_pattern' is None ➜ an attempt will be made to derive
|
|
215
218
|
the regex pattern using 'date_format' as the template.
|
|
216
219
|
• 'date_pattern' is not None ➜ 'date_format' is optional but
|
|
217
|
-
it can be included if you do not want to rely on
|
|
218
|
-
inferring the format.
|
|
220
|
+
it can be included if you do not want to rely on
|
|
221
|
+
pd.to_datetime inferring the format.
|
|
219
222
|
|
|
220
223
|
Example:
|
|
221
224
|
------------
|
|
222
225
|
Dealing with the same example from above, all the following
|
|
223
226
|
combinations will achieve the same result since the None values
|
|
224
227
|
being inferred:
|
|
225
|
-
• date_pattern=r'\d{4}\-\d{2}\-\d{2}',
|
|
228
|
+
• date_pattern=r'\d{4}\-\d{2}\-\d{2}',
|
|
229
|
+
date_format='%Y-%m-%d'
|
|
226
230
|
• date_pattern=None, date_format='%Y-%m-%d'
|
|
227
231
|
• date_pattern=r'\d{4}\-\d{2}\-\d{2}', date_format=None
|
|
228
232
|
|
|
229
233
|
ext : str
|
|
230
234
|
If not None, only those file objects with this file extension
|
|
231
235
|
will be considered candidates. This argument may be passed with
|
|
232
|
-
or without a period and is not case sensitive (e.g. '.txt',
|
|
233
|
-
'.TXT' are all valid).
|
|
236
|
+
or without a period and is not case sensitive (e.g. '.txt',
|
|
237
|
+
'txt', '.TXT' are all valid).
|
|
234
238
|
index : int
|
|
235
|
-
If not None, the object at this integer index in the filtered
|
|
236
|
-
sorted data is returned.
|
|
239
|
+
If not None, the object at this integer index in the filtered
|
|
240
|
+
and sorted data is returned.
|
|
237
241
|
sort_by : str
|
|
238
|
-
Name of self.meta_data column to sort by. This argument must
|
|
239
|
-
if the 'date_pattern' or 'date_format' arguments are
|
|
240
|
-
to the object created date if None.
|
|
242
|
+
Name of self.meta_data column to sort by. This argument must
|
|
243
|
+
be None if the 'date_pattern' or 'date_format' arguments are
|
|
244
|
+
not None. Defaults to the object created date if None.
|
|
241
245
|
ascending : bool
|
|
242
|
-
If True, data is sorted in ascending order, otherwise,
|
|
243
|
-
If the 'date_pattern' or 'date_format'
|
|
244
|
-
|
|
245
|
-
the
|
|
246
|
+
If True, data is sorted in ascending order, otherwise,
|
|
247
|
+
descending order. If the 'date_pattern' or 'date_format'
|
|
248
|
+
arguments are not None, the sort will occur on the timestamps
|
|
249
|
+
extracted from the object names, otherwise, the column denoted
|
|
250
|
+
by the 'sort_by' argument is used.
|
|
246
251
|
errors : bool
|
|
247
252
|
determines how exceptions are handled:
|
|
248
253
|
• 'raise': exception is raised
|
|
@@ -278,8 +283,9 @@ class Folder(object):
|
|
|
278
283
|
else:
|
|
279
284
|
if date_format or date_pattern:
|
|
280
285
|
raise ValueError(
|
|
281
|
-
"cannot pass 'sort_by' argument if either
|
|
282
|
-
"or 'date_pattern' arguments are
|
|
286
|
+
"cannot pass 'sort_by' argument if either "
|
|
287
|
+
"'date_format' or 'date_pattern' arguments are "
|
|
288
|
+
"not None."
|
|
283
289
|
)
|
|
284
290
|
|
|
285
291
|
kind = self.__class__.__name__.lower()
|
|
@@ -311,12 +317,15 @@ class Folder(object):
|
|
|
311
317
|
|
|
312
318
|
if df.empty:
|
|
313
319
|
if errors == 'ignore': return
|
|
314
|
-
message = [
|
|
315
|
-
|
|
320
|
+
message = [
|
|
321
|
+
f"no {kind} matching name pattern '{name_pattern}'"
|
|
322
|
+
]
|
|
323
|
+
if ext is not None:
|
|
324
|
+
message.append(f"with extension '.{ext}'")
|
|
316
325
|
raise ValueError(' '.join(message + ['found.']))
|
|
317
326
|
|
|
318
327
|
if date_format is not None and date_pattern is None:
|
|
319
|
-
date_pattern = cw.
|
|
328
|
+
date_pattern = cw.temporal_format_to_regex(date_format)
|
|
320
329
|
|
|
321
330
|
if date_pattern is not None:
|
|
322
331
|
date_pattern = format_date_pattern(date_pattern)
|
|
@@ -333,15 +342,18 @@ class Folder(object):
|
|
|
333
342
|
)
|
|
334
343
|
|
|
335
344
|
s = pd.to_datetime(s.dropna(), format=date_format)
|
|
336
|
-
|
|
345
|
+
|
|
346
|
+
df = df.join(s, how='inner')\
|
|
347
|
+
.sort_values(by=s.name, ascending=ascending)
|
|
337
348
|
|
|
338
349
|
if index is not None:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
350
|
+
odd.validate_value(
|
|
351
|
+
value=index,
|
|
352
|
+
name='index',
|
|
353
|
+
types=int
|
|
354
|
+
)
|
|
343
355
|
try:
|
|
344
|
-
return self.to_dict()[
|
|
356
|
+
return self.to_dict()[df.iloc[index].name]
|
|
345
357
|
except Exception as error:
|
|
346
358
|
if errors == 'ignore': return
|
|
347
359
|
raise error
|
|
@@ -682,7 +694,8 @@ class Folder(object):
|
|
|
682
694
|
self._subfolder_cache[key] = folder
|
|
683
695
|
return self._subfolder_cache[key]
|
|
684
696
|
|
|
685
|
-
# cache subfolder that does not exist yet
|
|
697
|
+
# cache subfolder that does not exist yet
|
|
698
|
+
# (it will now if read_only=False)
|
|
686
699
|
if self.troubleshoot:
|
|
687
700
|
print(f"could not find subfolder = '{name}'")
|
|
688
701
|
|
|
@@ -746,16 +759,17 @@ class Folder(object):
|
|
|
746
759
|
|
|
747
760
|
>> C:/Users/Me/MyFolder/A/B/
|
|
748
761
|
|
|
749
|
-
• Strings preceded by a period prior to the final argument are
|
|
750
|
-
like dot folders:
|
|
762
|
+
• Strings preceded by a period prior to the final argument are
|
|
763
|
+
treated like dot folders:
|
|
751
764
|
|
|
752
765
|
folder.join('.A','.B','C')
|
|
753
766
|
|
|
754
767
|
>> C:/Users/Me/MyFolder/.A/.B/C/
|
|
755
768
|
|
|
756
|
-
• Strings preceded by a period that are also the final argument
|
|
757
|
-
a special case. It will be treated as a dot file unless
|
|
758
|
-
slash to signal you intend for it to be considered a
|
|
769
|
+
• Strings preceded by a period that are also the final argument
|
|
770
|
+
present a special case. It will be treated as a dot file unless
|
|
771
|
+
you add a slash to signal you intend for it to be considered a
|
|
772
|
+
dot folder.
|
|
759
773
|
|
|
760
774
|
In this case, .env would be a dot folder subfile in folder B.
|
|
761
775
|
|
|
@@ -790,7 +804,8 @@ class Folder(object):
|
|
|
790
804
|
|
|
791
805
|
if is_file(f):
|
|
792
806
|
if not self.read_only:
|
|
793
|
-
# creates the folder(s) in the file path if they do not
|
|
807
|
+
# creates the folder(s) in the file path if they do not
|
|
808
|
+
# already exist
|
|
794
809
|
self.join(*f.replace(self.path,'').split('/')[:-1])
|
|
795
810
|
return self.spawn_file(f, **kwargs)
|
|
796
811
|
|
|
@@ -847,7 +862,11 @@ class Folder(object):
|
|
|
847
862
|
''' verify join args are valid '''
|
|
848
863
|
|
|
849
864
|
error_template = 'Invalid join argument detected at index %d:'
|
|
850
|
-
|
|
865
|
+
|
|
866
|
+
period_rules = (
|
|
867
|
+
'However, arguments may begin with '
|
|
868
|
+
'or contain a single period.'
|
|
869
|
+
)
|
|
851
870
|
|
|
852
871
|
for index, arg in enumerate(args):
|
|
853
872
|
err_msg = error_template % index
|
|
@@ -41,7 +41,8 @@ def trifurcate(f, default_folder=True):
|
|
|
41
41
|
f = f + '/' if explicitly_folder or '.' not in f.split('/')[-1] else f
|
|
42
42
|
if f.split('/')[0][-4:].lower() == '.com': f = '//' + f
|
|
43
43
|
f = f.rsplit('/', 1)
|
|
44
|
-
folder = f[0] + '/' if len(f) == 2 else
|
|
44
|
+
folder = f[0] + '/' if len(f) == 2 else \
|
|
45
|
+
(get_cwd() if default_folder else '')
|
|
45
46
|
name, ext = split_extension(f[-1])
|
|
46
47
|
return folder, name, ext.lower()
|
|
47
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pathpilot"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Library that facilitates file and folder manipulation in Python."
|
|
5
5
|
authors = ["Zachary Einck <zacharyeinck@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -10,9 +10,9 @@ homepage = "https://github.com/zteinck/pathpilot"
|
|
|
10
10
|
|
|
11
11
|
[tool.poetry.dependencies]
|
|
12
12
|
python = "^3.8"
|
|
13
|
-
clockwork = ">=0.
|
|
13
|
+
clockwork = ">=0.4.0"
|
|
14
14
|
cachegrab = ">=0.2.2"
|
|
15
|
-
oddments = ">=0.
|
|
15
|
+
oddments = ">=0.4.0"
|
|
16
16
|
pandas = "*"
|
|
17
17
|
numpy = "*"
|
|
18
18
|
XlsxWriter = "*"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|