Repository: pepkit/peppy

Issue:
Add orient argument to `to_dict` method
It would be niece to add `orient` argument, that sets orientation of the output structure of the samples and subsamples

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.to_dict.html

Related issue: https://github.com/pepkit/pephub/issues/186

Discussion:

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 22ea48b..3ecc382 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,16 @@
 
 This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
 
+## [0.35.6] -- 2023-06-27
+### Added
+- `orient` argument to `to_dict` method
+
+### Fixed
+- The name of the raw subsample object to match the actual name (list). Commit: #442
+
+### Changed
+- Reduced the number of items returned in the to_dict(extended=True) method to 3, with the name and description now stored in the config key.
+
 ## [0.35.5] -- 2023-03-27
 ### Fixed
 - A [bug](https://github.com/pepkit/peppy/issues/435) with custom sample ids
diff --git a/peppy/_version.py b/peppy/_version.py
index 322d9f8..a51f1e9 100644
--- a/peppy/_version.py
+++ b/peppy/_version.py
@@ -1,1 +1,1 @@
-__version__ = "0.35.5"
+__version__ = "0.35.6"
diff --git a/peppy/const.py b/peppy/const.py
index bf6cc1b..0f009a5 100644
--- a/peppy/const.py
+++ b/peppy/const.py
@@ -119,6 +119,6 @@ OTHER_CONSTANTS = [
 
 PEP_LATEST_VERSION = "2.1.0"
 SAMPLE_RAW_DICT_KEY = "_sample_dict"
-SUBSAMPLE_RAW_DICT_KEY = "_subsample_dict"
+SUBSAMPLE_RAW_LIST_KEY = "_subsample_list"
 
 __all__ = PROJECT_CONSTANTS + SAMPLE_CONSTANTS + OTHER_CONSTANTS
diff --git a/peppy/project.py b/peppy/project.py
index d16d470..9e21364 100644
--- a/peppy/project.py
+++ b/peppy/project.py
@@ -6,7 +6,7 @@ import os, sys
 from collections.abc import Mapping
 from contextlib import suppress
 from logging import getLogger
-from typing import Dict, Iterable, List, Tuple, Union
+from typing import Dict, Iterable, List, Tuple, Union, Literal
 
 import numpy as np
 import pandas as pd
@@ -57,7 +57,7 @@ from .const import (
     SAMPLE_TABLE_INDEX_KEY,
     SUBSAMPLE_DF_KEY,
     SUBSAMPLE_NAME_ATTR,
-    SUBSAMPLE_RAW_DICT_KEY,
+    SUBSAMPLE_RAW_LIST_KEY,
     SUBSAMPLE_TABLE_INDEX_KEY,
     SUBSAMPLE_TABLES_FILE_KEY,
 )
@@ -208,17 +208,17 @@ class Project(PathExAttMap):
         self[SAMPLE_DF_KEY] = pd.DataFrame(pep_dictionary[SAMPLE_RAW_DICT_KEY])
         self[CONFIG_KEY] = pep_dictionary[CONFIG_KEY]
 
-        if SUBSAMPLE_RAW_DICT_KEY in pep_dictionary:
-            if pep_dictionary[SUBSAMPLE_RAW_DICT_KEY]:
+        if SUBSAMPLE_RAW_LIST_KEY in pep_dictionary:
+            if pep_dictionary[SUBSAMPLE_RAW_LIST_KEY]:
                 self[SUBSAMPLE_DF_KEY] = [
                     pd.DataFrame(sub_a)
-                    for sub_a in pep_dictionary[SUBSAMPLE_RAW_DICT_KEY]
+                    for sub_a in pep_dictionary[SUBSAMPLE_RAW_LIST_KEY]
                 ]
-        if NAME_KEY in pep_dictionary:
-            self[NAME_KEY] = pep_dictionary[NAME_KEY]
+        if NAME_KEY in self[CONFIG_KEY]:
+            self[NAME_KEY] = self[CONFIG_KEY][NAME_KEY]
 
-        if DESC_KEY in pep_dictionary:
-            self[DESC_KEY] = pep_dictionary[DESC_KEY]
+        if DESC_KEY in self[CONFIG_KEY]:
+            self[DESC_KEY] = self[CONFIG_KEY][DESC_KEY]
 
         self.create_samples(modify=False if self[SAMPLE_TABLE_FILE_KEY] else True)
         self._sample_table = self._get_table_from_samples(
@@ -227,25 +227,35 @@ class Project(PathExAttMap):
 
         return self
 
-    def to_dict(self, expand: bool = False, extended: bool = False) -> dict:
+    def to_dict(
+        self,
+        expand: bool = False,
+        extended: bool = False,
+        orient: Literal[
+            "dict", "list", "series", "split", "tight", "records", "index"
+        ] = "dict",
+    ) -> dict:
         """
         Convert the Project object to a dictionary.
 
         :param bool expand: whether to expand the paths
         :param bool extended: whether to produce complete project dict (used to reinit the project)
+        :param Literal orient: orientation of the returned df
         :return dict: a dictionary representation of the Project object
         """
         if extended:
             if self[SUBSAMPLE_DF_KEY] is not None:
-                sub_df = [sub_a.to_dict() for sub_a in self[SUBSAMPLE_DF_KEY]]
+                sub_df = [
+                    sub_a.to_dict(orient=orient) for sub_a in self[SUBSAMPLE_DF_KEY]
+                ]
             else:
                 sub_df = None
+            self[CONFIG_KEY][NAME_KEY] = self[NAME_KEY]
+            self[CONFIG_KEY][DESC_KEY] = self[DESC_KEY]
             p_dict = {
-                SAMPLE_RAW_DICT_KEY: self[SAMPLE_DF_KEY].to_dict(),
-                CONFIG_KEY: self[CONFIG_KEY],
-                SUBSAMPLE_RAW_DICT_KEY: sub_df,
-                NAME_KEY: self[NAME_KEY],
-                DESC_KEY: self[DESC_KEY],
+                SAMPLE_RAW_DICT_KEY: self[SAMPLE_DF_KEY].to_dict(orient=orient),
+                CONFIG_KEY: self[CONFIG_KEY].to_dict(expand=expand),
+                SUBSAMPLE_RAW_LIST_KEY: sub_df,
             }
         else:
             p_dict = self.config.to_dict(expand=expand)


Test patch:
diff --git a/tests/test_Project.py b/tests/test_Project.py
index b585e04..85a1c48 100644
--- a/tests/test_Project.py
+++ b/tests/test_Project.py
@@ -553,13 +553,16 @@ class TestPostInitSampleCreation:
         p2 = Project(cfg=example_pep_csv_path)
         assert not p1 == p2
 
-    @pytest.mark.parametrize("example_pep_cfg_path", ["append"], indirect=True)
-    def test_from_dict(self, example_pep_cfg_path):
+    @pytest.mark.parametrize(
+        "example_pep_cfg_path", ["append", "subtable2"], indirect=True
+    )
+    @pytest.mark.parametrize("orient", ["dict", "records"])
+    def test_from_dict(self, example_pep_cfg_path, orient):
         """
         Test initializing project from dict
         """
         p1 = Project(cfg=example_pep_cfg_path)
-        p1_dict = p1.to_dict(extended=True)
+        p1_dict = p1.to_dict(extended=True, orient=orient)
         del p1_dict["_config"]["sample_table"]
         p2 = Project().from_dict(p1_dict)
         assert p1 == p2