pyecos 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.
- pyecos-0.1.0/.agents/plugins/marketplace.json +20 -0
- pyecos-0.1.0/.claude-plugin/marketplace.json +27 -0
- pyecos-0.1.0/.github/workflows/check.yml +25 -0
- pyecos-0.1.0/.github/workflows/publish.yml +61 -0
- pyecos-0.1.0/.github/workflows/reusable-check.yml +102 -0
- pyecos-0.1.0/.gitignore +33 -0
- pyecos-0.1.0/LICENSE +21 -0
- pyecos-0.1.0/PKG-INFO +538 -0
- pyecos-0.1.0/README.en.md +515 -0
- pyecos-0.1.0/README.md +511 -0
- pyecos-0.1.0/plugins/ecos/.claude-plugin/plugin.json +19 -0
- pyecos-0.1.0/plugins/ecos/.codex-plugin/plugin.json +33 -0
- pyecos-0.1.0/plugins/ecos/skills/catalog/SKILL.md +59 -0
- pyecos-0.1.0/plugins/ecos/skills/key-statistics/SKILL.md +56 -0
- pyecos-0.1.0/plugins/ecos/skills/series/SKILL.md +71 -0
- pyecos-0.1.0/pyproject.toml +58 -0
- pyecos-0.1.0/src/pyecos/__init__.py +56 -0
- pyecos-0.1.0/src/pyecos/__main__.py +8 -0
- pyecos-0.1.0/src/pyecos/_cache.py +75 -0
- pyecos-0.1.0/src/pyecos/_config.py +63 -0
- pyecos-0.1.0/src/pyecos/_parse.py +110 -0
- pyecos-0.1.0/src/pyecos/_transport.py +176 -0
- pyecos-0.1.0/src/pyecos/catalog.py +81 -0
- pyecos-0.1.0/src/pyecos/cli.py +245 -0
- pyecos-0.1.0/src/pyecos/client.py +231 -0
- pyecos-0.1.0/src/pyecos/curation/__init__.py +11 -0
- pyecos-0.1.0/src/pyecos/curation/_generated.py +1937 -0
- pyecos-0.1.0/src/pyecos/curation/_indicator.py +155 -0
- pyecos-0.1.0/src/pyecos/data/catalog.tsv.gz +0 -0
- pyecos-0.1.0/src/pyecos/exceptions.py +68 -0
- pyecos-0.1.0/src/pyecos/py.typed +0 -0
- pyecos-0.1.0/src/pyecos/types.py +133 -0
- pyecos-0.1.0/tests/test_catalog.py +89 -0
- pyecos-0.1.0/tests/test_cli.py +225 -0
- pyecos-0.1.0/tests/test_client.py +611 -0
- pyecos-0.1.0/tests/test_config.py +106 -0
- pyecos-0.1.0/tests/test_curation.py +241 -0
- pyecos-0.1.0/tests/test_gen_curation.py +147 -0
- pyecos-0.1.0/tools/curation_design.tsv +126 -0
- pyecos-0.1.0/tools/gen_catalog.py +61 -0
- pyecos-0.1.0/tools/gen_curation.py +205 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pyecos",
|
|
3
|
+
"interface": {
|
|
4
|
+
"displayName": "ecos"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "ecos",
|
|
9
|
+
"source": {
|
|
10
|
+
"source": "local",
|
|
11
|
+
"path": "./plugins/ecos"
|
|
12
|
+
},
|
|
13
|
+
"policy": {
|
|
14
|
+
"installation": "AVAILABLE",
|
|
15
|
+
"authentication": "ON_INSTALL"
|
|
16
|
+
},
|
|
17
|
+
"category": "Productivity"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pyecos",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "seokhoonj"
|
|
5
|
+
},
|
|
6
|
+
"description": "Read Bank of Korea ECOS economic statistics",
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "ecos",
|
|
10
|
+
"displayName": "ecos",
|
|
11
|
+
"source": "./plugins/ecos",
|
|
12
|
+
"description": "Read Bank of Korea ECOS economic statistics",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "seokhoonj"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/seokhoonj/pyecos",
|
|
17
|
+
"category": "Productivity",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"ecos",
|
|
20
|
+
"bank of korea",
|
|
21
|
+
"korea",
|
|
22
|
+
"economic statistics",
|
|
23
|
+
"interest rate"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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. The event name is in the
|
|
11
|
+
# key so a push never cancels a pull-request run, or vice versa.
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
# The check only reads the code to lint, type, and test it -- it never writes to the
|
|
17
|
+
# repo -- so it runs with the least privilege that still allows a checkout.
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
check:
|
|
23
|
+
# The full check lives in reusable-check.yml so the release gate in
|
|
24
|
+
# publish.yml runs the identical job on the release commit.
|
|
25
|
+
uses: ./.github/workflows/reusable-check.yml
|
|
@@ -0,0 +1,61 @@
|
|
|
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 pyecos / 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) cannot reach an upload. `build` builds the sdist and wheel and
|
|
11
|
+
# hands them to `publish` as an artifact. `publish` -- the only job holding the OIDC
|
|
12
|
+
# token -- downloads that artifact and uploads it, and checks out no source, so the
|
|
13
|
+
# 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: Build sdist and wheel, then check the metadata
|
|
41
|
+
run: |
|
|
42
|
+
uv build
|
|
43
|
+
uvx twine check dist/*
|
|
44
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
publish:
|
|
50
|
+
needs: build
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
timeout-minutes: 15
|
|
53
|
+
environment: pypi
|
|
54
|
+
permissions:
|
|
55
|
+
id-token: write # OIDC; this is what replaces a stored token
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
58
|
+
with:
|
|
59
|
+
name: dist
|
|
60
|
+
path: dist/
|
|
61
|
+
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
# Every minor release from the requires-python floor (>=3.11) through the
|
|
28
|
+
# current stable CPython -- the same set the trove classifiers advertise, so
|
|
29
|
+
# each is an audit of the other (no version claimed but untested).
|
|
30
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
37
|
+
|
|
38
|
+
- name: Install
|
|
39
|
+
# A venv per matrix version, not `uv pip --system`: the runner's system
|
|
40
|
+
# Python is externally managed and is one fixed version whatever the matrix
|
|
41
|
+
# says. This is also what the README tells a person to run.
|
|
42
|
+
run: |
|
|
43
|
+
uv venv --python ${{ matrix.python-version }}
|
|
44
|
+
uv pip install -e ".[dev]"
|
|
45
|
+
.venv/bin/python -c "import sys; print('testing on', sys.version)"
|
|
46
|
+
|
|
47
|
+
- name: Test
|
|
48
|
+
run: .venv/bin/pytest -q
|
|
49
|
+
|
|
50
|
+
- name: Lint
|
|
51
|
+
run: .venv/bin/ruff check src tests
|
|
52
|
+
|
|
53
|
+
- name: Types
|
|
54
|
+
run: .venv/bin/mypy src
|
|
55
|
+
|
|
56
|
+
- name: Confirm the only runtime dependency is httpx
|
|
57
|
+
# pandas is an optional extra, so a bare install must pull in nothing but
|
|
58
|
+
# httpx; a second runtime dependency creeping into `dependencies` is caught
|
|
59
|
+
# here rather than in a user's environment.
|
|
60
|
+
run: |
|
|
61
|
+
uv venv /tmp/bare --python ${{ matrix.python-version }}
|
|
62
|
+
uv pip install --python /tmp/bare/bin/python .
|
|
63
|
+
/tmp/bare/bin/python -c "
|
|
64
|
+
import re
|
|
65
|
+
import importlib.metadata as md
|
|
66
|
+
requires = md.requires('pyecos') or []
|
|
67
|
+
runtime = [r for r in requires if 'extra ==' not in r]
|
|
68
|
+
# Each requirement's bare name (before any version/marker/extra), PEP 503
|
|
69
|
+
# normalized; the runtime set must be exactly httpx.
|
|
70
|
+
names = sorted(re.split(r'[<>=!~;\[\s]', r, maxsplit=1)[0].replace('_', '-').lower() for r in runtime)
|
|
71
|
+
assert names == ['httpx'], f'unexpected runtime dependencies: {runtime}'
|
|
72
|
+
import pyecos
|
|
73
|
+
# The bundled offline catalog must ship in the wheel (source-tree tests
|
|
74
|
+
# can't catch a data file dropped from the build).
|
|
75
|
+
from pyecos import catalog
|
|
76
|
+
assert len(catalog.tables()) > 500, 'catalog.tsv.gz missing from the wheel'
|
|
77
|
+
print(f'pyecos imports with only httpx; offline catalog ships {len(catalog.tables())} tables')
|
|
78
|
+
"
|
|
79
|
+
# The console script runs, and its top-level --version prints (not shoved
|
|
80
|
+
# into a required subcommand).
|
|
81
|
+
/tmp/bare/bin/ecos --version
|
|
82
|
+
|
|
83
|
+
- name: Confirm a user's type checker can see the hints
|
|
84
|
+
# Every hint in this package is invisible to a user unless py.typed ships
|
|
85
|
+
# alongside it (PEP 561), and the source cannot answer whether it did:
|
|
86
|
+
# src/pyecos/py.typed can sit in git while the built wheel omits it. So ask
|
|
87
|
+
# it the way a user does -- install the built package into a clean
|
|
88
|
+
# environment and run their checker over their code.
|
|
89
|
+
run: |
|
|
90
|
+
uv venv /tmp/typed --python ${{ matrix.python-version }}
|
|
91
|
+
uv pip install --python /tmp/typed/bin/python . mypy
|
|
92
|
+
cat > /tmp/user_code.py <<'PY'
|
|
93
|
+
from pyecos import ECOS
|
|
94
|
+
|
|
95
|
+
# start takes str | None; an int is neither, so a checker that can see the
|
|
96
|
+
# shipped hints must reject it. If py.typed is missing from the wheel, mypy
|
|
97
|
+
# skips pyecos instead and emits no such error.
|
|
98
|
+
ECOS(api_key="k").fetch_series("722Y001", start=123)
|
|
99
|
+
PY
|
|
100
|
+
/tmp/typed/bin/mypy /tmp/user_code.py > /tmp/mypy_out 2>&1 || true
|
|
101
|
+
cat /tmp/mypy_out
|
|
102
|
+
grep -q 'incompatible type' /tmp/mypy_out
|
pyecos-0.1.0/.gitignore
ADDED
|
@@ -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/
|
pyecos-0.1.0/LICENSE
ADDED
|
@@ -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.
|