Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a2d02d6
Remove development section
BMatischen01 Sep 5, 2025
a86a4a9
Create CONTRBUTING.md
BMatischen01 Sep 5, 2025
35ed035
Update CONTRBUTING.md
BMatischen01 Sep 5, 2025
d6ee695
Update CONTRBUTING.md
BMatischen01 Sep 5, 2025
a9fe93d
docs: Add New Release section
BMatischen01 Sep 5, 2025
f38b254
Add guidelines for making issues
BMatischen01 Sep 5, 2025
47868f4
Rename CONTRBUTING.md to CONTRIBUTING.md
BMatischen01 Sep 5, 2025
c180d28
Readd Dev setup, add token setup in Install section
BMatischen01 Sep 5, 2025
ae9632f
Create overview.rst
BMatischen01 Sep 5, 2025
6fe30ce
Add basic export flow to overview.rst
BMatischen01 Sep 5, 2025
6cac794
Add code example for basic steps of exporting
BMatischen01 Sep 5, 2025
13f304e
Create getting_started.rst
BMatischen01 Sep 5, 2025
5c75c24
Remove code sample from overview.rst
BMatischen01 Sep 5, 2025
8d10b89
docs: Correct docstrings in formatter.py
BMatischen01 Sep 5, 2025
b7a4ad9
docs: Add more docstrings in exporter.py
BMatischen01 Sep 5, 2025
123a03b
Apply suggestion from @SJaffa
BMatischen01 Sep 5, 2025
9bce644
docs: Add html docs for classes, functions
BMatischen01 Sep 5, 2025
5107ab1
docs: Remove release section in CONTRIBUTING.md
BMatischen01 Sep 5, 2025
d7e21b0
fix: linting errors
BMatischen01 Sep 5, 2025
2bb657d
docs: Add how to run tests
BMatischen01 Sep 5, 2025
e7dcf0b
Merge pull request #120 from CenterForOpenScience/16-write-a-contribu…
BMatischen01 Sep 5, 2025
972ed98
Merge pull request #121 from CenterForOpenScience/119-technical-docum…
BMatischen01 Sep 5, 2025
30fc3dd
docs: Add acknowledgements
BMatischen01 Sep 5, 2025
0da61c0
Merge pull request #122 from CenterForOpenScience/34-refactor-readme-…
BMatischen01 Sep 5, 2025
0d054ae
chore: Bump version to 1.0.1
BMatischen01 Sep 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Contributing

`osfexport` is an open-source project. Contributions to report bugs, suggest new features or improvements, ask questions, develop the code/documentation, and any other kind of contribution are more than welcome.

By contributing, you are agreeing that we may redistribute your work under [this license](https://github.com/CenterForOpenScience/osf-project-exporter?tab=Apache-2.0-1-ov-file).

## Guidelines

The below are guidelines about how contributions should be made, and are not necessarily hard rules.

### Reporting a Bug

For reporting bugs, create a new issue:

- Describe what the bug is, what steps people can do to reproduce it, and what platform you were using `osfexport` on.
- Add a `bug` label to the issue.

### Suggesting a new Feature

Create a new issue:

- Describe what the feature is, and why it would be useful to add to the project.
- Add a `enhancement` label to the issue.

### GitHub Flow

Contrbutions to code and documentation should follow the [GitHub flow model](https://docs.github.com/en/get-started/using-github/github-flow) in general. Key points are:

- Feature branches should be branched off of develop or a release branch
- All changes to be merged must have a Pull Request opened first.
- Before submitting a pull request re-merge the source branch and resolve any merge conflicts
- A branch should not be merged until it passes all checks and has been approved by one person.
- Checks for linting quality and if tests pass automatically runs when pull requests are made and updated.
- Do not merge develop if you are working off a release branch and vice versa
- Use -'s for spaces not _'s
- Hotfixes are to be branched off main
- Hotfix PR should be names hotfix/brief-description
- A hotfix for an issue involving figshare metadata when empty lists are returned would behotfix/figshare-metadata-empty
- When hotfixes are merged a new branch will be created bumping the minor version ie hotfix/0.1.3 and the other PR will be merged into it

The naming convention for branches is: `<issue-number>-<brief-issue-description>`. For example, a branch for issue 5 `Make tests run faster` would be named `5-make-tests-run-faster`.

### Library Versioning

`osfexport` uses semantic versioning `<major>.<minor>.<patch>`

- Patches are reserved for hotfixes and bugfixes only
- Minor versions are for adding new functionality
- Minor versions: any changes must be backwards compatible
- Major versions can contain breaking changes

### Pull Request Guidelines

All code must pass [flake8 linting](https://peps.python.org/pep-0008/)

- Max line length is set 100 characters

Imports are should be ordered in pep8 style.

Keep commit histories as clean and simple as possible, with meaningful commit messages.
The [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) style helps with this.

Add docstrings to explain expected inputs, outputs and errors for classes and functions.
Add comments to explain why sections of code are structured like they are (and how they do it if that would be helpful.)

Add tests for new features or checking for bugs, to help verify the correctness of your changes.

Make a PR to resolve one issue only. Keep changes made to only those needed to resolve the issue.
In your PR, add a link to what the PR addresses, and describe how the changes made address the issue.

Pull requests should not be merged unless all checks pass and have been approved by one human reviewer.
45 changes: 30 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
# OSF Project Exporter

This is a CLI tool for exporting research project data and files from the [OSF website](https://osf.io/). This is to prototype tool to export projects from the OSF website to a PDF, allowing users to back up, share or document their OSF projects in an offline medium.
`osfexport` is a proof-of-concept Python library and command-line tool for exporting research project data and files from the [OSF website](https://osf.io/). It enables researchers to export project data into a PDF for archiving and backup of OSF projects.
The project data exported includes:

## Development Setup

### Virtual Environment
- Project metadata: title, description, funding sources, subjects, tags, date created, date modified, etc.
- A list of project files stored on OSF Storage. Files stored on the OSF can be downloaded directly from the website, you can use this list to check what files should be present.
- A list of contributors for the project: name, if they are bibliographic (appear on citations and public list of contributors), profile link
- Wiki page contents - includes formatted markdown and images
- Any components added as a sub-project

1. Clone this repository onto your local machine.
2. Create a virtual environment to install dependencies. For `virtualenv` this is done with ``virtualenv <myenvname>``. Make sure your virtual environment is setup to use Python 3.12 or above (e.g., ``virtualenv <myenvname> --python="/usr/bin/python3.12"`` on Linux.)
3. From local Git repo: Activate your virtual environment and run ``pip install -e osfexport`` to install this repository as a modifiable package. Then install other requirements separately via `pip install -r requirements.txt`.
4. On the OSF website, create or log in to your account. Set up a personal access token (PAT) by going into your account settings, select `Personal access tokens` in the left side menu, and clicking `Create token`. You should give the token a name that helps you remember why you made it, like "PDF export", and choose the `osf.full_read` scope - this allows this token to read all public and private projects on your account. You can delete this token once you have finished exporting your projects.
Currently this project is a proof-of-concept for data backup for the OSF focused on exporting project data which doesn't have a way to do so on the OSF website. It could be extended to include preprints, registrations, and other data types.

## Installation

### From PyPI: releases 0.1.4 and onwards
Install this library via pip:
`python -m pip install osfexport`

Activate your virtual environment: for example, using `virtualenv` this is done by:

- `source <myenvname>/bin/activate` on Linux
- `<myenvname>\Scripts\activate` on Windows/Mac
## Usage

Next, run `python -m pip install osfexport`. This will download and install this package and other dependencies from the PyPI index.
`osfexport` can be used as either a Python library or a command-line tool.

## Usage
To use as a command-line tool:

- On the OSF website, create or log in to your account and set up a personal access token (PAT)
- Go to your account settings, select `Personal access tokens` in the left side menu
- Click `Create token`. You should give the token a name that helps you remember why you made it, like "PDF export"
- Give your token the `osf.full_read` scope. This allows the token to access private projects you are a contributor to.
- Run `osfexport` to get a list of basic commands you can use.
- To see what a command needs as input, type `--help` after the command name (e.g. `osfexport welcome --help`; `osfexport --help`)
- To export all your projects from the OSF into a PDF, run `osfexport projects`.

## Development Setup

### Virtual Environment

1. Clone this repository onto your local machine.
2. Create a virtual environment to install dependencies. For `virtualenv` this is done with ``virtualenv <myenvname>``. Make sure your virtual environment is setup to use Python 3.12 or above (e.g., ``virtualenv <myenvname> --python="/usr/bin/python3.12"`` on Linux.)
3. From local Git repo: Activate your virtual environment and run ``pip install -e osfexport`` to install this repository as a modifiable package.
4. On the OSF website, create or log in to your account. Set up a personal access token (PAT) by going into your account settings, select `Personal access tokens` in the left side menu, and clicking `Create token`. You should give the token a name that helps you remember why you made it, like "PDF export", and choose the `osf.full_read` scope - this allows this token to read all public and private projects on your account.

## Acknowledgements

Work for v1.0.0 of `osfexport` was kindly funded by the Advance Open-Source Infrastructure for Research grant, as part of the The Open Source Awardee Program by the [Center for Open Science](https://www.cos.io/), and a collaboration between Center for Open Science and the [University of Manchester Research IT department](https://research-it.manchester.ac.uk/).
142 changes: 142 additions & 0 deletions docs/cli.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta name="generator" content="pdoc3 0.11.6">
<title>osfexport.cli API documentation</title>
<meta name="description" content="">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/sanitize.min.css" integrity="sha512-y1dtMcuvtTMJc1yPgEqF0ZjQbhnc/bFhyvIyVNb9Zk5mIGtqVaAB1Ttl28su8AvFMOY0EwRbAe+HCLqj6W7/KA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/10up-sanitize.css/13.0.0/typography.min.css" integrity="sha512-Y1DYSb995BAfxobCkKepB1BqJJTPrOp3zPL74AWFugHHmmdcvO+C48WLrUOlhGMc0QG7AE3f7gmvvcrmX2fDoA==" crossorigin>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" crossorigin>
<style>:root{--highlight-color:#fe9}.flex{display:flex !important}body{line-height:1.5em}#content{padding:20px}#sidebar{padding:1.5em;overflow:hidden}#sidebar > *:last-child{margin-bottom:2cm}.http-server-breadcrumbs{font-size:130%;margin:0 0 15px 0}#footer{font-size:.75em;padding:5px 30px;border-top:1px solid #ddd;text-align:right}#footer p{margin:0 0 0 1em;display:inline-block}#footer p:last-child{margin-right:30px}h1,h2,h3,h4,h5{font-weight:300}h1{font-size:2.5em;line-height:1.1em}h2{font-size:1.75em;margin:2em 0 .50em 0}h3{font-size:1.4em;margin:1.6em 0 .7em 0}h4{margin:0;font-size:105%}h1:target,h2:target,h3:target,h4:target,h5:target,h6:target{background:var(--highlight-color);padding:.2em 0}a{color:#058;text-decoration:none;transition:color .2s ease-in-out}a:visited{color:#503}a:hover{color:#b62}.title code{font-weight:bold}h2[id^="header-"]{margin-top:2em}.ident{color:#900;font-weight:bold}pre code{font-size:.8em;line-height:1.4em;padding:1em;display:block}code{background:#f3f3f3;font-family:"DejaVu Sans Mono",monospace;padding:1px 4px;overflow-wrap:break-word}h1 code{background:transparent}pre{border-top:1px solid #ccc;border-bottom:1px solid #ccc;margin:1em 0}#http-server-module-list{display:flex;flex-flow:column}#http-server-module-list div{display:flex}#http-server-module-list dt{min-width:10%}#http-server-module-list p{margin-top:0}.toc ul,#index{list-style-type:none;margin:0;padding:0}#index code{background:transparent}#index h3{border-bottom:1px solid #ddd}#index ul{padding:0}#index h4{margin-top:.6em;font-weight:bold}@media (min-width:200ex){#index .two-column{column-count:2}}@media (min-width:300ex){#index .two-column{column-count:3}}dl{margin-bottom:2em}dl dl:last-child{margin-bottom:4em}dd{margin:0 0 1em 3em}#header-classes + dl > dd{margin-bottom:3em}dd dd{margin-left:2em}dd p{margin:10px 0}.name{background:#eee;font-size:.85em;padding:5px 10px;display:inline-block;min-width:40%}.name:hover{background:#e0e0e0}dt:target .name{background:var(--highlight-color)}.name > span:first-child{white-space:nowrap}.name.class > span:nth-child(2){margin-left:.4em}.inherited{color:#999;border-left:5px solid #eee;padding-left:1em}.inheritance em{font-style:normal;font-weight:bold}.desc h2{font-weight:400;font-size:1.25em}.desc h3{font-size:1em}.desc dt code{background:inherit}.source > summary,.git-link-div{color:#666;text-align:right;font-weight:400;font-size:.8em;text-transform:uppercase}.source summary > *{white-space:nowrap;cursor:pointer}.git-link{color:inherit;margin-left:1em}.source pre{max-height:500px;overflow:auto;margin:0}.source pre code{font-size:12px;overflow:visible;min-width:max-content}.hlist{list-style:none}.hlist li{display:inline}.hlist li:after{content:',\2002'}.hlist li:last-child:after{content:none}.hlist .hlist{display:inline;padding-left:1em}img{max-width:100%}td{padding:0 .5em}.admonition{padding:.1em 1em;margin:1em 0}.admonition-title{font-weight:bold}.admonition.note,.admonition.info,.admonition.important{background:#aef}.admonition.todo,.admonition.versionadded,.admonition.tip,.admonition.hint{background:#dfd}.admonition.warning,.admonition.versionchanged,.admonition.deprecated{background:#fd4}.admonition.error,.admonition.danger,.admonition.caution{background:lightpink}</style>
<style media="screen and (min-width: 700px)">@media screen and (min-width:700px){#sidebar{width:30%;height:100vh;overflow:auto;position:sticky;top:0}#content{width:70%;max-width:100ch;padding:3em 4em;border-left:1px solid #ddd}pre code{font-size:1em}.name{font-size:1em}main{display:flex;flex-direction:row-reverse;justify-content:flex-end}.toc ul ul,#index ul ul{padding-left:1em}.toc > ul > li{margin-top:.5em}}</style>
<style media="print">@media print{#sidebar h1{page-break-before:always}.source{display:none}}@media print{*{background:transparent !important;color:#000 !important;box-shadow:none !important;text-shadow:none !important}a[href]:after{content:" (" attr(href) ")";font-size:90%}a[href][title]:after{content:none}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h1,h2,h3,h4,h5,h6{page-break-after:avoid}}</style>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin></script>
<script>window.addEventListener('DOMContentLoaded', () => {
hljs.configure({languages: ['bash', 'css', 'diff', 'graphql', 'ini', 'javascript', 'json', 'plaintext', 'python', 'python-repl', 'rust', 'shell', 'sql', 'typescript', 'xml', 'yaml']});
hljs.highlightAll();
/* Collapse source docstrings */
setTimeout(() => {
[...document.querySelectorAll('.hljs.language-python > .hljs-string')]
.filter(el => el.innerHTML.length > 200 && ['"""', "'''"].includes(el.innerHTML.substring(0, 3)))
.forEach(el => {
let d = document.createElement('details');
d.classList.add('hljs-string');
d.innerHTML = '<summary>"""</summary>' + el.innerHTML.substring(3);
el.replaceWith(d);
});
}, 100);
})</script>
</head>
<body>
<main>
<article id="content">
<header>
<h1 class="title">Module <code>osfexport.cli</code></h1>
</header>
<section id="section-intro">
</section>
<section>
</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="osfexport.cli.prompt_pat"><code class="name flex">
<span>def <span class="ident">prompt_pat</span></span>(<span>project_id='', usetest=False)</span>
</code></dt>
<dd>
<details class="source">
<summary>
<span>Expand source code</span>
</summary>
<pre><code class="python">def prompt_pat(project_id=&#39;&#39;, usetest=False):
&#34;&#34;&#34;
Ask for a PAT if exporting a single project or all projects a user has.

Parameters
-------------
project_id: str
ID of a single project to export.
If one provided then ask for a PAT.
usetest: bool
Flag to indicate whether to use the test/production API server.

Returns
-----------------
pat: str
Personal Access Token to use to authorise a user.

Raises
-------------------
HTTPError, URLError - passed on from is_public method.
&#34;&#34;&#34;

if usetest:
api_host = API_HOST_TEST
else:
api_host = API_HOST_PROD

if not project_id:
pat = click.prompt(
&#39;Please enter your PAT to export all your projects&#39;,
type=str,
hide_input=True
)
elif not exporter.is_public(f&#39;{api_host}/nodes/{project_id}/&#39;):
pat = click.prompt(
&#39;Please enter your PAT to export this private project&#39;,
type=str,
hide_input=True
)
else:
pat = &#39;&#39;

return pat</code></pre>
</details>
<div class="desc"><p>Ask for a PAT if exporting a single project or all projects a user has.</p>
<h2 id="parameters">Parameters</h2>
<pre><code>project_id: str
ID of a single project to export.
If one provided then ask for a PAT.
usetest: bool
Flag to indicate whether to use the test/production API server.
</code></pre>
<h2 id="returns">Returns</h2>
<pre><code>pat: str
Personal Access Token to use to authorise a user.
</code></pre>
<h2 id="raises">Raises</h2>
<pre><code>HTTPError, URLError - passed on from is_public method.
</code></pre></div>
</dd>
</dl>
</section>
<section>
</section>
</article>
<nav id="sidebar">
<div class="toc">
<ul></ul>
</div>
<ul id="index">
<li><h3>Super-module</h3>
<ul>
<li><code><a title="osfexport" href="index.html">osfexport</a></code></li>
</ul>
</li>
<li><h3><a href="#header-functions">Functions</a></h3>
<ul class="">
<li><code><a title="osfexport.cli.prompt_pat" href="#osfexport.cli.prompt_pat">prompt_pat</a></code></li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.11.6</a>.</p>
</footer>
</body>
</html>
Loading