Resolve the UN API token from the environment or a per-user file - #1206
Resolve the UN API token from the environment or a per-user file#1206marcelolafleur wants to merge 10 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1206 +/- ##
==========================================
+ Coverage 72.74% 74.07% +1.33%
==========================================
Files 22 22
Lines 5768 5922 +154
==========================================
+ Hits 4196 4387 +191
+ Misses 1572 1535 -37
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
arihantlodha-cmd
left a comment
There was a problem hiding this comment.
Really like this, and I can speak to it from the user side: I hit exactly the problem it fixes. Running OG-Japan I ended up with un_api_token.txt copies in two different directories, and my non-interactive background runs kept tripping the old input() prompt (which then wrote an empty file). The env var, the per-user file, and skipping the prompt when stdin isn't a tty all land on precisely those pain points.
I also verified the expiry decode against a real UN-issued token rather than a synthetic one: my portal token decodes cleanly with un_token_expiry to its expiry date, with aud = data-portal-api. So reading the JWT exp locally works on what the portal actually hands out, and an expired token now gets a clear message instead of silently falling back to the archive, which is exactly how it fails today. Nice touch.
One small thing, non-blocking: unless I missed it, the per-user file is written with default permissions, but it holds a credential, so on a shared machine it's world-readable. Might be worth creating it 0600 (open with mode 0o600, or chmod after write), the way the aws/gh CLIs treat their token files.
getpass instead of input so the token isn't echoed is also a good call. Thanks for this, it's the fix I wanted while working with the token.
|
@arihantlodha-cmd On permissions: the file is set to 600, with a chmod straight after it is created. If you would prefer it created as 600 directly, I can make that change. |
One token per user instead of one per directory. Addresses #1205.
get_un_datalooked forun_api_token.txtin the current working directory, and wrote one there when it didn't find one. So a copy built up in every folder anyone ran from.It now looks in this order, first hit wins:
un_token=argumentUN_API_TOKENenvironment variable$XDG_CONFIG_HOME/og/un_api_token.txt(%APPDATA%\og\on Windows)un_api_token.txtin the working directory — still read, prints a deprecation noticeThe prompt stays, since it's how people find out they need a token, but it now names both ways forward instead of leaving you to guess:
The token is read with
getpass, so it is not echoed into the terminal or its scrollback.masterusedinput()here, which showed the token as you typed it. Answering saves to the per-user file. Either way you are only asked once per machine, not once per folder. The prompt is skipped when stdin isn't interactive, so scheduled runs fall back to the archive instead of waiting on input.People usually get a token later rather than at that moment, so a run that falls back to the archive says once how to register one:
The full path is there because
og-tokenusually isn't on your PATH. The file is named too, in case that is easier. Printed once per run.An expired token used to fail the same way as a missing one, so it now says so:
The portal issues JSON Web Tokens, so the date is read from the token itself with no extra request and no signature check — the portal is still the authority on whether a token works. A token that isn't a readable JWT is used exactly as before, with no expiry shown. This matters more than it sounds: every token file on my machine had already expired, one of them 456 days ago, and nothing had ever said so.
There's also an
og-tokencommand so nobody has to hunt for the file:It reads the token without echoing, and never prints it back.
Two things to flag rather than have you find them:
[project.scripts]entry. It's a separate commit, so it's easy to drop if you'd rather not take that on.platformdirs, which is only an indirect dependency today.Version bumped to 0.20.0 with a dated changelog section, following #1190. Happy to change the number.
Tested
input(), since that would echo the token; another checks the expiry parser returns None rather than raising on anything that isn't a readable JWT.Authorizationheader, not just that the resolver returns the right string.og-tokenend to end from an installed console script: saves, strips aBearerprefix, writes the file0600, prints only the last four characters, and gives a usage error on a bad argument.UN_API_TOKENset,get_un_data("47", country_id="608")returns 100 rows for ages 0-99 with no fallback to the archive, so the resolved token really does reach the request.og-tokenlands on PATH, andget_pop_objswith no token anywhere falls back to the archive and solves as before, without prompting or leaving a token file behind. An existingun_api_token.txtin the working directory still resolves, with the deprecation notice.ruff format --check .andruff check .both clean. 48 demographics tests pass under CI's-m "not local and not benchmark", plus 485 across the rest of the suite.%APPDATA%path,os.chmod, andog-token.exeinScripts/differ from what I could test locally.local-marked tests. The docs change adds the three new public functions to the existing:members:list, and I checked every name in that list resolves and has a docstring.og-tokenimportsogcore, so it pays about 1.2s of import before doing anything. Fine for a command run once, but I didn't try to avoid it.