psdi-data-conversion 0.0.37__py3-none-any.whl → 0.0.38__py3-none-any.whl
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.
- psdi_data_conversion/app.py +30 -4
- psdi_data_conversion/constants.py +6 -5
- psdi_data_conversion/converter.py +13 -6
- psdi_data_conversion/converters/base.py +56 -53
- psdi_data_conversion/converters/c2x.py +1 -0
- psdi_data_conversion/converters/openbabel.py +10 -10
- psdi_data_conversion/database.py +335 -113
- psdi_data_conversion/main.py +151 -69
- psdi_data_conversion/static/javascript/data.js +18 -4
- psdi_data_conversion/static/javascript/format.js +22 -9
- psdi_data_conversion/templates/index.htm +57 -48
- psdi_data_conversion/testing/constants.py +3 -0
- psdi_data_conversion/testing/conversion_callbacks.py +2 -1
- psdi_data_conversion/testing/conversion_test_specs.py +27 -15
- psdi_data_conversion/testing/gui.py +354 -292
- psdi_data_conversion/testing/utils.py +35 -16
- {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.38.dist-info}/METADATA +88 -4
- {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.38.dist-info}/RECORD +21 -21
- {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.38.dist-info}/WHEEL +0 -0
- {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.38.dist-info}/entry_points.txt +0 -0
- {psdi_data_conversion-0.0.37.dist-info → psdi_data_conversion-0.0.38.dist-info}/licenses/LICENSE +0 -0
psdi_data_conversion/database.py
CHANGED
@@ -8,13 +8,14 @@ Python module provide utilities for accessing the converter database
|
|
8
8
|
from __future__ import annotations
|
9
9
|
|
10
10
|
from dataclasses import dataclass, field
|
11
|
+
from itertools import product
|
11
12
|
import json
|
12
13
|
from logging import getLogger
|
13
14
|
import os
|
14
|
-
from typing import Any
|
15
|
+
from typing import Any, Literal
|
15
16
|
|
16
17
|
from psdi_data_conversion import constants as const
|
17
|
-
from psdi_data_conversion.converter import D_REGISTERED_CONVERTERS
|
18
|
+
from psdi_data_conversion.converter import D_REGISTERED_CONVERTERS, D_SUPPORTED_CONVERTERS
|
18
19
|
from psdi_data_conversion.converters.base import FileConverterException
|
19
20
|
|
20
21
|
# Keys for top-level and general items in the database
|
@@ -418,15 +419,52 @@ class FormatInfo:
|
|
418
419
|
|
419
420
|
# Load attributes from input
|
420
421
|
self.name = name
|
422
|
+
"""The name of this format"""
|
423
|
+
|
421
424
|
self.parent = parent
|
425
|
+
"""The database which this format belongs to"""
|
422
426
|
|
423
427
|
# Load attributes from the database
|
424
428
|
self.id: int = d_single_format_info.get(DB_ID_KEY, -1)
|
429
|
+
"""The ID of this format"""
|
430
|
+
|
425
431
|
self.note: str = d_single_format_info.get(DB_FORMAT_NOTE_KEY, "")
|
432
|
+
"""The description of this format"""
|
433
|
+
|
426
434
|
self.composition = d_single_format_info.get(DB_FORMAT_COMP_KEY)
|
435
|
+
"""Whether or not this format stores composition information"""
|
436
|
+
|
427
437
|
self.connections = d_single_format_info.get(DB_FORMAT_CONN_KEY)
|
438
|
+
"""Whether or not this format stores connections information"""
|
439
|
+
|
428
440
|
self.two_dim = d_single_format_info.get(DB_FORMAT_2D_KEY)
|
441
|
+
"""Whether or not this format stores 2D structural information"""
|
442
|
+
|
429
443
|
self.three_dim = d_single_format_info.get(DB_FORMAT_3D_KEY)
|
444
|
+
"""Whether or not this format stores 3D structural information"""
|
445
|
+
|
446
|
+
self._disambiguated_name: str | None = None
|
447
|
+
|
448
|
+
@property
|
449
|
+
def disambiguated_name(self) -> str:
|
450
|
+
"""A unique name for this format which can be used to distinguish it from others which share the same extension,
|
451
|
+
by appending the name of each with a unique index"""
|
452
|
+
if self._disambiguated_name is None:
|
453
|
+
l_formats_with_same_name = [x for x in self.parent.l_format_info if x and x.name == self.name]
|
454
|
+
if len(l_formats_with_same_name) == 1:
|
455
|
+
self._disambiguated_name = self.name
|
456
|
+
else:
|
457
|
+
index_of_this = [i for i, x in enumerate(l_formats_with_same_name) if self is x][0]
|
458
|
+
self._disambiguated_name = f"{self.name}-{index_of_this}"
|
459
|
+
return self._disambiguated_name
|
460
|
+
|
461
|
+
def __str__(self):
|
462
|
+
"""When cast to string, convert to the name (extension) of the format"""
|
463
|
+
return self.name
|
464
|
+
|
465
|
+
def __int__(self):
|
466
|
+
"""When cast to int, return the ID of the format"""
|
467
|
+
return self.id
|
430
468
|
|
431
469
|
|
432
470
|
@dataclass
|
@@ -530,7 +568,6 @@ class ConversionsTable:
|
|
530
568
|
Raises
|
531
569
|
------
|
532
570
|
FileConverterDatabaseException
|
533
|
-
_description_
|
534
571
|
"""
|
535
572
|
|
536
573
|
self.parent = parent
|
@@ -543,8 +580,8 @@ class ConversionsTable:
|
|
543
580
|
num_converters = len(parent.converters)
|
544
581
|
num_formats = len(parent.formats)
|
545
582
|
|
546
|
-
self.
|
547
|
-
|
583
|
+
self.table = [[[0 for k in range(num_formats+1)] for j in range(num_formats+1)]
|
584
|
+
for i in range(num_converters+1)]
|
548
585
|
|
549
586
|
for possible_conversion in l_converts_to:
|
550
587
|
|
@@ -556,22 +593,22 @@ class ConversionsTable:
|
|
556
593
|
raise FileConverterDatabaseException(
|
557
594
|
f"Malformed 'converts_to' entry in database: {possible_conversion}")
|
558
595
|
|
559
|
-
self.
|
596
|
+
self.table[conv_id][in_id][out_id] = 1
|
560
597
|
|
561
598
|
def get_conversion_quality(self,
|
562
599
|
converter_name: str,
|
563
|
-
in_format: str,
|
564
|
-
out_format: str) -> ConversionQualityInfo | None:
|
600
|
+
in_format: str | int,
|
601
|
+
out_format: str | int) -> ConversionQualityInfo | None:
|
565
602
|
"""Get an indication of the quality of a conversion from one format to another, or if it's not possible
|
566
603
|
|
567
604
|
Parameters
|
568
605
|
----------
|
569
606
|
converter_name : str
|
570
607
|
The name of the converter
|
571
|
-
in_format : str
|
572
|
-
The extension of the input file format
|
573
|
-
out_format : str
|
574
|
-
The extension of the output file format
|
608
|
+
in_format : str | int
|
609
|
+
The extension or ID of the input file format
|
610
|
+
out_format : str | int
|
611
|
+
The extension or ID of the output file format
|
575
612
|
|
576
613
|
Returns
|
577
614
|
-------
|
@@ -580,12 +617,20 @@ class ConversionsTable:
|
|
580
617
|
`ConversionQualityInfo` object with info on the conversion
|
581
618
|
"""
|
582
619
|
|
583
|
-
|
584
|
-
|
585
|
-
|
620
|
+
# Check if this converter deals with ambiguous formats, so we know if we need to be strict about getting format
|
621
|
+
# info
|
622
|
+
if D_REGISTERED_CONVERTERS[converter_name].supports_ambiguous_extensions:
|
623
|
+
which_format = None
|
624
|
+
else:
|
625
|
+
which_format = 0
|
626
|
+
|
627
|
+
# Get info about the converter and formats
|
628
|
+
conv_id = self.parent.get_converter_info(converter_name).id
|
629
|
+
in_info = self.parent.get_format_info(in_format, which_format)
|
630
|
+
out_info: int = self.parent.get_format_info(out_format, which_format)
|
586
631
|
|
587
632
|
# First check if the conversion is possible
|
588
|
-
success_flag = self.
|
633
|
+
success_flag = self.table[conv_id][in_info.id][out_info.id]
|
589
634
|
if not success_flag:
|
590
635
|
return None
|
591
636
|
|
@@ -643,40 +688,50 @@ class ConversionsTable:
|
|
643
688
|
details=details,
|
644
689
|
d_prop_conversion_info=d_prop_conversion_info)
|
645
690
|
|
646
|
-
def
|
647
|
-
|
648
|
-
|
649
|
-
"""Get a list of converters which can perform a conversion from one format to another
|
650
|
-
|
691
|
+
def get_possible_conversions(self,
|
692
|
+
in_format: str | int,
|
693
|
+
out_format: str | int) -> list[tuple[str, FormatInfo, FormatInfo]]:
|
694
|
+
"""Get a list of converters which can perform a conversion from one format to another, disambiguating in the
|
695
|
+
case of ambiguous formats and providing IDs for input/output formats for possible conversions
|
651
696
|
|
652
697
|
Parameters
|
653
698
|
----------
|
654
|
-
in_format : str
|
655
|
-
The extension of the input file format
|
656
|
-
out_format : str
|
657
|
-
The extension of the output file format
|
699
|
+
in_format : str | int
|
700
|
+
The extension or ID of the input file format
|
701
|
+
out_format : str | int
|
702
|
+
The extension or ID of the output file format
|
658
703
|
|
659
704
|
Returns
|
660
705
|
-------
|
661
|
-
list[tuple[str,
|
662
|
-
A list of tuples, where each tuple's first item is the name of a converter which can perform
|
663
|
-
conversion,
|
706
|
+
list[tuple[str, FormatInfo, FormatInfo]]
|
707
|
+
A list of tuples, where each tuple's first item is the name of a converter which can perform a matching
|
708
|
+
conversion, the second is the info of the input format for this conversion, and the third is the info of the
|
709
|
+
output format
|
664
710
|
"""
|
665
|
-
|
666
|
-
|
711
|
+
l_in_format_infos: list[FormatInfo] = self.parent.get_format_info(in_format, which="all")
|
712
|
+
l_out_format_infos: list[FormatInfo] = self.parent.get_format_info(out_format, which="all")
|
667
713
|
|
668
|
-
#
|
669
|
-
|
714
|
+
# Start a list of all possible conversions
|
715
|
+
l_possible_conversions = []
|
670
716
|
|
671
|
-
#
|
672
|
-
|
673
|
-
|
717
|
+
# Iterate over all possible combinations of input and output formats
|
718
|
+
for in_format_info, out_format_info in product(l_in_format_infos, l_out_format_infos):
|
719
|
+
|
720
|
+
# Slice the table to get a list of the success for this conversion for each converter
|
721
|
+
l_converter_success = [x[in_format_info.id][out_format_info.id] for x in self.table]
|
722
|
+
|
723
|
+
# Filter for possible conversions and get the converter name and degree-of-success string
|
724
|
+
# for each possible conversion
|
725
|
+
l_converter_names = [self.parent.get_converter_info(converter_id).name
|
674
726
|
for converter_id, possible_flag
|
675
727
|
in enumerate(l_converter_success) if possible_flag > 0]
|
676
728
|
|
677
|
-
|
729
|
+
for converter_name in l_converter_names:
|
730
|
+
l_possible_conversions.append((converter_name, in_format_info, out_format_info))
|
731
|
+
|
732
|
+
return l_possible_conversions
|
678
733
|
|
679
|
-
def get_possible_formats(self, converter_name: str) -> tuple[list[
|
734
|
+
def get_possible_formats(self, converter_name: str) -> tuple[list[FormatInfo], list[FormatInfo]]:
|
680
735
|
"""Get a list of input and output formats that a given converter supports
|
681
736
|
|
682
737
|
Parameters
|
@@ -686,11 +741,11 @@ class ConversionsTable:
|
|
686
741
|
|
687
742
|
Returns
|
688
743
|
-------
|
689
|
-
tuple[list[
|
744
|
+
tuple[list[FormatInfo], list[FormatInfo]]
|
690
745
|
A tuple of a list of the supported input formats and a list of the supported output formats
|
691
746
|
"""
|
692
747
|
conv_id: int = self.parent.get_converter_info(converter_name).id
|
693
|
-
ll_in_out_format_success = self.
|
748
|
+
ll_in_out_format_success = self.table[conv_id]
|
694
749
|
|
695
750
|
# Filter for possible input formats by checking if at least one output format for each has a degree of success
|
696
751
|
# index greater than 0, and stored the filtered lists where the input format is possible so we only need to
|
@@ -707,8 +762,8 @@ class ConversionsTable:
|
|
707
762
|
sum([x[j] for x in ll_filtered_in_out_format_success]) > 0]
|
708
763
|
|
709
764
|
# Get the name for each format ID, and return lists of the names
|
710
|
-
return ([self.parent.get_format_info(x)
|
711
|
-
[self.parent.get_format_info(x)
|
765
|
+
return ([self.parent.get_format_info(x) for x in l_possible_in_format_ids],
|
766
|
+
[self.parent.get_format_info(x) for x in l_possible_out_format_ids])
|
712
767
|
|
713
768
|
|
714
769
|
class DataConversionDatabase:
|
@@ -774,37 +829,12 @@ class DataConversionDatabase:
|
|
774
829
|
return self._l_converter_info
|
775
830
|
|
776
831
|
@property
|
777
|
-
def d_format_info(self) -> dict[str, FormatInfo]:
|
832
|
+
def d_format_info(self) -> dict[str, list[FormatInfo]]:
|
778
833
|
"""Generate the format info dict when needed
|
779
834
|
"""
|
780
835
|
if self._d_format_info is None:
|
781
|
-
self.
|
782
|
-
|
783
|
-
for d_single_format_info in self.formats:
|
784
|
-
name: str = d_single_format_info[DB_FORMAT_EXT_KEY]
|
785
|
-
|
786
|
-
format_info = FormatInfo(name=name,
|
787
|
-
parent=self,
|
788
|
-
d_single_format_info=d_single_format_info)
|
789
|
-
|
790
|
-
if name in self._d_format_info:
|
791
|
-
logger.debug(f"File extension '{name}' appears more than once in the database. Duplicates will use "
|
792
|
-
"a key appended with an index")
|
793
|
-
loop_concluded = False
|
794
|
-
for i in range(97):
|
795
|
-
test_name = f"{name}-{i+2}"
|
796
|
-
if test_name in self._d_format_info:
|
797
|
-
continue
|
798
|
-
else:
|
799
|
-
self._d_format_info[test_name] = format_info
|
800
|
-
loop_concluded = True
|
801
|
-
break
|
802
|
-
if not loop_concluded:
|
803
|
-
logger.warning("Loop counter exceeded when searching for valid new name for file extension "
|
804
|
-
f"'{name}'. New entry will not be added to the database to avoid possibility of "
|
805
|
-
"an infinite loop")
|
806
|
-
else:
|
807
|
-
self._d_format_info[name] = format_info
|
836
|
+
self._init_formats_and_conversions()
|
837
|
+
|
808
838
|
return self._d_format_info
|
809
839
|
|
810
840
|
@property
|
@@ -812,13 +842,7 @@ class DataConversionDatabase:
|
|
812
842
|
"""Generate the format info list (indexed by ID) when needed
|
813
843
|
"""
|
814
844
|
if self._l_format_info is None:
|
815
|
-
|
816
|
-
max_id: int = max([x[DB_ID_KEY] for x in self.formats])
|
817
|
-
self._l_format_info: list[FormatInfo | None] = [None] * (max_id+1)
|
818
|
-
|
819
|
-
# Fill the list with all formats in the dict
|
820
|
-
for single_format_info in self.d_format_info.values():
|
821
|
-
self._l_format_info[single_format_info.id] = single_format_info
|
845
|
+
self._init_formats_and_conversions()
|
822
846
|
|
823
847
|
return self._l_format_info
|
824
848
|
|
@@ -826,11 +850,74 @@ class DataConversionDatabase:
|
|
826
850
|
def conversions_table(self) -> ConversionsTable:
|
827
851
|
"""Generates the conversions table when needed
|
828
852
|
"""
|
853
|
+
|
829
854
|
if self._conversions_table is None:
|
830
|
-
self.
|
831
|
-
|
855
|
+
self._init_formats_and_conversions()
|
856
|
+
|
832
857
|
return self._conversions_table
|
833
858
|
|
859
|
+
def _init_formats_and_conversions(self):
|
860
|
+
"""Initializes the format list and dict and the conversions table"""
|
861
|
+
|
862
|
+
# Start by initializing the list of conversions
|
863
|
+
|
864
|
+
# Pre-size a list based on the maximum ID plus 1 (since IDs are 1-indexed)
|
865
|
+
max_id: int = max([x[DB_ID_KEY] for x in self.formats])
|
866
|
+
self._l_format_info: list[FormatInfo | None] = [None] * (max_id+1)
|
867
|
+
|
868
|
+
for d_single_format_info in self.formats:
|
869
|
+
name: str = d_single_format_info[DB_FORMAT_EXT_KEY]
|
870
|
+
|
871
|
+
format_info = FormatInfo(name=name,
|
872
|
+
parent=self,
|
873
|
+
d_single_format_info=d_single_format_info)
|
874
|
+
|
875
|
+
self._l_format_info[format_info.id] = format_info
|
876
|
+
|
877
|
+
# Initialize the conversions table now
|
878
|
+
self._conversions_table = ConversionsTable(l_converts_to=self.converts_to,
|
879
|
+
parent=self)
|
880
|
+
|
881
|
+
# Use the conversions table to prune any formats which have no valid conversions
|
882
|
+
|
883
|
+
# Get a slice of the table which only includes supported converters
|
884
|
+
l_supported_converter_ids = [self.get_converter_info(x).id for x in D_SUPPORTED_CONVERTERS]
|
885
|
+
supported_table = [self._conversions_table.table[x] for x in l_supported_converter_ids]
|
886
|
+
|
887
|
+
for format_id, format_info in enumerate(self._l_format_info):
|
888
|
+
if not format_info:
|
889
|
+
continue
|
890
|
+
|
891
|
+
# Check if the format is supported as the input format for any conversion
|
892
|
+
ll_possible_from_conversions = [x[format_id] for x in supported_table]
|
893
|
+
if sum([sum(x) for x in ll_possible_from_conversions]) > 0:
|
894
|
+
continue
|
895
|
+
|
896
|
+
# Check if the format is supported as the output format for any conversion
|
897
|
+
ll_possible_to_conversions = [[y[format_id] for y in x] for x in supported_table]
|
898
|
+
if sum([sum(x) for x in ll_possible_to_conversions]) > 0:
|
899
|
+
continue
|
900
|
+
|
901
|
+
# If we get here, the format isn't supported for any conversions, so remove it from our list
|
902
|
+
self._l_format_info[format_id] = None
|
903
|
+
|
904
|
+
# Now create the formats dict, with only the pruned list of formats
|
905
|
+
self._d_format_info: dict[str, list[FormatInfo]] = {}
|
906
|
+
|
907
|
+
for format_info in self.l_format_info:
|
908
|
+
|
909
|
+
if not format_info:
|
910
|
+
continue
|
911
|
+
|
912
|
+
name = format_info.name
|
913
|
+
|
914
|
+
# Each name may correspond to multiple formats, so we use a list for each entry to list all possible
|
915
|
+
# formats for each name
|
916
|
+
if name not in self._d_format_info:
|
917
|
+
self._d_format_info[name] = []
|
918
|
+
|
919
|
+
self._d_format_info[name].append(format_info)
|
920
|
+
|
834
921
|
def get_converter_info(self, converter_name_or_id: str | int) -> ConverterInfo:
|
835
922
|
"""Get a converter's info from either its name or ID
|
836
923
|
"""
|
@@ -838,7 +925,8 @@ class DataConversionDatabase:
|
|
838
925
|
try:
|
839
926
|
return self.d_converter_info[converter_name_or_id]
|
840
927
|
except KeyError:
|
841
|
-
raise FileConverterDatabaseException(f"Converter name '{converter_name_or_id}' not recognised"
|
928
|
+
raise FileConverterDatabaseException(f"Converter name '{converter_name_or_id}' not recognised",
|
929
|
+
help=True)
|
842
930
|
elif isinstance(converter_name_or_id, int):
|
843
931
|
return self.l_converter_info[converter_name_or_id]
|
844
932
|
else:
|
@@ -846,20 +934,97 @@ class DataConversionDatabase:
|
|
846
934
|
f" of type '{type(converter_name_or_id)}'. Type must be `str` or "
|
847
935
|
"`int`")
|
848
936
|
|
849
|
-
def get_format_info(self,
|
850
|
-
|
937
|
+
def get_format_info(self,
|
938
|
+
format_name_or_id: str | int | FormatInfo,
|
939
|
+
which: int | Literal["all"] | None = None) -> FormatInfo | list[FormatInfo]:
|
940
|
+
"""Gets the information on a given file format stored in the database
|
941
|
+
|
942
|
+
Parameters
|
943
|
+
----------
|
944
|
+
format_name_or_id : str | int | FormatInfo
|
945
|
+
The name (extension) of the format, or its ID. In the case of ambiguous extensions which could apply to
|
946
|
+
multiple formats, the ID must be used here or a FileConverterDatabaseException will be raised. This also
|
947
|
+
allows passing a FormatInfo to this, in which case that object will be silently returned, to allow
|
948
|
+
normalising the input to always be a FormatInfo when output from this
|
949
|
+
which : int | None
|
950
|
+
In the case that an extension string is provided which turns out to be ambiguous, which of the listed
|
951
|
+
possibilities to use from the zero-indexed list. Default None, which raises an exception for an ambiguous
|
952
|
+
format. 0 may be used to select the first in the database, which is often a good default choice. The literal
|
953
|
+
string "all" may be used to request all possibilites, in which case this method will return a list (even if
|
954
|
+
there are zero or one possibilities)
|
955
|
+
|
956
|
+
Returns
|
957
|
+
-------
|
958
|
+
FormatInfo | list[FormatInfo]
|
851
959
|
"""
|
960
|
+
|
961
|
+
if which == "all":
|
962
|
+
return_as_list = True
|
963
|
+
else:
|
964
|
+
return_as_list = False
|
965
|
+
|
852
966
|
if isinstance(format_name_or_id, str):
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
967
|
+
# Silently strip leading period
|
968
|
+
if format_name_or_id.startswith("."):
|
969
|
+
format_name_or_id = format_name_or_id[1:]
|
970
|
+
|
971
|
+
# Check for a hyphen in the format, which indicates a preference from the user as to which, overriding the
|
972
|
+
# `which` kwarg
|
973
|
+
if "-" in format_name_or_id:
|
974
|
+
l_name_segments = format_name_or_id.split("-")
|
975
|
+
if len(l_name_segments) > 2:
|
976
|
+
raise FileConverterDatabaseException(f"Format name '{format_name_or_id} is improperly formatted - "
|
977
|
+
"It may contain at most one hyphen, separating the extension "
|
978
|
+
"from an index indicating which of the formats with that "
|
979
|
+
"extension to use, e.g. 'pdb-0', 'pdb-1', etc.",
|
980
|
+
help=True)
|
981
|
+
format_name_or_id = l_name_segments[0]
|
982
|
+
which = int(l_name_segments[1])
|
983
|
+
|
984
|
+
l_possible_format_info = self.d_format_info.get(format_name_or_id, [])
|
985
|
+
|
986
|
+
if which == "all":
|
987
|
+
return l_possible_format_info
|
988
|
+
|
989
|
+
elif len(l_possible_format_info) == 1:
|
990
|
+
format_info = l_possible_format_info[0]
|
991
|
+
|
992
|
+
elif len(l_possible_format_info) == 0:
|
993
|
+
raise FileConverterDatabaseException(f"Format name '{format_name_or_id}' not recognised",
|
994
|
+
help=True)
|
995
|
+
|
996
|
+
elif which is not None and which < len(l_possible_format_info):
|
997
|
+
format_info = l_possible_format_info[which]
|
998
|
+
|
999
|
+
else:
|
1000
|
+
msg = (f"Extension '{format_name_or_id}' is ambiguous and must be defined by ID. Possible formats "
|
1001
|
+
"and their IDs are:")
|
1002
|
+
for possible_format_info in l_possible_format_info:
|
1003
|
+
msg += (f"\n{possible_format_info.id}: {possible_format_info.disambiguated_name} "
|
1004
|
+
f"({possible_format_info.note})")
|
1005
|
+
raise FileConverterDatabaseException(msg, help=True)
|
1006
|
+
|
857
1007
|
elif isinstance(format_name_or_id, int):
|
858
|
-
|
1008
|
+
try:
|
1009
|
+
format_info = self.l_format_info[format_name_or_id]
|
1010
|
+
except IndexError:
|
1011
|
+
if return_as_list:
|
1012
|
+
return []
|
1013
|
+
raise FileConverterDatabaseException(f"Format ID '{format_name_or_id}' not recognised",
|
1014
|
+
help=True)
|
1015
|
+
|
1016
|
+
elif isinstance(format_name_or_id, FormatInfo):
|
1017
|
+
# Silently return the FormatInfo if it was used as a key here
|
1018
|
+
format_info = format_name_or_id
|
1019
|
+
|
859
1020
|
else:
|
860
1021
|
raise FileConverterDatabaseException(f"Invalid key passed to `get_format_info`: '{format_name_or_id}'"
|
861
1022
|
f" of type '{type(format_name_or_id)}'. Type must be `str` or "
|
862
1023
|
"`int`")
|
1024
|
+
if return_as_list:
|
1025
|
+
return [format_info]
|
1026
|
+
|
1027
|
+
return format_info
|
863
1028
|
|
864
1029
|
|
865
1030
|
# The database will be loaded on demand when `get_database()` is called
|
@@ -919,35 +1084,45 @@ def get_converter_info(name: str) -> ConverterInfo:
|
|
919
1084
|
return get_database().d_converter_info[name]
|
920
1085
|
|
921
1086
|
|
922
|
-
def get_format_info(
|
1087
|
+
def get_format_info(format_name_or_id: str | int | FormatInfo,
|
1088
|
+
which: int | Literal["all"] | None = None) -> FormatInfo | list[FormatInfo]:
|
923
1089
|
"""Gets the information on a given file format stored in the database
|
924
1090
|
|
925
1091
|
Parameters
|
926
1092
|
----------
|
927
|
-
|
928
|
-
The name (extension) of the
|
1093
|
+
format_name_or_id : str | int | FormatInfo
|
1094
|
+
The name (extension) of the format, or its ID. In the case of ambiguous extensions which could apply to multiple
|
1095
|
+
formats, the ID must be used here or a FileConverterDatabaseException will be raised. This also allows passing a
|
1096
|
+
FormatInfo to this, in which case that object will be silently returned, to allow normalising the input to
|
1097
|
+
always be a FormatInfo when output from this
|
1098
|
+
which : int | None
|
1099
|
+
In the case that an extension string is provided which turns out to be ambiguous, which of the listed
|
1100
|
+
possibilities to use from the zero-indexed list. Default None, which raises an exception for an ambiguous
|
1101
|
+
format. 0 may be used to select the first in the database, which is often a good default choice. The literal
|
1102
|
+
string "all" may be used to request all possibilites, in which case this method will return a list (even if
|
1103
|
+
there are zero or one possibilities)
|
929
1104
|
|
930
1105
|
Returns
|
931
1106
|
-------
|
932
|
-
FormatInfo
|
1107
|
+
FormatInfo | list[FormatInfo]
|
933
1108
|
"""
|
934
1109
|
|
935
|
-
return get_database().
|
1110
|
+
return get_database().get_format_info(format_name_or_id, which)
|
936
1111
|
|
937
1112
|
|
938
1113
|
def get_conversion_quality(converter_name: str,
|
939
|
-
in_format: str,
|
940
|
-
out_format: str) -> ConversionQualityInfo | None:
|
1114
|
+
in_format: str | int,
|
1115
|
+
out_format: str | int) -> ConversionQualityInfo | None:
|
941
1116
|
"""Get an indication of the quality of a conversion from one format to another, or if it's not possible
|
942
1117
|
|
943
1118
|
Parameters
|
944
1119
|
----------
|
945
1120
|
converter_name : str
|
946
1121
|
The name of the converter
|
947
|
-
in_format : str
|
948
|
-
The extension of the input file format
|
949
|
-
out_format : str
|
950
|
-
The extension of the output file format
|
1122
|
+
in_format : str | int
|
1123
|
+
The extension or ID of the input file format
|
1124
|
+
out_format : str | int
|
1125
|
+
The extension or ID of the output file format
|
951
1126
|
|
952
1127
|
Returns
|
953
1128
|
-------
|
@@ -961,30 +1136,77 @@ def get_conversion_quality(converter_name: str,
|
|
961
1136
|
out_format=out_format)
|
962
1137
|
|
963
1138
|
|
964
|
-
def
|
965
|
-
|
966
|
-
"""Get a list of converters which can perform a conversion from one format to another and
|
967
|
-
|
1139
|
+
def get_possible_conversions(in_format: str | int,
|
1140
|
+
out_format: str | int) -> list[tuple[str, FormatInfo, FormatInfo]]:
|
1141
|
+
"""Get a list of converters which can perform a conversion from one format to another and disambiguate in the case
|
1142
|
+
of ambiguous input/output formats
|
968
1143
|
|
969
1144
|
Parameters
|
970
1145
|
----------
|
971
|
-
in_format : str
|
972
|
-
The extension of the input file format
|
973
|
-
out_format : str
|
974
|
-
The extension of the output file format
|
1146
|
+
in_format : str | int
|
1147
|
+
The extension or ID of the input file format
|
1148
|
+
out_format : str | int
|
1149
|
+
The extension or ID of the output file format
|
975
1150
|
|
976
1151
|
Returns
|
977
1152
|
-------
|
978
|
-
list[tuple[str,
|
979
|
-
A list of tuples, where each tuple's first item is the name of a converter which can perform
|
980
|
-
conversion,
|
1153
|
+
list[tuple[str, FormatInfo, FormatInfo]]
|
1154
|
+
A list of tuples, where each tuple's first item is the name of a converter which can perform a matching
|
1155
|
+
conversion, the second is the info of the input format for this conversion, and the third is the info of the
|
1156
|
+
output format
|
981
1157
|
"""
|
982
1158
|
|
983
|
-
return get_database().conversions_table.
|
984
|
-
|
1159
|
+
return get_database().conversions_table.get_possible_conversions(in_format=in_format,
|
1160
|
+
out_format=out_format)
|
1161
|
+
|
1162
|
+
|
1163
|
+
def disambiguate_formats(converter_name: str,
|
1164
|
+
in_format: str | int | FormatInfo,
|
1165
|
+
out_format: str | int | FormatInfo) -> tuple[FormatInfo, FormatInfo]:
|
1166
|
+
"""Try to disambiguate formats by seeing if there's only one possible conversion between formats matching those
|
1167
|
+
provided.
|
985
1168
|
|
1169
|
+
Parameters
|
1170
|
+
----------
|
1171
|
+
converter_name : str
|
1172
|
+
The name of the converter
|
1173
|
+
in_format : str | int
|
1174
|
+
The extension or ID of the input file format
|
1175
|
+
out_format : str | int
|
1176
|
+
The extension or ID of the output file format
|
1177
|
+
|
1178
|
+
Returns
|
1179
|
+
-------
|
1180
|
+
tuple[FormatInfo, FormatInfo]
|
1181
|
+
The input and output format for this conversion, if only one combination is possible
|
1182
|
+
|
1183
|
+
Raises
|
1184
|
+
------
|
1185
|
+
FileConverterDatabaseException
|
1186
|
+
If more than one format combination is possible for this conversion, or no conversion is possible
|
1187
|
+
"""
|
986
1188
|
|
987
|
-
|
1189
|
+
# Get all possible conversions, and see if we only have one for this converter
|
1190
|
+
l_possible_conversions = [x for x in get_possible_conversions(in_format, out_format)
|
1191
|
+
if x[0] == converter_name]
|
1192
|
+
|
1193
|
+
if len(l_possible_conversions) == 1:
|
1194
|
+
return l_possible_conversions[0][1], l_possible_conversions[0][2]
|
1195
|
+
elif len(l_possible_conversions) == 0:
|
1196
|
+
raise FileConverterDatabaseException(f"Conversion from {in_format} to {out_format} with converter "
|
1197
|
+
f"{converter_name} is not supported", help=True)
|
1198
|
+
else:
|
1199
|
+
msg = (f"Conversion from {in_format} to {out_format} with converter {converter_name} is ambiguous.\n"
|
1200
|
+
"Possible matching conversions are:\n")
|
1201
|
+
for _, possible_in_format, possible_out_format in l_possible_conversions:
|
1202
|
+
msg += (f"{possible_in_format.disambiguated_name} ({possible_in_format.note}) to "
|
1203
|
+
f"{possible_out_format.disambiguated_name} ({possible_out_format.note})\n")
|
1204
|
+
# Trim the final newline from the message
|
1205
|
+
msg = msg[:-1]
|
1206
|
+
raise FileConverterDatabaseException(msg, help=True)
|
1207
|
+
|
1208
|
+
|
1209
|
+
def get_possible_formats(converter_name: str) -> tuple[list[FormatInfo], list[FormatInfo]]:
|
988
1210
|
"""Get a list of input and output formats that a given converter supports
|
989
1211
|
|
990
1212
|
Parameters
|
@@ -994,7 +1216,7 @@ def get_possible_formats(converter_name: str) -> tuple[list[str], list[str]]:
|
|
994
1216
|
|
995
1217
|
Returns
|
996
1218
|
-------
|
997
|
-
tuple[list[
|
1219
|
+
tuple[list[FormatInfo], list[FormatInfo]]
|
998
1220
|
A tuple of a list of the supported input formats and a list of the supported output formats
|
999
1221
|
"""
|
1000
1222
|
return get_database().conversions_table.get_possible_formats(converter_name=converter_name)
|
@@ -1009,7 +1231,7 @@ def _find_arg(tl_args: tuple[list[FlagInfo], list[OptionInfo]],
|
|
1009
1231
|
if len(l_found) > 0:
|
1010
1232
|
return l_found[0]
|
1011
1233
|
# If we get here, it wasn't found in either list
|
1012
|
-
raise FileConverterDatabaseException(f"Argument {arg} was not found in the list of allowed arguments for this "
|
1234
|
+
raise FileConverterDatabaseException(f"Argument '{arg}' was not found in the list of allowed arguments for this "
|
1013
1235
|
"conversion")
|
1014
1236
|
|
1015
1237
|
|