psdi-data-conversion 0.0.36__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.
@@ -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._table = [[[0 for k in range(num_formats+1)] for j in range(num_formats+1)]
547
- for i in range(num_converters+1)]
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._table[conv_id][in_id][out_id] = 1
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
- conv_id: int = self.parent.get_converter_info(converter_name).id
584
- in_info = self.parent.get_format_info(in_format)
585
- out_info: int = self.parent.get_format_info(out_format)
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._table[conv_id][in_info.id][out_info.id]
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 get_possible_converters(self,
647
- in_format: str,
648
- out_format: str) -> list[str]:
649
- """Get a list of converters which can perform a conversion from one format to another and the degree of success
650
- with each of these converters
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, str]]
662
- A list of tuples, where each tuple's first item is the name of a converter which can perform this
663
- conversion, and the second item is the degree of success for the 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
- in_id: int = self.parent.get_format_info(in_format).id
666
- out_id: int = self.parent.get_format_info(out_format).id
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
- # Slice the table to get a list of the success for this conversion for each converter
669
- l_converter_success = [x[in_id][out_id] for x in self._table]
714
+ # Start a list of all possible conversions
715
+ l_possible_conversions = []
670
716
 
671
- # Filter for possible conversions and get the converter name and degree-of-success string
672
- # for each possible conversion
673
- l_possible_converters = [self.parent.get_converter_info(converter_id).name
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
- return l_possible_converters
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[str], list[str]]:
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[str], list[str]]
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._table[conv_id]
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).name for x in l_possible_in_format_ids],
711
- [self.parent.get_format_info(x).name for x in l_possible_out_format_ids])
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._d_format_info: dict[str, FormatInfo] = {}
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
- # Pre-size a list based on the maximum ID plus 1 (since IDs are 1-indexed)
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._conversions_table = ConversionsTable(l_converts_to=self.converts_to,
831
- parent=self)
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, format_name_or_id: str | int) -> FormatInfo:
850
- """Get a format's ID info from either its name or ID
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
- try:
854
- return self.d_format_info[format_name_or_id]
855
- except KeyError:
856
- raise FileConverterDatabaseException(f"Format name '{format_name_or_id}' not recognised")
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
- return self.l_format_info[format_name_or_id]
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(name: str) -> FormatInfo:
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
- name : str
928
- The name (extension) of the form
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().d_format_info[name]
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 get_possible_converters(in_format: str,
965
- out_format: str) -> list[str]:
966
- """Get a list of converters which can perform a conversion from one format to another and the degree of success
967
- with each of these converters
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, str]]
979
- A list of tuples, where each tuple's first item is the name of a converter which can perform this
980
- conversion, and the second item is the degree of success for the 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.get_possible_converters(in_format=in_format,
984
- out_format=out_format)
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
- def get_possible_formats(converter_name: str) -> tuple[list[str], list[str]]:
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[str], list[str]]
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