{"id":28537548,"url":"https://github.com/python-trio/snekomatic","last_synced_at":"2025-09-11T11:47:59.838Z","repository":{"id":48311811,"uuid":"202060736","full_name":"python-trio/snekomatic","owner":"python-trio","description":"The code behind @trio-bot","archived":false,"fork":false,"pushed_at":"2021-08-02T07:50:52.000Z","size":239,"stargazers_count":21,"open_issues_count":24,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-28T09:26:23.991Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/python-trio.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-08-13T03:56:01.000Z","updated_at":"2024-01-14T00:00:14.000Z","dependencies_parsed_at":"2022-09-05T06:51:31.491Z","dependency_job_id":null,"html_url":"https://github.com/python-trio/snekomatic","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/python-trio/snekomatic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-trio%2Fsnekomatic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-trio%2Fsnekomatic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-trio%2Fsnekomatic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-trio%2Fsnekomatic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/python-trio","download_url":"https://codeload.github.com/python-trio/snekomatic/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-trio%2Fsnekomatic/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263052426,"owners_count":23406103,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-09T18:10:12.343Z","updated_at":"2025-07-08T07:31:57.378Z","avatar_url":"https://github.com/python-trio.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"🐍🐍🐍🤖\n========\n\nLicense: Your choice of MIT or Apache License 2.0\n\nThis is the code behind trio-bot, a Trio-powered github bot for\nhelping us maintain the python-trio projects.\n\n\nFeatures\n========\n\nRight now the main feature is to invite folks to join the org after\ntheir first PR is merged. There are also lots of new features we could\nadd – for more ideas see:\n\n* https://github.com/python-trio/trio/issues/220\n* https://github.com/python-trio/trio/issues/1187\n\nThere's also a nice generic library for writing Github Apps hidden in\nthe ``gh.py`` file – possibly it should get migrated into its own\nproject at some point.\n\n\nRepo setup and permissions\n==========================\n\nThe bot runs on Heroku, and is automatically re-deployed every time a\ncommit lands in the ``master`` branch.\n\nThe production deployment basically has administrator permissions on\nthe python-trio org. If the bot became evil, it could kick us all out\nand take over the project for itself. To reduce the risk of that\nhappening, this repo is a bit more locked down than most of our repos:\nthe code is public and anyone can submit PRs, but branch protection is\nset so that only python-trio administrators can actually merge PRs\ninto master.\n\n\nHacking on snekomatic\n=====================\n\nWhat the heck is a \"Github App\" anyway?\n---------------------------------------\n\nI found the \"Github Apps\" concept super confusing and it took me a\nlong time to figure out how it fit together. So here's an overview to\nhopefully save you that trouble.\n\nLet's say we want to write a program that does automated actions on\nGithub – a \"bot\". Concretely, there are exactly two ways that a bot\ninteracts with Github:\n\n- They can send requests to the Github API, to ask for information or\n  to make changes. This happens whenever the bot wants.\n\n- They can receive webhooks from Github. This lets us get notification\n  that something happened that we might want to react to. Otherwise\n  we'd have to poll the API constantly to watch for changes, which is\n  slow and rude.\n\nThis requires two things:\n\n- Whenever we use the API, we need to specify some kind of user\n  account, so that Github can check permissions and enforce rate\n  limits.\n\n- To receive webhooks, we need to set up a public HTTPS server\n  somewhere, and then tell Github which events it should send. Again,\n  there are some permissions issues here – Github's not going to let\n  me go and subscribe to notifications about everything happening in\n  NSA/top-secret-repository.\n\nNow technically, you don't need a \"Github App\" to do these things. You\ncan use your regular account to make API calls, or you register a\nregular user account for your bot and use that. And if you have\npermissions on a repo, you can go into the settings and set up webhook\nsubscriptions. There are lots of older bots that work this way.\n\nBut, that's pretty ad hoc. And every tech company CEO/investor is\ndeeply envious of the iOS App Store. So Github wanted to make their\nown App store, where folks could list their bots, and then users could\ncome along and start using the bot on their repos with one click (and\nmaybe a credit card). It's convenient for users, provides a revenue\nstream for developers and Github, and increases platform lock-in – a\nwin-win-win!\n\nBut, we're not going to be able to generate all that shareholder value\nif every time you set up a bot, you have to manually register a new\nuser account, send it an invitation to join the repo, keep track of\nthe credentials, and then separately go in and configure some\nwebhooks...\n\nAlso, Github would rather not have folks running armies of thousands\nof individual accounts; that looks an awful lot like what spammers do.\nBut a popular bot can't just use one account, because then you'll run\ninto all kinds of problems with rate limits, plus your customers are\nprobably going to get nervous about giving permissions to an account\nthat *also* has permissions on all their competitors repos. It's all\nvery awkward.\n\nSo that's why \"Github Apps\" were invented. Basically, the \"App\" system\nis a special set of APIs that makes it **easy to set up a bot once and\nuse it on lots of different repos**, and to **manage the permissions\ninvolved in doing this**.\n\nNow you might be thinking: \"but I'm just a regular human, not a\nventure capitalist. I don't care about App stores and credit cards and\nthousands of users; I just want to make a little bot that runs on one\nrepo and makes my life a little easier\". That's cool, imaginary\nreader. In fact you sound suspiciously like me when I started reading\nabout this. And the Github App API can handle our problems too; it's\nbasically the standard way to make bots in general now. But the API\nwon't make any sense if you don't understand the background of what\nit's trying to do.\n\nAnyway, let's make this more concrete. Setting up an App has two\nsteps. First, you **create** the App, by going to:\n\n  https://github.com/settings/apps/new\n\nThere are a ton of fields to fill out there, but basically you're\ngiving your bot a name, and then saying what permissions it will need\nand what webhooks it will want to subscribe to. Also there's a bunch\nof secret-related stuff to let you and Github talk to each other\nsecurely. But what you *aren't* doing here is actually giving your bot\nany permissions, or subscribing to any webhooks, on any particular\nrepos.\n\nThat happens in the next step, when you **install** the App. This name\nis super confusing, because we're not installing anything anywhere.\nWhen you click the \"install\" button, what it actually does is:\n\n- Creates a kind of virtual user account, that your app will use when\n  its accessing *this particular repo or organization*\n- Gives that virtual user account the permissions that you listed\n  during the \"creation\" step *on these repos*\n- Sets up the webhooks that you listed during the \"creation\" step *on\n  these repos*\n\nWhenever you see the Github API docs refer to an \"installation\",\nthat's basically talking about this virtual user. An App can have\nmultiple installations; each one gets its own virtual user account,\nwith its own set of permissions and webhook subscriptions. Each of\n\"installation\" is identified by an opaque string called the\n\"installation id\".\n\nSo how do you manage all these virtual accounts? With another virtual\naccount, of course!\n\nWhen you \"create\" an app, Github creates *another* virtual user\naccount, which we'll call the \"app account\". There's a special\nmechanism for authenticating to the Github API using the app account,\nthat involves a \"JWT\" and a private key that you have to generate on\nthe app configuration screen. Once you've figured that part out, you\ncan make API requests using the app account, *but* the app account is\nsuper locked-down: basically the only operations its allowed to use\nare the ones listed on this page, which are all for managing the\napplication itself:\n\n    https://developer.github.com/v3/apps/\n\nBut! The app account has one *special superpower*: it can take an\n\"installation id\", and `turn it into an authorization token\n\u003chttps://developer.github.com/v3/apps/#create-a-new-installation-token\u003e`__.\nThen you can switch to using *that* token to connect to the Github\nAPI, and that's how you do stuff using the virtual user account that\nwas created for that installation, and it gets its own rate limits,\nand because the permissions for different installations are split up\nit's harder for your bot to get tricked into accessing data it wasn't\nsupposed to.\n\nSo to summarize, each Github App has:\n\n- A template specifying what permissions and webhooks it needs\n- A bunch of virtual accounts created by applying the template\n  permissions/webhooks to a specific set of repos\n- A master \"app account\" that your bot can use to access all those\n  virtual accounts\n\nAnd what if you want to make a simple little private bot just for your\nproject? Then during the \"creation\" phase you tick an extra checkbox\nthat makes it so that only you're allowed \"install\" the app, and\nno-one else can. Everything else is exactly the same.\n\nIt's pretty complicated, but fortunately, we can hide most of the\ncomplexity inside a library.\n\nThe way I'm approaching it for now, is that you create a ``GithubApp``\nobject representing the app as a whole. Its ``.app_client`` attribute\nis a Github client object that uses the app account; but usually, what\nyou want to do is use ``.client_for(installation_id)`` to get a client\nobject that uses the token for that installation id. These clients all\nautomatically handle token renewal, caching, etc., behind the scenes.\nAnd when a webhook is received, we automatically give the handler an\nappropriate client object, so in fact you usually don't have to think\nabout this stuff at all, just use that client and it'll do the right\nthing. See ``gh.py`` for more details.\n\n\nRunning the tests\n-----------------\n\nThe tests are fairly straightforward to run locally, though you do\nneed a Postgres daemon running. The easiest way is to do something\nlike:\n\n1. Clone a source tree from github, and ``cd`` into it\n2. Create a fresh new virtualenv, and activate it\n3. Run: ``pip install -r test-requirements.txt``\n4. Install docker (`Windows\n   \u003chttps://docs.docker.com/docker-for-windows/install/\u003e`__, `macOS\n   \u003chttps://docs.docker.com/docker-for-mac/install/\u003e`__, and I'll\n   assume if you're running Linux on your desktop you can figure out\n   how to install docker on your distro)\n5. Open a new terminal, and run: ``docker run --rm -p 5432:5432\n   -e POSTGRES_HOST_AUTH_METHOD=trust postgres:alpine``\n\n   This will download and start a temporary Postgres daemon inside a\n   container, configured the way we need it. When you're done running\n   tests, you can hit control-C again to shutdown and delete the\n   container.\n6. Back in you first terminal, run: ``pytest``\n\nNote that there are a few tests that contact that Github API directly.\nThe necessary credentials are baked into the testsuite so it should\njust work, but these tests will fail if you're not connected to the\ninternet.\n\n\nTesting against Github for real\n-------------------------------\n\nThe Github credentials we use in the automated test suite are public,\nso they're set up to have basically no permissions at all; we only use\nthem to check we can send requests to Github and parse responses. We\nalso have automated tests for our more complex code paths (like\ninviting people to join the organization!), but they use a fake\nversion of the Github API that just returns canned responses.\n\nThat's great for automated tests to make sure we haven't broken stuff,\nbut when you're developing a new feature you probably want to\nexperiment with running it against Github for real! It's the only way\nto see what Github actually does.\n\nThis is totally doable, but unfortunately it's kind of annoying to set\nup. Luckily, you only have to do it once. And we can use Heroku's free\ntier, so it won't cost you any money.\n\n**Getting started**\n\nFork this repo into your personal Github account, and make a branch to\nstart working on. (Suggestion: for your first change, just add a\n``print`` statement at the top of ``snekomatic.app.main``, so when you\nlook at the program output later you'll be able to confirm that you\nreally are running your branch.) Push your branch to your new Github\nfork.\n\n**Get the code running in the cloud**\n\nSign up for an account at `Heroku \u003chttps://heroku.com\u003e`__.\n\nClick on \"Create new app\", and give your app a name. Maybe\n``\u003cyourname\u003e-snekomatic-test-app``\n\nClick on the \"Deploy\" tab, and scroll down and connect your new Heroku\napp to your Github fork.\n\nScroll down a little more to the \"Automatic deploys\" section, then\nselect your working branch and click \"Enable automatic deploys\". Now\nevery time you push to your work-in-progress branch to Github, Heroku\nwill automatically start running your code on a free cloud VM,\naccessible as ``https://\u003cthe name you chose for your\napp\u003e.herokuapp.com``. If you visit that URL now in your web browser,\nyou should see a short message from snekomatic.\n\nSwitch to the \"Resources\" tab, and scroll down to where it says\n\"Add-ons\". Use the search box to add a \"Heroku Postgres\" add-on (free\nlevel).\n\nThen go back to the Add-ons search box, and add \"Papertrail\", again at\nthe free tier. Once you've done that, your Add-ons list should have an\nentry labeled \"Papertrail\", which is a link. Click on the link, and\nyou'll get a live view of logs from your app, in your web browser. I'd\nrecommend taking a few minutes to look this over and familiarize\nyourself with it. You should see the `print` call that you added\nabove – do you?\n\nCongratulations! You've got a private snekomatic install running in\nthe cloud, for free. Now we need to hook it up to Github.\n\n**Creating a Github App for testing**\n\nSnekomatic is designed to manage a Github organization, so the easiest\nway to test it is to make your own Github organization. Go to\n`Github's page for creating a new organization\n\u003chttps://github.com/organizations/new\u003e`__, and make a new organization\nnamed something like ``\u003cyourname\u003e-test-org``.\n\nOnce you've got an org, click on the \"Settings\" tab, then in the box\non the left you're looking for \"Developer Settings → GitHub Apps\".\n(Note: you *don't* want \"Installed GitHub Apps\" – that's something\nelse!) Then on the right side of your screen there should be a button\nlabeled \"New GitHub App\". Click on the button.\n\nThen GitHub will give you a huge form to fill out. You can skip a lot\nof it, but some parts are important:\n\n*GitHub App name*: Make up a name for your app. It can't match the\nname of any existing GitHub account or org, but it doesn't really\nmatter beyond that. If your bot posts comments, then this is the name\nthat will appear next to them. I'd suggest using the same name you\nused for your app on Heroku.\n\n*Webhook URL*: This has to be: ``https://\u003cyour heroku app\nname\u003e.herokuapp.com/webhook/github``\n\n*Webhook secret*: This is a secret password that your bot and GitHub\nneed to agree on. The easiest thing to do is to open up a Python\ninterpreter, and run ``import secrets; secrets.token_urlsafe()``. Then\npaste the blob of data you get into the form, and also save it for\nlater.\n\n*Permissions*: This control what your app will be able to read/write\non Github. There are separate sections for \"Repository permissions\",\n\"Organization permissions\", and \"User permissions\". Currently the\npermissions snekomatic needs are:\n\n* Under \"Repository permissions\": \"Pull requests: Read \u0026 Write\".\n* Under \"Organization permissions\": \"Organization members: Read \u0026 Write\".\n* Under \"User permissions\": None, you can skip this section entirely.\n\nYou can give your app more permissions if you want; they'll only be to\nyour test org, so it's not particularly dangerous, and can be useful\nfor testing. Also, you can always edit the permissions list again\nlater if you want to change things.\n\n*Subscribe to events*: This selects which events GitHub will notify\nyour bot about. Currently snekomatic just needs \"Pull requests\".\nAgain, you can always change this in the future.\n\n*Where can this GitHub App be installed?*: choose \"Only on this account\".\n\nWhen you're done, click \"Create GitHub App\". You should see a\nconfiguration page for your new GitHub App. At the top it will say\n\"About\", and then give the app name and an \"App ID\" (an integer, like\n38822 or something). Write down that App ID for later.\n\nThen, you have to create a private key. (This is similar to the\n\"Webhook secret\" you made earlier, but different: the \"webhook secret\"\nis how you can tell that webhook notifications are really coming from\ngithub; the \"Private key\" is how github can tell that your API\nrequests are really coming from you.) To do this, you have to scroll\ndown to the bottom of the \"General\" configuration page for your new\nGithub app, and click on the button that says \"Generate a private\nkey\". This will prompt you to download a file named\n``somethingsomething.private-key.pem``. Save that file somewhere for\nlater.\n\nFinally, we need to tell Github that we want to actually *use* the\napp, by \"installing\" it on our organization. Until we do this, it\nwon't actually do anything. On the left side of the app configuration\npages, there should be a box with several options, and one of them is\n\"Install App\". Click on that, and then click to install it on your\norganization. When it asks, tell it to install on \"All repositories\".\nOK! The Github install part is finally done.\n\n(In case you lost the app configuration page, you can find it by going\nto your Github org → Settings → Developer Settings / Github Apps →\nthen clicking \"Edit\" next to your app. You'll probably reference this\npage a lot, so you might want to bookmark it or something.)\n\nNow, last step: we need to go back to Heroku, and finish configuring\nour app, so that it knows how to connect to the Github stuff we just\nset up. Log into Heroku and open up your app. Click on the \"Settings\"\ntab, and find the \"Config Vars\" section. Click on \"Reveal config\nvars\", and then add the following config vars:\n\n* ``GITHUB_APP_ID``: The integer your wrote down earlier, from the top\n  of the Github App configuration page.\n* ``GITHUB_WEBHOOK_SECRET``: The \"webhook secret\" you set earlier.\n* ``GITHUB_PRIVATE_KEY``: Open up that ``blahblah.private-key.pem``\n  file you saved earlier, and paste its full contents into the text\n  field. It should be a bunch of lines, starting with ``-----BEGIN\n  RSA PRIVATE KEY-----``.\n* ``GITHUB_USER_AGENT``: Your Github username. (`Github says\n  \u003chttps://developer.github.com/v3/#user-agent-required\u003e`__ that you\n  have to set a user-agent whenever connecting to the Github API, and\n  gives a few suggestions for what it might look like; this is the\n  simplest.)\n\n**Finishing up**\n\nDo stuff on the repo and watch the webhooks get delivered in your logs!\n\nSorry, that was a lot. If you have any suggestions for how to simplify\nit, please let us know. But the good news is, now you know most of\nwhat you need to to set up your own Github apps on your own projects,\nsince it's pretty much the same process!\n\n**Other tips:**\n\n- Working with webhooks: You'll want to get familiar with the page at\n  ``https://github.com/organizations/\u003cYOUR ORG NAME\u003e/settings/apps/\u003cYOUR BOT NAME\u003e/advanced``\n\n  It's super useful for two reasons. First, you can see which webhooks\n  Github thinks it delivered, including the full payload, and what\n  response it got from your app. If you want to know how Github\n  reports a new PR being opened, then you can open a PR in your test\n  org and then look at this page to see what the webhooks did.\n\n  And second, for each webhook there's a \"Redeliver\" button, which is\n  super handy when debugging. If you make a handler that's supposed to\n  respond to a new PR being created, then probably the first time you\n  try it you'll get some error because you have a typo or whatever.\n  Now your debugging cycle goes: fix your code, push it to Heroku,\n  click the \"Redeliver\" button, and see if your fix worked, repeat.\n  The key thing here is that the \"Redeliver\" button lets you skip\n  creating a new test PR after every fix.\n\n- The `Heroku CLI\n  \u003chttps://devcenter.heroku.com/articles/heroku-cli\u003e`__ is very handy.\n  You can do things like see logs, change config variables, connect\n  directly to your database to poke around, etc.\n\n  If you run the ``heroku`` command inside your local git checkout,\n  then it will automatically try to figure out which heroku app\n  corresponds to this checkout. The way it does this is by looking for\n  a git remote named ``heroku``. So if you do::\n\n    git remote add heroku https://git.heroku.com/\u003cYOUR APP NAME\u003e.git\n\n  Then after that the ``heroku`` command will automatically know which\n  app you're talking about.\n\n- `Sentry \u003chttps://sentry.io/\u003e`__ is also handy, because it lets you\n  get more info on crashes that happen in your app. You should be able\n  to add the free tier as an \"Add-on\" in Heroku, and snekomatic will\n  automatically start delivering crash reports.\n\n\nWhy is it called \"snekomatic\"?\n==============================\n\nIt's kind of an inside joke: the Trio logo (and the bot's avatar) is a\n`triskelion \u003chttps://en.wikipedia.org/wiki/Triskelion\u003e`__ made of\nsnakes – a trisnekion – and one of Trio's original taglines was \"Async\nI/O for Humans and Snake People\". I think of the 🐍 as standing for\nthe friendliness, accessibility, etc. that make Python so welcoming,\nand the bot's purpose is to make the project itself more welcoming and\naccessible, so it just makes sense. Plus it's fun to say.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-trio%2Fsnekomatic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpython-trio%2Fsnekomatic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-trio%2Fsnekomatic/lists"}