Skip to content

Commit

Permalink
Merge pull request #178 from zcutlip/development
Browse files Browse the repository at this point in the history
merge development into main: v4.1.0
  • Loading branch information
zcutlip committed Nov 28, 2023
2 parents da23293 + 69bd202 commit a068906
Show file tree
Hide file tree
Showing 132 changed files with 1,441 additions and 327 deletions.
43 changes: 43 additions & 0 deletions .init/00_pyonepassword
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ then
alias cdpyonepw='cd "$_pyonepasswd_src_root"; ./scripts/archive_op_binary.sh'
fi

_readlink(){ readlink "$1" || echo "$1"; }
# Don't shadow the 'realpath' executable which may be installed on
# some systems (e.g., via homebrew)
_realpath() { _path="$1"; cd "$(dirname "$_path")" && _readlink "$(pwd)"/"$(basename "$_path")"; }

function _local_branches() {
if [ "$PWD" != "$_pyonepasswd_src_root" ];
then
Expand All @@ -27,6 +32,27 @@ function _local_branch_completion(){
_arguments "1:first:($(local_branches "$branch"))"
}

_is_subdir() {
local _ret
local parent_dir sub_dir
parent_dir=$(_realpath "$1")
sub_dir=$(_realpath "$2")
if [ -d "$sub_dir" ];
then
if [[ $sub_dir = $parent_dir/* ]];
then
_ret=0
else
_ret=1
fi
else
echo "sub_dir: $sub_dir is not a directory" >&2
_ret=1
fi
return $_ret
}


function deletebranch_pyop(){
local branch="$1"
if [ "$PWD" != "$_pyonepasswd_src_root" ];
Expand All @@ -38,6 +64,14 @@ function deletebranch_pyop(){
}

function pytest_pyop(){

local _popd=0
if _is_subdir "$_pyonepasswd_src_root" "$PWD";
then
pushd "$_pyonepasswd_src_root"
_popd=1
fi

if [ "$PWD" != "$_pyonepasswd_src_root" ];
then
echo "wrong directory to run 'pytest_pyop'"
Expand All @@ -47,7 +81,16 @@ function pytest_pyop(){
# _nproc="$(nproc)"
# echo "pytest: $_nproc parallel processes"
# pytest -n "$_nproc"

pytest -n auto

if [ $_popd -eq 1 ];
then
dirs
popd
_popd=0
fi

}

function mypy_pyop(){
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

All notable changes to this project will be documented in this file.

## [4.1.0] 2023-11-28

### Added

- Document editing (gh-150):
- `OP.document_edit()`

### Documentation

- Describe document editing in `docs/document-editing.md`
- Added set of document editing examples under `examples/document_editing`

### Misc

Substantial reorganization of `tests/`

## [4.0.0] 2023-11-15

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ For details on editing existing items in a 1Password vault, see [item-editing.md

Also see the examles in [examples/item_editing](examples/item_editing/)

### Document Editing

For details on editing existing document item file contents, see [document-editing.md](docs/document-editing.md)

See examples in [examples/document_editing](examples/document_editing.py)

### More Examples

Lots more examples are available in the `examples` directory
26 changes: 26 additions & 0 deletions docs/document-editing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# PYONEPASSWORD DOCUMENT EDITING

## Description
As of version 4.1.0, `pyonepassword` supports in-place document editing. There is API to match the operations supported by the `op document edit` command.

The API for document editing is the `OP.docuemnt_edit()` method. This method replaces the bytes of an existing document item with the contents of a new file.


## Use and arguments
The `document_edit()` method takes two mandatory arguments:

- `document_identitifer`: A string representing the title or unique ID of a document item
- `file_path_or_document_bytes`: This is what the document should be replaced with. It may be either:
- A `str` or `Path` object referencing existing file on disk to read
- A `bytes` object that is the new file's contents

Additionally, you may *also* change the document item's:

- filename via the `file_name=` kwarg
- title via the `new_title=` kwarg

**Note**: You may not set a new filename or document item title via document_edit() without also specifying a path or bytes to set the document's contents to. This is not supported by `op document edit`. If this behavior is required, the equivalent would be to provide the original document's contents

### Return value

If successful, the `document_edit()` function returns a string representing the unique ID of the document item edited. This may be useful for confirming the expected document item is the one that was edited, in the event a document title was provided.
38 changes: 38 additions & 0 deletions examples/document_editing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path

from pyonepassword import OP


def replace_document():
op = OP()
document_title = "example document 01"
replacement_file_path = "path/to/replacement_image_01.png"

op.document_edit(document_title, replacement_file_path)

# or replacmenet document can be bytes instead of str/Path:
replacement_bytes = open(replacement_file_path, "rb").read()

op.document_edit(document_title, replacement_bytes)


def replace_document_set_title():
op = OP()
document_title = "example document 01"
replacement_file_path = "path/to/replacement_image_01.png"
new_document_title = "updated example document 01"
op.document_edit(document_title, replacement_file_path,
new_title=new_document_title)


def replace_document_set_filename():
op = OP()
document_title = "example document 01"

# replacement path may be Path or str
replacement_file_path = Path("path/to/replacement_image_01.png")

# get basename: "replacement_image_01.png"
new_document_filename = replacement_file_path.name
op.document_edit(document_title, replacement_file_path,
file_name=new_document_filename)
9 changes: 9 additions & 0 deletions ipython_snippets/_util/functions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import pathlib

from pyonepassword import OP
from pyonepassword.logging import console_debug_logger


def snippet_dir():
main_file = pathlib.Path(__file__)
Expand All @@ -12,3 +15,9 @@ def scratch_dir():
scratch = pathlib.Path(main_dir, "scratch")
scratch.mkdir(exist_ok=True)
return scratch


def get_op(logger_name):
logger = console_debug_logger(logger_name)
op = OP(logger=logger)
return op
2 changes: 1 addition & 1 deletion ipython_snippets/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pyonepassword import OP, logging
from pyonepassword._op_cli_config import OPCLIAccountConfig, OPCLIConfig
from pyonepassword._py_op_commands import ( # noqa: F401
from pyonepassword._op_commands import ( # noqa: F401
EXISTING_AUTH_AVAIL,
EXISTING_AUTH_IGNORE,
EXISTING_AUTH_REQD
Expand Down
12 changes: 12 additions & 0 deletions ipython_snippets/document_edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from _util.functions import get_op
from dotenv import load_dotenv

from pyonepassword.api.exceptions import OPCmdFailedException # noqa: F401

load_dotenv("./dot_env_files/.env_pyonepassword_test_rw")

print("run: op = get_op(\"document-edit\")")

vault = "Test Data 2"
document_name = "example document 10"
new_file_path = "working_data/images/replacement_image_10.png"
2 changes: 1 addition & 1 deletion ipython_snippets/pytest/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pyonepassword import OP, logging
from pyonepassword._op_cli_config import OPCLIConfig
from pyonepassword._py_op_commands import EXISTING_AUTH_REQD
from pyonepassword._op_commands import EXISTING_AUTH_REQD
from tests.fixtures.op_fixtures import _setup_alt_env, _setup_normal_env
from tests.fixtures.valid_op_cli_config import (
VALID_OP_CONFIG_NO_SHORTHAND_KEY,
Expand Down
2 changes: 1 addition & 1 deletion ipython_snippets/signin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pyonepassword import OP, logging
from pyonepassword._op_cli_config import OPCLIAccountConfig, OPCLIConfig
from pyonepassword._py_op_commands import ( # noqa: F401
from pyonepassword._op_commands import ( # noqa: F401
EXISTING_AUTH_AVAIL,
EXISTING_AUTH_IGNORE,
EXISTING_AUTH_REQD
Expand Down
31 changes: 0 additions & 31 deletions item-editing-todo.md

This file was deleted.

2 changes: 1 addition & 1 deletion pyonepassword/__about__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "pyonepassword"
__version__ = "4.0.1"
__version__ = "4.1.0"
__summary__ = "A python API to query a 1Password account using the 'op' command-line tool"

"""
Expand Down
14 changes: 7 additions & 7 deletions pyonepassword/_py_op_cli.py → pyonepassword/_op_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ def _should_log_op_errors(cls) -> bool:
return should_log

@classmethod
def _run_raw(cls, argv, input_string=None, capture_stdout=False, ignore_error=False, env=environ):
def _run_raw(cls, argv, input=None, capture_stdout=False, ignore_error=False, env=environ):
stdout = subprocess.PIPE if capture_stdout else None
if input_string:
if isinstance(input_string, str):
input_string = input_string.encode("utf-8")
if input:
if isinstance(input, str):
input = input.encode("utf-8")

_ran = subprocess.run(
argv, input=input_string, stderr=subprocess.PIPE, stdout=stdout, env=env)
argv, input=input, stderr=subprocess.PIPE, stdout=stdout, env=env)

stdout = _ran.stdout
stderr = _ran.stderr
Expand Down Expand Up @@ -90,12 +90,12 @@ def _run_raw(cls, argv, input_string=None, capture_stdout=False, ignore_error=Fa
return (stdout, stderr, returncode)

@classmethod
def _run(cls, argv, capture_stdout=False, input_string=None, decode=None, env=environ):
def _run(cls, argv, capture_stdout=False, input=None, decode=None, env=environ):
cls.logger.debug(f"Running: {argv.cmd_str()}")
output = None
try:
output, _, _ = cls._run_raw(
argv, input_string=input_string, capture_stdout=capture_stdout, env=env)
argv, input=input, capture_stdout=capture_stdout, env=env)
if decode and output is not None:
output = output.decode(decode)
except FileNotFoundError as err:
Expand Down
Loading

0 comments on commit a068906

Please sign in to comment.