fred-client 0.1.0__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.
- fred_client-0.1.0/.agents/plugins/marketplace.json +20 -0
- fred_client-0.1.0/.claude-plugin/marketplace.json +27 -0
- fred_client-0.1.0/.github/workflows/check.yml +24 -0
- fred_client-0.1.0/.github/workflows/publish.yml +77 -0
- fred_client-0.1.0/.github/workflows/reusable-check.yml +96 -0
- fred_client-0.1.0/.gitignore +33 -0
- fred_client-0.1.0/LICENSE +21 -0
- fred_client-0.1.0/PKG-INFO +385 -0
- fred_client-0.1.0/README.ko.md +354 -0
- fred_client-0.1.0/README.md +357 -0
- fred_client-0.1.0/plugins/fred/.claude-plugin/plugin.json +19 -0
- fred_client-0.1.0/plugins/fred/.codex-plugin/plugin.json +33 -0
- fred_client-0.1.0/plugins/fred/skills/fetch/SKILL.md +29 -0
- fred_client-0.1.0/plugins/fred/skills/fetch/agents/openai.yaml +4 -0
- fred_client-0.1.0/plugins/fred/skills/list/SKILL.md +18 -0
- fred_client-0.1.0/plugins/fred/skills/list/agents/openai.yaml +4 -0
- fred_client-0.1.0/plugins/fred/skills/metadata/SKILL.md +22 -0
- fred_client-0.1.0/plugins/fred/skills/metadata/agents/openai.yaml +4 -0
- fred_client-0.1.0/plugins/fred/skills/search/SKILL.md +34 -0
- fred_client-0.1.0/plugins/fred/skills/search/agents/openai.yaml +4 -0
- fred_client-0.1.0/pyproject.toml +73 -0
- fred_client-0.1.0/src/fred_client/__init__.py +89 -0
- fred_client-0.1.0/src/fred_client/_config.py +92 -0
- fred_client-0.1.0/src/fred_client/_endpoint.py +105 -0
- fred_client-0.1.0/src/fred_client/catalog.py +34 -0
- fred_client-0.1.0/src/fred_client/cli.py +221 -0
- fred_client-0.1.0/src/fred_client/client.py +1447 -0
- fred_client-0.1.0/src/fred_client/errors.py +75 -0
- fred_client-0.1.0/src/fred_client/py.typed +0 -0
- fred_client-0.1.0/src/fred_client/session.py +310 -0
- fred_client-0.1.0/src/fred_client/types.py +148 -0
- fred_client-0.1.0/tests/__init__.py +0 -0
- fred_client-0.1.0/tests/test_catalog.py +44 -0
- fred_client-0.1.0/tests/test_cli.py +332 -0
- fred_client-0.1.0/tests/test_client.py +486 -0
- fred_client-0.1.0/tests/test_config.py +145 -0
- fred_client-0.1.0/tests/test_session.py +499 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fred-client",
|
|
3
|
+
"interface": {
|
|
4
|
+
"displayName": "fred"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "fred",
|
|
9
|
+
"source": {
|
|
10
|
+
"source": "local",
|
|
11
|
+
"path": "./plugins/fred"
|
|
12
|
+
},
|
|
13
|
+
"policy": {
|
|
14
|
+
"installation": "AVAILABLE",
|
|
15
|
+
"authentication": "ON_INSTALL"
|
|
16
|
+
},
|
|
17
|
+
"category": "Productivity"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fred-client",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "seokhoonj"
|
|
5
|
+
},
|
|
6
|
+
"description": "Search and read FRED economic data",
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "fred",
|
|
10
|
+
"displayName": "fred",
|
|
11
|
+
"source": "./plugins/fred",
|
|
12
|
+
"description": "Search and read FRED economic data",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "seokhoonj"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/seokhoonj/fred-client",
|
|
17
|
+
"category": "Productivity",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"fred",
|
|
20
|
+
"alfred",
|
|
21
|
+
"federal reserve",
|
|
22
|
+
"economic data",
|
|
23
|
+
"macroeconomics"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
# A newer push to the same branch or PR makes the older run's result obsolete,
|
|
10
|
+
# so cancel it rather than let both run to completion.
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
# The check only reads the code to lint, type, and test it -- it never writes to the
|
|
16
|
+
# repo -- so it runs with the least privilege that still allows a checkout.
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
check:
|
|
22
|
+
# The full check lives in reusable-check.yml so the release gate in
|
|
23
|
+
# publish.yml runs the identical job on the release commit.
|
|
24
|
+
uses: ./.github/workflows/reusable-check.yml
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publish to PyPI on a GitHub Release, via PyPI Trusted Publishing (OIDC): no API
|
|
4
|
+
# token or password is stored anywhere -- GitHub mints a short-lived identity token
|
|
5
|
+
# that PyPI verifies against the publisher registered for this repo (owner seokhoonj
|
|
6
|
+
# / repo fred-client / this workflow / environment "pypi").
|
|
7
|
+
#
|
|
8
|
+
# Three jobs. `gate` runs the same reusable check that check.yml runs, on the
|
|
9
|
+
# release commit, so a red build (a lint/type error, a broken test, a lost py.typed
|
|
10
|
+
# or console script, an accidental runtime dependency) cannot reach an upload.
|
|
11
|
+
# `build` builds the sdist and wheel and hands them to `publish` as an artifact.
|
|
12
|
+
# `publish` -- the only job holding the OIDC token -- downloads that artifact and
|
|
13
|
+
# uploads it, and checks out no source, so the privileged step runs the least code.
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
release:
|
|
17
|
+
types: [published]
|
|
18
|
+
|
|
19
|
+
# Workflow-level floor: any job without its own `permissions:` block still gets a
|
|
20
|
+
# read-only token, so a job added later cannot silently inherit a write-capable
|
|
21
|
+
# default. The build and publish jobs narrow it further below.
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
gate:
|
|
27
|
+
uses: ./.github/workflows/reusable-check.yml
|
|
28
|
+
|
|
29
|
+
build:
|
|
30
|
+
needs: gate
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
timeout-minutes: 15
|
|
33
|
+
permissions:
|
|
34
|
+
contents: read
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
37
|
+
with:
|
|
38
|
+
persist-credentials: false
|
|
39
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
40
|
+
- name: Confirm the release tag matches the package version
|
|
41
|
+
# The build takes the version from pyproject, not from the tag that fired the
|
|
42
|
+
# release. Assert they agree so a tag/version mismatch fails here instead of
|
|
43
|
+
# publishing a surprising or duplicate version.
|
|
44
|
+
run: |
|
|
45
|
+
tag='${{ github.event.release.tag_name }}'
|
|
46
|
+
version=$(python3 -c "import tomllib, pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
47
|
+
test "$tag" = "v$version" || { echo "release tag $tag != package version v$version" >&2; exit 1; }
|
|
48
|
+
- name: Build sdist and wheel, then check the metadata
|
|
49
|
+
run: |
|
|
50
|
+
uv build
|
|
51
|
+
uvx twine check --strict dist/*
|
|
52
|
+
- name: Confirm the built wheel installs and runs in a clean environment
|
|
53
|
+
# The gate installs a fresh build from source; this exercises the *exact* wheel
|
|
54
|
+
# that gets uploaded, so a wheel that builds but is unusable never reaches PyPI.
|
|
55
|
+
run: |
|
|
56
|
+
uv venv /tmp/dist-check
|
|
57
|
+
uv pip install --python /tmp/dist-check/bin/python dist/*.whl
|
|
58
|
+
/tmp/dist-check/bin/python -c "import fred_client; print('installed', fred_client.__version__)"
|
|
59
|
+
/tmp/dist-check/bin/fred --version
|
|
60
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
61
|
+
with:
|
|
62
|
+
name: dist
|
|
63
|
+
path: dist/
|
|
64
|
+
|
|
65
|
+
publish:
|
|
66
|
+
needs: build
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
timeout-minutes: 15
|
|
69
|
+
environment: pypi
|
|
70
|
+
permissions:
|
|
71
|
+
id-token: write # OIDC; this is what replaces a stored token
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
74
|
+
with:
|
|
75
|
+
name: dist
|
|
76
|
+
path: dist/
|
|
77
|
+
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: reusable check
|
|
2
|
+
|
|
3
|
+
# The full check -- tests, lint, types, and the packaging assertions -- factored
|
|
4
|
+
# into one reusable job so both `check.yml` (on push / PR) and the release gate in
|
|
5
|
+
# `publish.yml` run the *identical* thing. One definition is what makes "publish
|
|
6
|
+
# gates on the full check" true; a second hand-maintained copy would drift, and a
|
|
7
|
+
# check added here silently would not gate releases.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
workflow_call:
|
|
11
|
+
|
|
12
|
+
# The floor lives on the shared body, not only on `check.yml`: `publish.yml`'s gate
|
|
13
|
+
# calls this same workflow, and if the floor were only on `check.yml` the gate would
|
|
14
|
+
# run this check with the repo-default (possibly write) token. Declared here, the
|
|
15
|
+
# check is read-only from every caller. A reusable workflow can only downscope from
|
|
16
|
+
# the caller, so this never widens anyone's token.
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
check:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 15
|
|
24
|
+
strategy:
|
|
25
|
+
fail-fast: false
|
|
26
|
+
matrix:
|
|
27
|
+
# requires-python says >=3.11; test every version the classifiers claim,
|
|
28
|
+
# 3.11 being the floor that claim has to hold on.
|
|
29
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
36
|
+
|
|
37
|
+
- name: Install
|
|
38
|
+
# A venv per matrix version, not `uv pip --system`: the runner's system
|
|
39
|
+
# Python is externally managed and is one fixed version whatever the matrix
|
|
40
|
+
# says. This is also what the README tells a person to run.
|
|
41
|
+
run: |
|
|
42
|
+
uv venv --python ${{ matrix.python-version }}
|
|
43
|
+
uv pip install -e ".[dev]"
|
|
44
|
+
.venv/bin/python -c "import sys; print('testing on', sys.version)"
|
|
45
|
+
|
|
46
|
+
- name: Test
|
|
47
|
+
run: .venv/bin/pytest -q
|
|
48
|
+
|
|
49
|
+
- name: Lint
|
|
50
|
+
run: .venv/bin/ruff check src tests
|
|
51
|
+
|
|
52
|
+
- name: Types
|
|
53
|
+
run: .venv/bin/mypy
|
|
54
|
+
|
|
55
|
+
- name: Confirm there are no runtime dependencies
|
|
56
|
+
# The zero-dependency claim is documented in the pyproject comment; a claim
|
|
57
|
+
# nothing checks quietly stops being true. Installed into a bare environment,
|
|
58
|
+
# fred-client must import and run with nothing beyond the standard library.
|
|
59
|
+
run: |
|
|
60
|
+
uv venv /tmp/bare --python ${{ matrix.python-version }}
|
|
61
|
+
uv pip install --python /tmp/bare/bin/python .
|
|
62
|
+
/tmp/bare/bin/python -c "
|
|
63
|
+
import importlib.metadata as md
|
|
64
|
+
requires = md.requires('fred-client') or []
|
|
65
|
+
# A requirement guarded by 'extra ==' is an optional (dev) dependency, not a
|
|
66
|
+
# runtime one; the runtime set must be empty for the zero-dependency claim.
|
|
67
|
+
runtime = [r for r in requires if 'extra ==' not in r]
|
|
68
|
+
assert runtime == [], f'unexpected runtime dependencies: {runtime}'
|
|
69
|
+
import fred_client
|
|
70
|
+
print('fred_client imports with no third-party dependency present')
|
|
71
|
+
"
|
|
72
|
+
# The console script runs, and its top-level --version prints (not shoved
|
|
73
|
+
# into a required subcommand).
|
|
74
|
+
/tmp/bare/bin/fred --version
|
|
75
|
+
|
|
76
|
+
- name: Confirm a user's type checker can see the hints
|
|
77
|
+
# Every hint in this package is invisible to a user unless py.typed ships
|
|
78
|
+
# alongside it (PEP 561), and the source cannot answer whether it did:
|
|
79
|
+
# src/fred_client/py.typed can sit in git while the built wheel omits it. So
|
|
80
|
+
# ask it the way a user does -- install the built package into a clean
|
|
81
|
+
# environment and run their checker over their code.
|
|
82
|
+
run: |
|
|
83
|
+
uv venv /tmp/typed --python ${{ matrix.python-version }}
|
|
84
|
+
uv pip install --python /tmp/typed/bin/python . mypy
|
|
85
|
+
cat > /tmp/user_code.py <<'PY'
|
|
86
|
+
from fred_client import FRED
|
|
87
|
+
|
|
88
|
+
# series_id is str; an int is not, so a checker that can see the shipped
|
|
89
|
+
# hints must reject it and name the expected type.
|
|
90
|
+
FRED("x" * 32).series.get(123)
|
|
91
|
+
PY
|
|
92
|
+
/tmp/typed/bin/mypy /tmp/user_code.py > /tmp/mypy_out 2>&1 || true
|
|
93
|
+
cat /tmp/mypy_out
|
|
94
|
+
# Match the specific int-vs-str diagnostic, not just any line containing "str",
|
|
95
|
+
# so the check proves the shipped hint was seen rather than passing vacuously.
|
|
96
|
+
grep -q 'incompatible type "int"; expected "str"' /tmp/mypy_out
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.env
|
|
11
|
+
|
|
12
|
+
# uv
|
|
13
|
+
uv.lock
|
|
14
|
+
|
|
15
|
+
# Tooling caches
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Local scratch (never tracked)
|
|
23
|
+
dev/
|
|
24
|
+
refs/
|
|
25
|
+
|
|
26
|
+
# AI coding agents
|
|
27
|
+
CLAUDE.md
|
|
28
|
+
.claude/
|
|
29
|
+
AGENTS.md
|
|
30
|
+
AGENT.md
|
|
31
|
+
.codex/
|
|
32
|
+
GEMINI.md
|
|
33
|
+
.gemini/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Seokhoon Joo
|
|
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.
|