pl8-base 0.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.
pl8_base-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Chen
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,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: pl8-base
3
+ Version: 0.0.1
4
+ Summary: PL8, a lightweight issue tracker backed by DynamoDB
5
+ Keywords: issue-tracker,dynamodb,aws,lambda
6
+ Author: Daniel Chen
7
+ Author-email: Daniel Chen <danieljnchen@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: boto3~=1.43.40
19
+ Requires-Dist: msgspec~=0.21.1
20
+ Requires-Python: >=3.11
21
+ Project-URL: Homepage, https://github.com/dchenstealth/pl8-base
22
+ Project-URL: Repository, https://github.com/dchenstealth/pl8-base
23
+ Project-URL: Issues, https://github.com/dchenstealth/pl8-base/issues
24
+ Description-Content-Type: text/markdown
25
+
26
+ # pl8-base
27
+
28
+ `pl8-base` is the data layer for PL8, a lightweight issue tracker backed by
29
+ DynamoDB. It's a Python library, not a service: it defines PL8's entities
30
+ (`Issue`, `Space`, `IssueBlocker`), its events, and the `BasePL8` manager
31
+ that reads and writes them against a DynamoDB table. It's the source of
32
+ truth for PL8's data model, consumed by the Lambda functions that actually
33
+ run PL8 in AWS.
34
+
35
+ ## Getting started
36
+
37
+ ### Install
38
+
39
+ ```bash
40
+ pip install pl8-base
41
+ ```
42
+
43
+ or with [uv](https://docs.astral.sh/uv/):
44
+
45
+ ```bash
46
+ uv add pl8-base
47
+ ```
48
+
49
+ ### Basic usage
50
+
51
+ `BasePL8` wraps a `boto3` DynamoDB client and a structured logger. Error
52
+ logging passes arbitrary keyword args through to be merged into the log
53
+ record (e.g. `self.logger.error(msg, item=item)`), which a stdlib
54
+ `logging.Logger` rejects — pass an
55
+ [`aws-lambda-powertools`](https://docs.powertools.aws.dev/lambda/python/latest/)
56
+ `Logger` instead (`pip install aws-lambda-powertools` — it's not a dependency
57
+ of this package, since only your logger instance needs it, not `pl8-base`
58
+ itself). It also expects a table already provisioned with `PK`/`SK` and a
59
+ `GSI1` global secondary index (`GSI1PK`/`GSI1SK`) — see
60
+ [Deploying](#deploying) below.
61
+
62
+ ```python
63
+ import boto3
64
+ from aws_lambda_powertools import Logger
65
+
66
+ from pl8_base.manager import BasePL8
67
+ from pl8_base.types import IssueStatus
68
+
69
+ pl8 = BasePL8(
70
+ dynamodb_client=boto3.client("dynamodb"),
71
+ table_name="pl8",
72
+ logger=Logger(),
73
+ )
74
+
75
+ space = pl8.create_space(
76
+ space_id="eng",
77
+ name="Engineering",
78
+ description="Issues for the engineering team",
79
+ )
80
+
81
+ issue = pl8.create_issue(
82
+ space_id="eng",
83
+ title="Fix login bug",
84
+ description="Users can't log in on Safari",
85
+ status=IssueStatus.TODO,
86
+ )
87
+
88
+ pl8.transition_issue(space_id="eng", issue_id=issue.issue_id,
89
+ status=IssueStatus.IN_PROGRESS)
90
+ ```
91
+
92
+ ## Deploying
93
+
94
+ `pl8-base` only talks to a DynamoDB table you already have; it doesn't
95
+ provision or run anything in AWS itself. Work is underway to create other
96
+ repos to scaffold this.
@@ -0,0 +1,71 @@
1
+ # pl8-base
2
+
3
+ `pl8-base` is the data layer for PL8, a lightweight issue tracker backed by
4
+ DynamoDB. It's a Python library, not a service: it defines PL8's entities
5
+ (`Issue`, `Space`, `IssueBlocker`), its events, and the `BasePL8` manager
6
+ that reads and writes them against a DynamoDB table. It's the source of
7
+ truth for PL8's data model, consumed by the Lambda functions that actually
8
+ run PL8 in AWS.
9
+
10
+ ## Getting started
11
+
12
+ ### Install
13
+
14
+ ```bash
15
+ pip install pl8-base
16
+ ```
17
+
18
+ or with [uv](https://docs.astral.sh/uv/):
19
+
20
+ ```bash
21
+ uv add pl8-base
22
+ ```
23
+
24
+ ### Basic usage
25
+
26
+ `BasePL8` wraps a `boto3` DynamoDB client and a structured logger. Error
27
+ logging passes arbitrary keyword args through to be merged into the log
28
+ record (e.g. `self.logger.error(msg, item=item)`), which a stdlib
29
+ `logging.Logger` rejects — pass an
30
+ [`aws-lambda-powertools`](https://docs.powertools.aws.dev/lambda/python/latest/)
31
+ `Logger` instead (`pip install aws-lambda-powertools` — it's not a dependency
32
+ of this package, since only your logger instance needs it, not `pl8-base`
33
+ itself). It also expects a table already provisioned with `PK`/`SK` and a
34
+ `GSI1` global secondary index (`GSI1PK`/`GSI1SK`) — see
35
+ [Deploying](#deploying) below.
36
+
37
+ ```python
38
+ import boto3
39
+ from aws_lambda_powertools import Logger
40
+
41
+ from pl8_base.manager import BasePL8
42
+ from pl8_base.types import IssueStatus
43
+
44
+ pl8 = BasePL8(
45
+ dynamodb_client=boto3.client("dynamodb"),
46
+ table_name="pl8",
47
+ logger=Logger(),
48
+ )
49
+
50
+ space = pl8.create_space(
51
+ space_id="eng",
52
+ name="Engineering",
53
+ description="Issues for the engineering team",
54
+ )
55
+
56
+ issue = pl8.create_issue(
57
+ space_id="eng",
58
+ title="Fix login bug",
59
+ description="Users can't log in on Safari",
60
+ status=IssueStatus.TODO,
61
+ )
62
+
63
+ pl8.transition_issue(space_id="eng", issue_id=issue.issue_id,
64
+ status=IssueStatus.IN_PROGRESS)
65
+ ```
66
+
67
+ ## Deploying
68
+
69
+ `pl8-base` only talks to a DynamoDB table you already have; it doesn't
70
+ provision or run anything in AWS itself. Work is underway to create other
71
+ repos to scaffold this.
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "pl8-base"
3
+ version = "0.0.1"
4
+ description = "PL8, a lightweight issue tracker backed by DynamoDB"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ keywords = [
10
+ "issue-tracker",
11
+ "dynamodb",
12
+ "aws",
13
+ "lambda",
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "boto3~=1.43.40",
27
+ "msgspec~=0.21.1",
28
+ ]
29
+
30
+ [[project.authors]]
31
+ name = "Daniel Chen"
32
+ email = "danieljnchen@gmail.com"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/dchenstealth/pl8-base"
36
+ Repository = "https://github.com/dchenstealth/pl8-base"
37
+ Issues = "https://github.com/dchenstealth/pl8-base/issues"
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "aws-lambda-powertools~=3.35.0",
42
+ "moto~=5.2.2",
43
+ "ruff~=0.16.8",
44
+ "pytest~=9.1.1",
45
+ ]
46
+
47
+ [tool.ruff.lint.per-file-ignores]
48
+ "tests/test_util.py" = ["FURB157"]
49
+
50
+ [tool.pytest.ini_options]
51
+ pythonpath = ["src"]
52
+
53
+ [build-system]
54
+ requires = ["uv_build>=0.12.10,<0.13.0"]
55
+ build-backend = "uv_build"
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "pl8-base"
3
+ version = "0.0.1"
4
+ description = "PL8, a lightweight issue tracker backed by DynamoDB"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Daniel Chen", email = "danieljnchen@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["issue-tracker", "dynamodb", "aws", "lambda"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "boto3~=1.43.40",
25
+ "msgspec~=0.21.1",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/dchenstealth/pl8-base"
30
+ Repository = "https://github.com/dchenstealth/pl8-base"
31
+ Issues = "https://github.com/dchenstealth/pl8-base/issues"
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "aws-lambda-powertools~=3.35.0",
36
+ "moto~=5.2.2",
37
+ "ruff~=0.16.8",
38
+ "pytest~=9.1.1",
39
+ ]
40
+
41
+ [tool.ruff.lint.per-file-ignores]
42
+ # FURB157 would rewrite Decimal("5") as Decimal(5). The cleanup_decimals
43
+ # tests pair an integral case with a fractional one throughout, and only the
44
+ # integral half of each pair can be rewritten, so taking the fix leaves every
45
+ # pair visually mismatched. The string form also mirrors how boto3 hands
46
+ # these values back.
47
+ "tests/test_util.py" = ["FURB157"]
48
+
49
+ [tool.pytest.ini_options]
50
+ pythonpath = [
51
+ "src",
52
+ ]
53
+
54
+ [build-system]
55
+ requires = ["uv_build>=0.12.10,<0.13.0"]
56
+ build-backend = "uv_build"
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,25 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ MAX_ISSUE_ID_LEN = 16
4
+ MIN_ISSUE_ID_LEN = 3
5
+ RETRY_ISSUE_ID_COLLISIONS = 5
6
+
7
+ # Bounded because space_id is caller-supplied and composes both SPACE#{space_id}
8
+ # and ISSUE#{space_id}#{issue_id}. 64 leaves the composite key far inside
9
+ # DynamoDB's 2048 byte limit.
10
+ MAX_SPACE_ID_LEN = 64
11
+
12
+ # Full-jitter exponential backoff for TransactionConflict retries
13
+ TRANSACT_RETRY_ATTEMPTS = 5
14
+ TRANSACT_RETRY_BASE_DELAY = 0.05
15
+ TRANSACT_RETRY_MAX_DELAY = 1.0
16
+
17
+ # CancellationReasons code marking a transient transaction conflict
18
+ TRANSACT_CONFLICT_REASON = "TransactionConflict"
19
+ # CancellationReasons code marking a failed ConditionExpression
20
+ CONDITION_FAILED_REASON = "ConditionalCheckFailed"
21
+ # Error code a failed ConditionExpression raises outside a transaction
22
+ CONDITION_FAILED_CODE = "ConditionalCheckFailedException"
23
+
24
+ # Name of the single GSI on the base table
25
+ GSI1_INDEX_NAME = "GSI1"
@@ -0,0 +1,86 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ class DDBError(Exception):
4
+ """Base class for DDB Issues"""
5
+
6
+
7
+ class DDBInternalError(DDBError):
8
+ """Raised for general service errors"""
9
+
10
+
11
+ class DDBCorruptedError(DDBInternalError):
12
+ """Raised if data read from database is corrupted"""
13
+
14
+
15
+ class DDBMissingError(DDBError):
16
+ """Raised when a requested item does not exist"""
17
+
18
+
19
+ class DDBExistsError(DDBError):
20
+ """Raised when an item already exists"""
21
+
22
+
23
+ class DDBArgsError(DDBError):
24
+ """Raised on invalid arguments passed in"""
25
+
26
+
27
+ class DDBIdCollisionError(DDBError):
28
+ """Raised on ID collision after too many retries"""
29
+
30
+
31
+ class DDBTransactionConflictError(DDBError):
32
+ """Raised on transaction conflict.
33
+
34
+ Transient contention. The same request may be retried unchanged; see
35
+ util.retry_on_transaction_conflict.
36
+ """
37
+
38
+
39
+ class DDBVersionConflictError(DDBError):
40
+ """Raised when a write's version condition fails.
41
+
42
+ The caller's view of the item is stale, so retrying the same request will
43
+ fail again. The caller must re-read and reapply its change.
44
+ """
45
+
46
+
47
+ class DDBTerminalStatusError(DDBError):
48
+ """Raised when an operation would move an Issue out of DONE, or would
49
+ mutate an Issue whose DONE status forbids the change.
50
+
51
+ DONE is terminal; see types.enums.IssueStatus for why that rule is
52
+ load-bearing beyond the product requirement.
53
+ """
54
+
55
+
56
+ class DDBStillBlockedError(DDBError):
57
+ """Raised when an Issue would be transitioned out of BLOCKED while it
58
+ still has active IssueBlockers.
59
+
60
+ The caller must delete the remaining IssueBlockers first, or wait for the
61
+ blocking Issues to reach DONE.
62
+ """
63
+
64
+
65
+ class DDBBlockingIssueDoneError(DDBError):
66
+ """Raised when an IssueBlocker would name a DONE Issue as the blocker.
67
+
68
+ A DONE Issue blocks nothing; the relationship would be created already
69
+ satisfied.
70
+ """
71
+
72
+
73
+ class EventError(Exception):
74
+ """Base class for event issues"""
75
+
76
+
77
+ class EventSendError(EventError):
78
+ """Raised when EventBridge rejects or fails to accept an event.
79
+
80
+ Covers both a failed put_events call and a per-entry failure reported
81
+ back with FailedEntryCount > 0.
82
+ """
83
+
84
+
85
+ class EventCorruptedError(EventError):
86
+ """Raised when an event's Detail is missing/unknown type, or malformed"""