msreport 0.0.29__py3-none-any.whl → 0.0.30__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.
msreport/__init__.py CHANGED
@@ -8,4 +8,4 @@ from msreport.fasta import import_protein_database
8
8
  from msreport.qtable import Qtable
9
9
  from msreport.reader import FragPipeReader, MaxQuantReader, SpectronautReader
10
10
 
11
- __version__ = "0.0.29"
11
+ __version__ = "0.0.30"
@@ -204,7 +204,8 @@ def experiment_ratios(
204
204
  mask = np.all([(qtable.data[f"Events {exp}"] > 0) for exp in experiments], axis=0)
205
205
  if exclude_invalid:
206
206
  mask = mask & qtable["Valid"]
207
- experiment_data = experiment_data[mask]
207
+ # Use `mask.to_numpy` to solve issue with different indices of mask and dataframe
208
+ experiment_data = experiment_data[mask.to_numpy()]
208
209
  pseudo_reference = np.nanmean(experiment_data, axis=1)
209
210
  ratio_data = experiment_data.subtract(pseudo_reference, axis=0)
210
211
 
msreport/qtable.py CHANGED
@@ -27,13 +27,11 @@ class Qtable:
27
27
  design: A pandas.DataFrame describing the experimental design.
28
28
  """
29
29
 
30
- _default_id_column = "Representative protein"
31
-
32
30
  def __init__(
33
31
  self,
34
32
  data: pd.DataFrame,
35
- design: Optional[pd.DataFrame] = None,
36
- id_column: str = "Representative protein",
33
+ design: pd.DataFrame,
34
+ id_column: str,
37
35
  ):
38
36
  """Initializes the Qtable.
39
37
 
@@ -42,12 +40,13 @@ class Qtable:
42
40
 
43
41
  Args:
44
42
  data: A dataframe containing quantitative proteomics data in a wide format.
43
+ The index of the dataframe must contain unique values.
45
44
  design: A dataframe describing the experimental design that must at least
46
45
  contain the columns "Sample" and "Experiment". The "Sample" entries
47
46
  should correspond to the Sample names present in the quantitative
48
47
  columns of the data.
49
48
  id_column: The name of the column that contains the unique identifiers for
50
- the entries in the data table. Default is "Representative protein".
49
+ the entries in the data table.
51
50
 
52
51
  Raises:
53
52
  KeyError: If the specified id_column is not found in data.
@@ -76,8 +75,7 @@ class Qtable:
76
75
  self._id_column = id_column
77
76
  if "Valid" not in self.data.columns:
78
77
  self.data["Valid"] = True
79
- if design is not None:
80
- self.add_design(design)
78
+ self.add_design(design)
81
79
 
82
80
  self._expression_columns: list[str] = []
83
81
  self._expression_features: list[str] = []
@@ -438,6 +436,11 @@ class Qtable:
438
436
 
439
437
  Returns:
440
438
  An instance of Qtable loaded from the specified files.
439
+
440
+ Raises:
441
+ ValueError: If the loaded config file does not contain the
442
+ "Unique ID column" key. This is due to the qtable being saved with a
443
+ version of msreport <= 0.0.27.
441
444
  """
442
445
  filepaths = _get_qtable_export_filepaths(directory, basename)
443
446
  with open(filepaths["config"]) as openfile:
@@ -458,13 +461,20 @@ class Qtable:
458
461
  filepaths["design"], sep="\t", index_col=0, keep_default_na=True
459
462
  )
460
463
 
461
- qtable = Qtable(data, design)
464
+ if "Unique ID column" not in config_data:
465
+ # Mention that the qtable was likely saved with a version of msreport <= 0.0.27
466
+ raise ValueError(
467
+ "The qtable config file does not contain the 'Unique ID column' key. "
468
+ "This is likely due to the qtable being saved with a version of "
469
+ "msreport <= 0.0.27."
470
+ )
471
+ id_column = config_data["Unique ID column"]
472
+
473
+ qtable = Qtable(data, design, id_column)
462
474
  qtable._expression_columns = config_data["Expression columns"]
463
475
  qtable._expression_features = config_data["Expression features"]
464
476
  qtable._expression_sample_mapping = config_data["Expression sample mapping"]
465
477
  # This check is required for backwards compatibility with msreport <= 0.0.27
466
- if "Unique ID column" in config_data:
467
- qtable._id_column = config_data["Unique ID column"]
468
478
  return qtable
469
479
 
470
480
  def to_tsv(self, path: str, index: bool = False):
@@ -570,7 +580,7 @@ class Qtable:
570
580
  self._expression_sample_mapping = {}
571
581
 
572
582
  def __copy__(self) -> Qtable:
573
- new_instance = Qtable(self.data, self.design)
583
+ new_instance = Qtable(self.data, self.design, self.id_column)
574
584
  # Copy all private attributes
575
585
  for attr in dir(self):
576
586
  if (
msreport/reader.py CHANGED
@@ -541,6 +541,8 @@ class FragPipeReader(ResultReader):
541
541
  """FragPipe result reader.
542
542
 
543
543
  Methods:
544
+ import_design: Reads a "fragpipe-files.fp-manifest" file and returns a
545
+ processed design dataframe.
544
546
  import_proteins: Reads a "combined_protein.tsv" or "protein.tsv" file and
545
547
  returns a processed dataframe, conforming to the MsReport naming
546
548
  convention.
@@ -583,12 +585,19 @@ class FragPipeReader(ResultReader):
583
585
  "ions": "combined_ion.tsv",
584
586
  "ion_evidence": "ion.tsv",
585
587
  "psm_evidence": "psm.tsv",
588
+ "design": "fragpipe-files.fp-manifest",
586
589
  }
587
590
  isobar_filenames: dict[str, str] = {
588
591
  "proteins": "protein.tsv",
589
592
  "peptides": "peptide.tsv",
590
593
  "ions": "ion.tsv",
591
594
  }
595
+ sil_filenames: dict[str, str] = {
596
+ "proteins": "combined_protein_label_quant.tsv",
597
+ "peptides": "combined_modified_peptide_label_quant.tsv",
598
+ "ions": "combined_ion_label_quant.tsv",
599
+ }
600
+
592
601
  protected_columns: list[str] = []
593
602
  sample_column_tags: list[str] = [
594
603
  "Spectral Count",
@@ -609,6 +618,7 @@ class FragPipeReader(ResultReader):
609
618
  "Modified Sequence": "Modified sequence", # Modified peptide and ion
610
619
  "Start": "Start position", # Peptide and ion
611
620
  "End": "End position", # Peptide and ion
621
+ "Mapped Proteins": "Mapped proteins", # All PSM, ion, and peptide tables
612
622
  "Combined Total Peptides": "Total peptides", # From LFQ
613
623
  "Total Peptides": "Total peptides", # From TMT
614
624
  "Description": "Protein name",
@@ -638,7 +648,11 @@ class FragPipeReader(ResultReader):
638
648
  protein_info_tags: list[str] = []
639
649
 
640
650
  def __init__(
641
- self, directory: str, isobar: bool = False, contaminant_tag: str = "contam_"
651
+ self,
652
+ directory: str,
653
+ isobar: bool = False,
654
+ sil: bool = False,
655
+ contaminant_tag: str = "contam_",
642
656
  ) -> None:
643
657
  """Initializes the FragPipeReader.
644
658
 
@@ -646,16 +660,69 @@ class FragPipeReader(ResultReader):
646
660
  directory: Location of the FragPipe result folder
647
661
  isobar: Set to True if quantification strategy was TMT, iTRAQ or similar;
648
662
  default False.
663
+ sil: Set to True if the FragPipe result files are from a stable isotope
664
+ labeling experiment, such as SILAC; default False.
649
665
  contaminant_tag: Prefix of Protein ID entries to identify contaminants;
650
666
  default "contam_".
651
667
  """
668
+ if sil and isobar:
669
+ raise ValueError("Cannot set both 'isobar' and 'sil' to True.")
652
670
  self._add_data_directory(directory)
653
671
  self._isobar: bool = isobar
672
+ self._sil: bool = sil
654
673
  self._contaminant_tag: str = contaminant_tag
655
- if not isobar:
674
+ if isobar:
675
+ self.filenames = self.isobar_filenames
676
+ elif sil:
677
+ self.filenames = self.sil_filenames
678
+ else:
656
679
  self.filenames = self.default_filenames
680
+
681
+ def import_design(
682
+ self, filename: Optional[str] = None, sort: bool = False
683
+ ) -> pd.DataFrame:
684
+ """Reads a 'fp-manifest' file and returns a processed design dataframe.
685
+
686
+ Args:
687
+ filename: Allows specifying an alternative filename, otherwise the default
688
+ filename is used.
689
+ sort: If True, the design dataframe is sorted by "Experiment" and
690
+ "Replicate"; default False.
691
+
692
+ Returns:
693
+ A dataframe containing the processed design table with columns:
694
+ "Sample", "Experiment", "Replicate", "Rawfile".
695
+
696
+ Raises:
697
+ FileNotFoundError: If the specified manifest file does not exist.
698
+ """
699
+ if filename is None:
700
+ filepath = os.path.join(self.data_directory, self.filenames["design"])
657
701
  else:
658
- self.filenames = self.isobar_filenames
702
+ filepath = os.path.join(self.data_directory, filename)
703
+ if not os.path.exists(filepath):
704
+ raise FileNotFoundError(
705
+ f"File '{filepath}' does not exist. Please check the file path."
706
+ )
707
+ fp_manifest = pd.read_csv(filepath, sep="\t", header=None, dtype=str)
708
+ fp_manifest.columns = ["Path", "Experiment", "Bioreplicate", "Data type"]
709
+
710
+ design = pd.DataFrame(
711
+ {
712
+ "Sample": fp_manifest["Experiment"] + "_" + fp_manifest["Bioreplicate"],
713
+ "Experiment": fp_manifest["Experiment"],
714
+ "Replicate": fp_manifest["Bioreplicate"],
715
+ "Rawfile": fp_manifest["Path"].apply(
716
+ # Required to handle Windows and Unix style paths on either system
717
+ lambda x: x.replace("\\", "/").split("/")[-1]
718
+ ),
719
+ }
720
+ )
721
+
722
+ if sort:
723
+ design.sort_values(by=["Experiment", "Replicate"], inplace=True)
724
+ design.reset_index(drop=True, inplace=True)
725
+ return design
659
726
 
660
727
  def import_proteins(
661
728
  self,
@@ -737,6 +804,7 @@ class FragPipeReader(ResultReader):
737
804
  df = self._read_file("peptides" if filename is None else filename)
738
805
  df["Protein reported by software"] = _extract_protein_ids(df["Protein"])
739
806
  df["Representative protein"] = df["Protein reported by software"]
807
+ df["Mapped Proteins"] = self._collect_mapped_proteins(df)
740
808
  # Note that _add_protein_entries would need to be adapted for the peptide table.
741
809
  # df = self._add_protein_entries(df)
742
810
  if rename_columns:
@@ -793,6 +861,8 @@ class FragPipeReader(ResultReader):
793
861
  # 'Indistinguishable Proteins' to the ion table.
794
862
  df["Protein reported by software"] = _extract_protein_ids(df["Protein"])
795
863
  df["Representative protein"] = df["Protein reported by software"]
864
+ df["Mapped Proteins"] = self._collect_mapped_proteins(df)
865
+
796
866
  if rename_columns:
797
867
  df = self._rename_columns(df, prefix_column_tags)
798
868
  if rewrite_modifications and rename_columns:
@@ -879,6 +949,8 @@ class FragPipeReader(ResultReader):
879
949
  # 'Indistinguishable Proteins' to the ion table.
880
950
  df["Protein reported by software"] = _extract_protein_ids(df["Protein"])
881
951
  df["Representative protein"] = df["Protein reported by software"]
952
+ df["Mapped Proteins"] = self._collect_mapped_proteins(df)
953
+
882
954
  if rename_columns:
883
955
  df = self._rename_columns(df, prefix_column_tags)
884
956
  if rewrite_modifications and rename_columns:
@@ -928,23 +1000,7 @@ class FragPipeReader(ResultReader):
928
1000
 
929
1001
  df["Protein reported by software"] = _extract_protein_ids(df["Protein"])
930
1002
  df["Representative protein"] = df["Protein reported by software"]
931
- df["Mapped Proteins"] = df["Mapped Proteins"].astype(str).replace("nan", "")
932
-
933
- # FP only lists additional mapped proteins in the "Mapped Proteins" column
934
- # MsReport reports all matching proteins in the "Mapped proteins" column
935
- mapped_proteins_entries = []
936
- for protein, mapped_protein_fp in zip(
937
- df["Representative protein"], df["Mapped Proteins"], strict=True
938
- ):
939
- if mapped_protein_fp == "":
940
- mapped_proteins = [protein]
941
- else:
942
- additional_mapped_proteins = msreport.reader._extract_protein_ids(
943
- mapped_protein_fp.split(", ")
944
- )
945
- mapped_proteins = [protein] + additional_mapped_proteins
946
- mapped_proteins_entries.append(";".join(mapped_proteins))
947
- df["Mapped proteins"] = mapped_proteins_entries
1003
+ df["Mapped Proteins"] = self._collect_mapped_proteins(df)
948
1004
 
949
1005
  if rename_columns:
950
1006
  df = self._rename_columns(df, prefix_tag=True)
@@ -980,6 +1036,35 @@ class FragPipeReader(ResultReader):
980
1036
  df[key] = protein_entry_table[key]
981
1037
  return df
982
1038
 
1039
+ def _collect_mapped_proteins(self, df: pd.DataFrame) -> list[str]:
1040
+ """Generates a list of mapped proteins entries.
1041
+
1042
+ This method extracts protein IDs from the 'Representative protein' and the
1043
+ 'Mapped Proteins' column and combines them into a single string for each row,
1044
+ where multiple protein IDs are separated by semicolons.
1045
+
1046
+ Args:
1047
+ df: DataFrame containing the 'Mapped Proteins' column.
1048
+
1049
+ Returns:
1050
+ A list of mapped proteins entries.
1051
+ """
1052
+ mapped_proteins_entries = []
1053
+ for protein, mapped_protein_fp in zip(
1054
+ df["Representative protein"],
1055
+ df["Mapped Proteins"].astype(str).replace("nan", ""),
1056
+ strict=True,
1057
+ ):
1058
+ if mapped_protein_fp == "":
1059
+ mapped_proteins = [protein]
1060
+ else:
1061
+ additional_mapped_proteins = msreport.reader._extract_protein_ids(
1062
+ mapped_protein_fp.split(", ")
1063
+ )
1064
+ mapped_proteins = [protein] + additional_mapped_proteins
1065
+ mapped_proteins_entries.append(";".join(mapped_proteins))
1066
+ return mapped_proteins_entries
1067
+
983
1068
  def _collect_leading_protein_entries(self, df: pd.DataFrame) -> list[list[str]]:
984
1069
  """Generates a list of leading protein entries.
985
1070
 
@@ -995,6 +1080,9 @@ class FragPipeReader(ResultReader):
995
1080
  A list of the same length as the input dataframe. Each position contains a
996
1081
  list of leading protein entries, which a minimum of one entry.
997
1082
  """
1083
+ if self._sil: # No "Indistinguishable Proteins" columns in 'SIL' data
1084
+ return [[p] for p in df["Protein"]]
1085
+
998
1086
  leading_protein_entries = []
999
1087
  for protein_entry, indist_protein_entry in zip(
1000
1088
  df["Protein"], df["Indistinguishable Proteins"].fillna("").astype(str)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: msreport
3
- Version: 0.0.29
3
+ Version: 0.0.30
4
4
  Summary: Post processing and analysis of quantitative proteomics data
5
5
  Author-email: "David M. Hollenstein" <hollenstein.david@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -29,7 +29,7 @@ Requires-Dist: seaborn>=0.12.0
29
29
  Requires-Dist: statsmodels>=0.13.2
30
30
  Requires-Dist: typing_extensions>=4
31
31
  Provides-Extra: r
32
- Requires-Dist: rpy2!=3.5.13,>=3.5.3; extra == "r"
32
+ Requires-Dist: rpy2<3.5.13,>=3.5.3; extra == "r"
33
33
  Provides-Extra: dev
34
34
  Requires-Dist: mypy>=1.15.0; extra == "dev"
35
35
  Requires-Dist: pytest>=8.3.5; extra == "dev"
@@ -40,6 +40,7 @@ Dynamic: license-file
40
40
  # MsReport
41
41
 
42
42
  [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)
43
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.15309090.svg)](https://doi.org/10.5281/zenodo.15309090)
43
44
  ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fhollenstein%2Fmsreport%2Fmain%2Fpyproject.toml)
44
45
  [![Run tests](https://github.com/hollenstein/msreport/actions/workflows/run-tests.yml/badge.svg)](https://github.com/hollenstein/msreport/actions/workflows/run-tests.yml)
45
46
 
@@ -55,6 +56,7 @@ bottom-up mass spectrometry experiments.
55
56
  - [Additional requirements](#additional-requirements)
56
57
  - [Optional Dependencies](#optional-dependencies)
57
58
  - [Development status](#development-status)
59
+ - [How to cite](#how-to-cite)
58
60
 
59
61
  ## What is MsReport?
60
62
 
@@ -134,3 +136,9 @@ For example, the R home directory might look like this on Windows: `C:\Program F
134
136
  ## Development status
135
137
 
136
138
  MsReport is a stable and reliable library that has been used on a daily basis for over two years in the Mass Spectrometry Facility at the Max Perutz Labs and the Mass Spectrometry Facility of IMP/IMBA/GMI. While the current interface of MsReport is stable, the library is still under active development, with new features being added regularly. Please note that a major rewrite is planned, which may introduce changes to the API in the future.
139
+
140
+ ## How to cite
141
+
142
+ If you use MsReport for your research or publications, please include the following citation and consider giving the project a star on GitHub.
143
+
144
+ > Hollenstein, D. M., & Hartl, M. (2025). hollenstein/msreport: v0.0.29 (0.0.29). Zenodo. https://doi.org/10.5281/zenodo.15309090
@@ -1,4 +1,4 @@
1
- msreport/__init__.py,sha256=mwpJ3VkH0wctK3CzSQwaywF-mkOC8revi1Ra82apJ3U,339
1
+ msreport/__init__.py,sha256=ajNIgBNRP06cDKDl6tsTeDhWSlJQw922QAxoJFd8Mhs,339
2
2
  msreport/analyze.py,sha256=I1sfxvXy02AjFcfLRlvC-F_bg0J8ePKoSIU8yDWxLs0,31313
3
3
  msreport/errors.py,sha256=X9yFxMiIOCWQdxuqBGr8L7O3vRV2KElXdX1uHbFcZMk,421
4
4
  msreport/export.py,sha256=YvY3Nly5JC2CUM-JY1gydU1g2eqnennzToZfQQ5phO0,20156
@@ -7,8 +7,8 @@ msreport/impute.py,sha256=bf2Zy8VQNJ0Oh1sKn84Xp9iV5svi_Hp7iHxwRrFBwsI,10327
7
7
  msreport/isobar.py,sha256=m6NhLaKBiItIXuBhly_z2wEslxQGFC2f3-e1bzYXB78,6575
8
8
  msreport/normalize.py,sha256=K1x3DjL5Rep3t_eDIKIghMr0sAJiROnX6skHnOMPZ_k,20160
9
9
  msreport/peptidoform.py,sha256=26USj6WPrMgMIc7LttQ2n6Oq5jo1o7ayUQLR6gsRmZY,12015
10
- msreport/qtable.py,sha256=0e-TXmuiKBU6W5TL3tz06nNrjtEyT-CI9bvUq8W6qME,26768
11
- msreport/reader.py,sha256=Dqm3Ii9RKiQ61RNATXIqWzD8eOfQyPQQ7lddl-thLQA,112075
10
+ msreport/qtable.py,sha256=4bJaWac1ePDZB1q7ssINWPdciqx4BIc6tiYUx5xrCsY,27265
11
+ msreport/reader.py,sha256=ozw6QJ22aC0B3kSeb_frIIjkzGLx2yIV-1ZI9w8WffI,115638
12
12
  msreport/aggregate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  msreport/aggregate/condense.py,sha256=eIh5A3RUvXrmoFUjRXagiPl0m-ucuRwYD8kDBI7voVs,5862
14
14
  msreport/aggregate/pivot.py,sha256=rn8li-FrtOZS4oWA8COk0uV2m71GCEbNu1ALNoMuHOA,5081
@@ -21,7 +21,7 @@ msreport/helper/temp.py,sha256=jNulgDATf9sKXEFWMXAhjflciOZPAqlxg_7QZS7IkW8,3736
21
21
  msreport/plot/__init__.py,sha256=SnoQORfrjgz9SmqPZ-1J1aeVC5xu-cFfZINP4aYVCmY,1488
22
22
  msreport/plot/_partial_plots.py,sha256=tqZTSXEPuruMgVakaGR2tUQl5OrHgo2cROJ0S4cqkR0,5598
23
23
  msreport/plot/comparison.py,sha256=J8zWyQrzx7rxDLxeZQkfAlcSmLY3e_7wwPG-cGuWo2M,18564
24
- msreport/plot/distribution.py,sha256=a2Rw6HxQwGfDwRSy8dwpT7zvEQ968wYHjcVPOdXI3l8,10150
24
+ msreport/plot/distribution.py,sha256=QNFL5vG9p-vqhwEk5WcCSXa2B8u5QgySZlAQIPys0-0,10248
25
25
  msreport/plot/multivariate.py,sha256=0xzxggqbIGQYOfgiij93DTRWfG6GvvhqI9u1GNPHarY,13111
26
26
  msreport/plot/quality.py,sha256=dIo_dpdexEN_vp35WpUTt626E-QJ2qNbJmjUai_8uck,15861
27
27
  msreport/plot/style.py,sha256=67jWf4uA1ub9RJDu4xhuSoXAW0lbLj6SMP4QXQO76Pc,10591
@@ -31,8 +31,8 @@ msreport/rinterface/__init__.py,sha256=Zs6STvbDqaVZVPRM6iU0kKjq0TWz_2p2ChvNAveRd
31
31
  msreport/rinterface/limma.py,sha256=fxYRUkkJKI-JpDvivjWj8bUS0ug7RRTMnaf2UOgRsXQ,5421
32
32
  msreport/rinterface/rinstaller.py,sha256=AGs6NFMSwTLrzrIJz1E5BE5jFUz8eQBHlpM_MWVChzA,1370
33
33
  msreport/rinterface/rscripts/limma.R,sha256=gr_yjMm_YoG45irDhWOo6gkRQSTwj_7uU_p3NBRHPm8,4331
34
- msreport-0.0.29.dist-info/licenses/LICENSE.txt,sha256=Pd-b5cKP4n2tFDpdx27qJSIq0d1ok0oEcGTlbtL6QMU,11560
35
- msreport-0.0.29.dist-info/METADATA,sha256=pI4CU6ol8LHTgkVEAGs-5HrCNefD52o-VdrZakKtwE4,8008
36
- msreport-0.0.29.dist-info/WHEEL,sha256=ooBFpIzZCPdw3uqIQsOo4qqbA4ZRPxHnOH7peeONza0,91
37
- msreport-0.0.29.dist-info/top_level.txt,sha256=Drl8mCckJHFIw-Ovh5AnyjKnqvLJltDOBUr1JAcHAlI,9
38
- msreport-0.0.29.dist-info/RECORD,,
34
+ msreport-0.0.30.dist-info/licenses/LICENSE.txt,sha256=Pd-b5cKP4n2tFDpdx27qJSIq0d1ok0oEcGTlbtL6QMU,11560
35
+ msreport-0.0.30.dist-info/METADATA,sha256=FO20yj_zTnw7F6pcWrWDbLLFwo_0Bz8Qm8djt1eOcWs,8444
36
+ msreport-0.0.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ msreport-0.0.30.dist-info/top_level.txt,sha256=Drl8mCckJHFIw-Ovh5AnyjKnqvLJltDOBUr1JAcHAlI,9
38
+ msreport-0.0.30.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5