pylabrat 0.1__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.
Files changed (39) hide show
  1. pylabrat-0.1/LICENSE +21 -0
  2. pylabrat-0.1/MANIFEST.in +3 -0
  3. pylabrat-0.1/PKG-INFO +178 -0
  4. pylabrat-0.1/README.md +142 -0
  5. pylabrat-0.1/labrat/__init__.py +3 -0
  6. pylabrat-0.1/labrat/cli.py +187 -0
  7. pylabrat-0.1/labrat/filemanager/__init__.py +2 -0
  8. pylabrat-0.1/labrat/filemanager/archive.py +94 -0
  9. pylabrat-0.1/labrat/filemanager/organize.py +235 -0
  10. pylabrat-0.1/labrat/genetics/__init__.py +0 -0
  11. pylabrat-0.1/labrat/genetics/codons.json +68 -0
  12. pylabrat-0.1/labrat/genetics/dna_analysis.py +65 -0
  13. pylabrat-0.1/labrat/genetics/protein_analysis.py +92 -0
  14. pylabrat-0.1/labrat/graphpad/__init__.py +0 -0
  15. pylabrat-0.1/labrat/graphpad/scripts.py +0 -0
  16. pylabrat-0.1/labrat/guis/__init__.py +0 -0
  17. pylabrat-0.1/labrat/math/__init__.py +2 -0
  18. pylabrat-0.1/labrat/math/functions.py +127 -0
  19. pylabrat-0.1/labrat/notebook/__init__.py +0 -0
  20. pylabrat-0.1/labrat/project/__init__.py +1 -0
  21. pylabrat-0.1/labrat/project/projectmanager.py +445 -0
  22. pylabrat-0.1/labrat/utils.py +31 -0
  23. pylabrat-0.1/labrat-env/bin/jp.py +54 -0
  24. pylabrat-0.1/pylabrat.egg-info/PKG-INFO +178 -0
  25. pylabrat-0.1/pylabrat.egg-info/SOURCES.txt +38 -0
  26. pylabrat-0.1/pylabrat.egg-info/dependency_links.txt +1 -0
  27. pylabrat-0.1/pylabrat.egg-info/entry_points.txt +2 -0
  28. pylabrat-0.1/pylabrat.egg-info/not-zip-safe +1 -0
  29. pylabrat-0.1/pylabrat.egg-info/requires.txt +12 -0
  30. pylabrat-0.1/pylabrat.egg-info/top_level.txt +6 -0
  31. pylabrat-0.1/pyproject.toml +61 -0
  32. pylabrat-0.1/setup.cfg +16 -0
  33. pylabrat-0.1/setup.py +11 -0
  34. pylabrat-0.1/tests/test_archiver.py +43 -0
  35. pylabrat-0.1/tests/test_cli.py +313 -0
  36. pylabrat-0.1/tests/test_file_organizer.py +72 -0
  37. pylabrat-0.1/tests/test_math_functions.py +189 -0
  38. pylabrat-0.1/tests/test_project_manager.py +87 -0
  39. pylabrat-0.1/tests/test_protein_analysis.py +187 -0
pylabrat-0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Shaurita D. Hutchins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include tests/
2
+ include labrat/genetics/codons.json
3
+ recursive-include labrat *.json
pylabrat-0.1/PKG-INFO ADDED
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylabrat
3
+ Version: 0.1
4
+ Summary: A package of helpful guis and functions to improve reproducibility for genetics/psychiatry related labs.
5
+ Author-email: Shaurita Hutchins <shaurita.d.hutchins@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sdhutchins/labrat
8
+ Project-URL: Documentation, https://labrat.readthedocs.io/en/latest/
9
+ Project-URL: Releases, https://github.com/sdhutchins/labrat/releases
10
+ Project-URL: Issues, https://github.com/sdhutchins/labrat/issues
11
+ Keywords: science,lab,genetics,math,filemanagement
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: Unix
16
+ Classifier: Natural Language :: English
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Requires-Python: >=3.7
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: cookiecutter>=1.5.1
25
+ Requires-Dist: logzero>=1.3.1
26
+ Requires-Dist: exmemo>=0.1.0
27
+ Requires-Dist: click>=6.7
28
+ Requires-Dist: jinja2-time>=0.2.0
29
+ Provides-Extra: docs
30
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
31
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
32
+ Requires-Dist: mkdocs-click>=0.8.0; extra == "docs"
33
+ Requires-Dist: mkdocstrings[python]>=0.23.0; extra == "docs"
34
+ Requires-Dist: pymdown-extensions>=10.0.0; extra == "docs"
35
+ Dynamic: license-file
36
+
37
+ # labrat
38
+
39
+ [![Build Status](https://app.travis-ci.com/sdhutchins/labrat.svg?token=xfnbNTQhjNbir5xACn8R&branch=master)](https://app.travis-ci.com/sdhutchins/labrat)
40
+ [![codecov](https://codecov.io/gh/sdhutchins/labrat/graph/badge.svg?token=LqA1Lqf0uu)](https://codecov.io/gh/sdhutchins/labrat)
41
+ [![DOI](https://zenodo.org/badge/99277244.svg)](https://doi.org/10.5281/zenodo.17705600)
42
+
43
+ A basic science lab framework aimed at reproducibility and lab management. This package is in the very early stages of development.
44
+
45
+ ## Features
46
+
47
+ - Create, list, and track/manage computational biology projects with structured templates
48
+ - Calculate solution dilutions, molarity, transmittance/absorbance conversions, and more
49
+ - Automatically organize scientific data files (FASTQ, FASTA, SAM, BAM, VCF, etc.) and others files like pictures, videos, and archives
50
+ - Archive projects and directories with timestamped backups
51
+ - Convert DNA sequences to amino acids and analyze genetic data
52
+ - Full-featured CLI for all major operations
53
+
54
+ ## Install
55
+
56
+ Install from PyPI:
57
+ ```bash
58
+ pip install pylabrat
59
+ ```
60
+
61
+ Or install from source:
62
+ ```bash
63
+ git clone https://github.com/sdhutchins/labrat.git
64
+ cd labrat
65
+ pip install .
66
+ ```
67
+
68
+ For development, install in editable mode:
69
+ ```bash
70
+ pip install -e .
71
+ ```
72
+
73
+ ## Examples
74
+
75
+ ### Command-Line Interface
76
+
77
+ Create a new project:
78
+ ```bash
79
+ labrat project new --type computational-biology --name "KARG Analysis" \
80
+ --path ./karg_analysis --description "Analyze the KARG data"
81
+ ```
82
+
83
+ List all projects:
84
+ ```bash
85
+ labrat project list
86
+ ```
87
+
88
+ Archive files or directories:
89
+ ```bash
90
+ labrat archive --source ./my_project --destination ~/Archive --name "project_backup"
91
+ ```
92
+
93
+ Organize scientific data files:
94
+ ```bash
95
+ labrat organize --science
96
+ ```
97
+
98
+ ### Python API
99
+
100
+ Calculate solution dilutions:
101
+ ```python
102
+ from labrat.math import dilute_stock
103
+
104
+ # Calculate final concentration
105
+ final_conc = dilute_stock(100, 2, vF=4) # Returns 50.0
106
+ ```
107
+
108
+ Manage projects programmatically:
109
+ ```python
110
+ from labrat.project import ProjectManager
111
+
112
+ # Create a new project
113
+ manager = ProjectManager('Dr. Jane Doe')
114
+ manager.new_project(
115
+ project_type='computational-biology',
116
+ project_name='KARG Analysis',
117
+ project_path='./karg_analysis',
118
+ description="Analyze the KARG data."
119
+ )
120
+
121
+ # List all projects
122
+ projects = manager.list_projects()
123
+ ```
124
+
125
+ ## Tests
126
+
127
+ Before running tests, ensure all dependencies are installed:
128
+
129
+ ```bash
130
+ pip install -r requirements.txt
131
+ ```
132
+
133
+ Or if installing the package:
134
+
135
+ ```bash
136
+ pip install .
137
+ ```
138
+
139
+ Run all tests using unittest:
140
+
141
+ ```bash
142
+ python -m unittest discover -s tests
143
+ ```
144
+
145
+ Or run tests with pytest (if installed):
146
+
147
+ ```bash
148
+ pytest tests/
149
+ ```
150
+
151
+ To run a specific test file:
152
+
153
+ ```bash
154
+ python -m unittest tests.test_archiver
155
+ python -m unittest tests.test_file_organizer
156
+ python -m unittest tests.test_project_manager
157
+ ```
158
+
159
+ ## ToDo
160
+
161
+ - [ ] Add a lab inventory app
162
+ - [ ] Add project report template
163
+ - [ ] Integrate [exmemo](https://github.com/kalekundert/exmemo)
164
+
165
+ ## Author
166
+
167
+ Shaurita Hutchins · [@sdhutchins](https://github.com/sdhutchins)
168
+ · [:email:](mailto:shaurita.d.hutchins@gmail.com)
169
+
170
+ ## Contributing
171
+
172
+ If you would like to contribute to this package, install the package in
173
+ development mode, and check out our [contributing
174
+ guidelines](https://github.com/sdhutchins/labrat/blob/master/CONTRIBUTING.md).
175
+
176
+ ## License
177
+
178
+ [MIT](https://github.com/sdhutchins/labrat/blob/master/LICENSE)
pylabrat-0.1/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # labrat
2
+
3
+ [![Build Status](https://app.travis-ci.com/sdhutchins/labrat.svg?token=xfnbNTQhjNbir5xACn8R&branch=master)](https://app.travis-ci.com/sdhutchins/labrat)
4
+ [![codecov](https://codecov.io/gh/sdhutchins/labrat/graph/badge.svg?token=LqA1Lqf0uu)](https://codecov.io/gh/sdhutchins/labrat)
5
+ [![DOI](https://zenodo.org/badge/99277244.svg)](https://doi.org/10.5281/zenodo.17705600)
6
+
7
+ A basic science lab framework aimed at reproducibility and lab management. This package is in the very early stages of development.
8
+
9
+ ## Features
10
+
11
+ - Create, list, and track/manage computational biology projects with structured templates
12
+ - Calculate solution dilutions, molarity, transmittance/absorbance conversions, and more
13
+ - Automatically organize scientific data files (FASTQ, FASTA, SAM, BAM, VCF, etc.) and others files like pictures, videos, and archives
14
+ - Archive projects and directories with timestamped backups
15
+ - Convert DNA sequences to amino acids and analyze genetic data
16
+ - Full-featured CLI for all major operations
17
+
18
+ ## Install
19
+
20
+ Install from PyPI:
21
+ ```bash
22
+ pip install pylabrat
23
+ ```
24
+
25
+ Or install from source:
26
+ ```bash
27
+ git clone https://github.com/sdhutchins/labrat.git
28
+ cd labrat
29
+ pip install .
30
+ ```
31
+
32
+ For development, install in editable mode:
33
+ ```bash
34
+ pip install -e .
35
+ ```
36
+
37
+ ## Examples
38
+
39
+ ### Command-Line Interface
40
+
41
+ Create a new project:
42
+ ```bash
43
+ labrat project new --type computational-biology --name "KARG Analysis" \
44
+ --path ./karg_analysis --description "Analyze the KARG data"
45
+ ```
46
+
47
+ List all projects:
48
+ ```bash
49
+ labrat project list
50
+ ```
51
+
52
+ Archive files or directories:
53
+ ```bash
54
+ labrat archive --source ./my_project --destination ~/Archive --name "project_backup"
55
+ ```
56
+
57
+ Organize scientific data files:
58
+ ```bash
59
+ labrat organize --science
60
+ ```
61
+
62
+ ### Python API
63
+
64
+ Calculate solution dilutions:
65
+ ```python
66
+ from labrat.math import dilute_stock
67
+
68
+ # Calculate final concentration
69
+ final_conc = dilute_stock(100, 2, vF=4) # Returns 50.0
70
+ ```
71
+
72
+ Manage projects programmatically:
73
+ ```python
74
+ from labrat.project import ProjectManager
75
+
76
+ # Create a new project
77
+ manager = ProjectManager('Dr. Jane Doe')
78
+ manager.new_project(
79
+ project_type='computational-biology',
80
+ project_name='KARG Analysis',
81
+ project_path='./karg_analysis',
82
+ description="Analyze the KARG data."
83
+ )
84
+
85
+ # List all projects
86
+ projects = manager.list_projects()
87
+ ```
88
+
89
+ ## Tests
90
+
91
+ Before running tests, ensure all dependencies are installed:
92
+
93
+ ```bash
94
+ pip install -r requirements.txt
95
+ ```
96
+
97
+ Or if installing the package:
98
+
99
+ ```bash
100
+ pip install .
101
+ ```
102
+
103
+ Run all tests using unittest:
104
+
105
+ ```bash
106
+ python -m unittest discover -s tests
107
+ ```
108
+
109
+ Or run tests with pytest (if installed):
110
+
111
+ ```bash
112
+ pytest tests/
113
+ ```
114
+
115
+ To run a specific test file:
116
+
117
+ ```bash
118
+ python -m unittest tests.test_archiver
119
+ python -m unittest tests.test_file_organizer
120
+ python -m unittest tests.test_project_manager
121
+ ```
122
+
123
+ ## ToDo
124
+
125
+ - [ ] Add a lab inventory app
126
+ - [ ] Add project report template
127
+ - [ ] Integrate [exmemo](https://github.com/kalekundert/exmemo)
128
+
129
+ ## Author
130
+
131
+ Shaurita Hutchins · [@sdhutchins](https://github.com/sdhutchins)
132
+ · [:email:](mailto:shaurita.d.hutchins@gmail.com)
133
+
134
+ ## Contributing
135
+
136
+ If you would like to contribute to this package, install the package in
137
+ development mode, and check out our [contributing
138
+ guidelines](https://github.com/sdhutchins/labrat/blob/master/CONTRIBUTING.md).
139
+
140
+ ## License
141
+
142
+ [MIT](https://github.com/sdhutchins/labrat/blob/master/LICENSE)
@@ -0,0 +1,3 @@
1
+ # Package Global Variables
2
+ _DATEFMT1 = '%a %b %d %I:%M:%S %p %Y' # Used to add as a date
3
+ _DATEFMT2 = '%m-%d-%Y_%I-%M-%S-%p' # Used to append to archives
@@ -0,0 +1,187 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Command-line interface for labrat."""
3
+ import click
4
+ from pathlib import Path
5
+ from labrat.project import ProjectManager
6
+ from labrat.filemanager import Archiver, FileOrganizer
7
+
8
+
9
+ @click.group()
10
+ def main():
11
+ """Labrat - A basic science lab framework for reproducibility and lab management."""
12
+ pass
13
+
14
+
15
+ @main.group()
16
+ def project():
17
+ """Manage projects."""
18
+ pass
19
+
20
+
21
+ @project.command('new')
22
+ @click.option('--type', 'project_type', required=True,
23
+ help='Type of project (e.g., computational-biology, data-science)')
24
+ @click.option('--name', 'project_name', required=True,
25
+ help='Name of the project')
26
+ @click.option('--path', 'project_path', required=True, type=click.Path(),
27
+ help='Path where the project will be created')
28
+ @click.option('--description', required=True,
29
+ help='Description of the project')
30
+ @click.option('--username', default=None,
31
+ help='Username for project manager (defaults to system default)')
32
+ def new_project(project_type, project_name, project_path, description, username):
33
+ """Create a new project."""
34
+ try:
35
+ manager = ProjectManager(username=username)
36
+ manager.new_project(
37
+ project_type=project_type,
38
+ project_name=project_name,
39
+ project_path=project_path,
40
+ description=description
41
+ )
42
+ click.echo(f"✓ Project '{project_name}' created successfully at {project_path}")
43
+ except Exception as e:
44
+ click.echo(f"✗ Error creating project: {e}", err=True)
45
+ raise click.Abort()
46
+
47
+
48
+ @project.command('list')
49
+ @click.option('--username', default=None,
50
+ help='Username for project manager (defaults to system default)')
51
+ def list_projects(username):
52
+ """List all projects."""
53
+ try:
54
+ manager = ProjectManager(username=username)
55
+ projects = manager.list_projects()
56
+
57
+ if not projects:
58
+ click.echo("No projects found.")
59
+ return
60
+
61
+ click.echo(f"\nFound {len(projects)} project(s):\n")
62
+ for idx, proj in enumerate(projects, 1):
63
+ click.echo(f"{idx}. {proj.get('name', 'Unknown')}")
64
+ click.echo(f" Path: {proj.get('path', 'Unknown')}")
65
+ click.echo(f" Type: {proj.get('project_type', 'Unknown')}")
66
+ click.echo(f" Created: {proj.get('created_at', 'Unknown')}")
67
+ click.echo()
68
+ except Exception as e:
69
+ click.echo(f"✗ Error listing projects: {e}", err=True)
70
+ raise click.Abort()
71
+
72
+
73
+ @project.command('delete')
74
+ @click.option('--path', 'project_path', required=True, type=click.Path(exists=True),
75
+ help='Path to the project to delete')
76
+ @click.option('--archive-dir', required=True, type=click.Path(),
77
+ help='Directory where the archived project will be stored')
78
+ @click.option('--username', default=None,
79
+ help='Username for project manager (defaults to system default)')
80
+ @click.confirmation_option(prompt='Are you sure you want to delete this project?')
81
+ def delete_project(project_path, archive_dir, username):
82
+ """Delete a project (archives it first)."""
83
+ try:
84
+ manager = ProjectManager(username=username)
85
+ archive_path = manager.delete_project(project_path, archive_dir)
86
+ click.echo(f"✓ Project deleted and archived to: {archive_path}")
87
+ except Exception as e:
88
+ click.echo(f"✗ Error deleting project: {e}", err=True)
89
+ raise click.Abort()
90
+
91
+
92
+ @main.command('archive')
93
+ @click.option('--source', required=True, type=click.Path(exists=True, dir_okay=True),
94
+ help='Source directory to archive')
95
+ @click.option('--destination', required=True, type=click.Path(),
96
+ help='Base directory for storing archives')
97
+ @click.option('--name', 'project_name', required=True,
98
+ help='Name for the archive')
99
+ def archive(source, destination, project_name):
100
+ """Archive a directory."""
101
+ try:
102
+ archive_dir = Archiver.get_archive_dir(destination, project_name)
103
+ archiver = Archiver(source_dir=source, archive_dir=archive_dir)
104
+ zip_path = archiver.archive()
105
+ click.echo(f"✓ Archive created successfully: {zip_path}")
106
+ except Exception as e:
107
+ click.echo(f"✗ Error creating archive: {e}", err=True)
108
+ raise click.Abort()
109
+
110
+
111
+ @main.command('organize')
112
+ @click.option('--science', 'organize_science', is_flag=True,
113
+ help='Organize scientific data files (fastq, fasta, sam, bam, vcf, fits, hdf5, etc.) to Documents/Research_Data')
114
+ @click.option('--science-dir', type=click.Path(),
115
+ help='Custom directory for scientific data files (default: Documents/Research_Data)')
116
+ @click.option('--keyword', default=None,
117
+ help='Move files containing this keyword to a specific folder')
118
+ @click.option('--pictures', 'organize_pictures', is_flag=True,
119
+ help='Organize picture files to Pictures folder')
120
+ @click.option('--videos', 'organize_videos', is_flag=True,
121
+ help='Organize video files to Videos folder')
122
+ @click.option('--archives', 'organize_archives', is_flag=True,
123
+ help='Organize archive files by compression type')
124
+ @click.option('--all', 'organize_all', is_flag=True,
125
+ help='Organize all file types')
126
+ def organize(organize_science, science_dir, keyword, organize_pictures,
127
+ organize_videos, organize_archives, organize_all):
128
+ """
129
+ Organize files in Downloads and Documents directories.
130
+
131
+ By default, scientific data files (fastq, fasta, sam, bam, vcf, fits, hdf5, nc, etc.)
132
+ are moved to Documents/Research_Data. Use --science-dir to specify a custom location.
133
+
134
+ Examples:
135
+ labrat organize --science
136
+ labrat organize --science --science-dir ~/Research
137
+ labrat organize --keyword "project_alpha"
138
+ labrat organize --all
139
+ """
140
+ if not any([organize_science, keyword, organize_pictures, organize_videos,
141
+ organize_archives, organize_all]):
142
+ click.echo("Error: Specify at least one organization option", err=True)
143
+ click.echo("Use --science to organize science files, or --all for everything", err=True)
144
+ raise click.Abort()
145
+
146
+ try:
147
+ organizer = FileOrganizer()
148
+ actions_taken = []
149
+
150
+ if organize_all:
151
+ organizer.organize_all()
152
+ actions_taken.append("all files")
153
+ else:
154
+ # Organize science files (default behavior for scientists)
155
+ if organize_science:
156
+ organizer.organize_science_files(science_dir=science_dir)
157
+ location = science_dir if science_dir else "Documents/Research_Data"
158
+ actions_taken.append(f"science files to {location}")
159
+
160
+ # Organize media files
161
+ if organize_pictures or organize_videos:
162
+ organizer.organize_files()
163
+ media = []
164
+ if organize_pictures:
165
+ media.append("pictures")
166
+ if organize_videos:
167
+ media.append("videos")
168
+ actions_taken.append(f"{' and '.join(media)}")
169
+
170
+ # Organize archives
171
+ if organize_archives:
172
+ organizer.organize_archives()
173
+ actions_taken.append("archives")
174
+
175
+ # Handle keyword-based organization
176
+ if keyword:
177
+ organizer.move_specific_files(keyword=keyword)
178
+ actions_taken.append(f"files with keyword '{keyword}'")
179
+
180
+ click.echo(f"✓ Organized {', '.join(actions_taken)} successfully")
181
+ except Exception as e:
182
+ click.echo(f"✗ Error organizing files: {e}", err=True)
183
+ raise click.Abort()
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()
@@ -0,0 +1,2 @@
1
+ from .archive import Archiver
2
+ from .organize import FileOrganizer
@@ -0,0 +1,94 @@
1
+ from shutil import copytree, SameFileError, make_archive
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ import logzero
5
+ from logzero import logger
6
+ from labrat.utils import get_labrat_dir
7
+
8
+
9
+ class Archiver:
10
+ """Archive folders and files."""
11
+
12
+ def __init__(self, source_dir, archive_dir):
13
+ """
14
+ Initialize logger and archive parameters.
15
+
16
+ Args:
17
+ source_dir (str or Path): The directory to copy or archive.
18
+ archive_dir (str or Path): The destination directory for the archive.
19
+ """
20
+ self.source_dir = Path(source_dir).resolve()
21
+ self.archive_dir = Path(archive_dir).resolve()
22
+
23
+ if not self.source_dir.exists() or not self.source_dir.is_dir():
24
+ raise ValueError(f"Source directory '{self.source_dir}' does not exist or is not a directory.")
25
+ logger.debug(f"Source directory: {self.source_dir}")
26
+
27
+ if not self.archive_dir.parent.exists():
28
+ raise ValueError(f"Archive directory parent '{self.archive_dir.parent}' does not exist.")
29
+ logger.debug(f"Archive directory: {self.archive_dir}")
30
+
31
+ # Configure log file in .labrat directory
32
+ # Store logs in user's home directory under .labrat folder
33
+ labrat_dir = get_labrat_dir()
34
+ log_file = labrat_dir / f"archive_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
35
+ logzero.logfile(str(log_file))
36
+ logger.info("Archive initialized.")
37
+
38
+ def archive(self):
39
+ """
40
+ Perform the archive by copying the source directory to the archive directory
41
+ and then creating a zip file of the archived folder.
42
+
43
+ Raises:
44
+ SameFileError: If the source and destination are the same.
45
+ OSError: For other filesystem-related errors.
46
+ """
47
+ logger.info("Starting archive process...")
48
+ logger.info(f"Source: {self.source_dir}")
49
+ logger.info(f"Destination: {self.archive_dir}")
50
+
51
+ # Step 1: Copy the source directory to the archive directory
52
+ try:
53
+ copytree(self.source_dir, self.archive_dir)
54
+ logger.info(f"Archive folder created successfully: {self.source_dir} -> {self.archive_dir}")
55
+ except SameFileError as e:
56
+ logger.error(f"Source and destination are the same: {e}")
57
+ raise
58
+ except FileExistsError:
59
+ logger.warning(f"Archive destination already exists: {self.archive_dir}")
60
+ except OSError as e:
61
+ logger.error(f"Failed to complete archive: {e}")
62
+ raise
63
+
64
+ # Create a zip file for the archived folder
65
+ try:
66
+ zip_path = make_archive(
67
+ base_name=str(self.archive_dir), # The base name of the archive
68
+ format="zip", # Archive format
69
+ root_dir=str(self.archive_dir), # Root directory to archive
70
+ )
71
+ logger.info(f"Archive zipped successfully: {zip_path}")
72
+ except OSError as e:
73
+ logger.error(f"Failed to zip the archive: {e}")
74
+ raise
75
+
76
+ return zip_path
77
+
78
+ @staticmethod
79
+ def get_archive_dir(base_dir, project_name):
80
+ """
81
+ Generate a timestamped archive directory path.
82
+
83
+ Args:
84
+ base_dir (str or Path): The base directory where archives are stored.
85
+ project_name (str): The name of the project being archived.
86
+
87
+ Returns:
88
+ Path: A path to the timestamped archive directory.
89
+ """
90
+ base_dir = Path(base_dir).resolve()
91
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
92
+ archive_dir = base_dir / f"{project_name}_archive_{timestamp}"
93
+ logger.debug(f"Generated archive directory: {archive_dir}")
94
+ return archive_dir