{"id":184,"date":"2010-02-06T20:25:15","date_gmt":"2010-02-07T03:25:15","guid":{"rendered":"http:\/\/www.connorgarvey.com\/blog\/?p=184"},"modified":"2010-03-19T15:23:05","modified_gmt":"2010-03-19T22:23:05","slug":"unit-testing-in-python-with-injection-and-mocking","status":"publish","type":"post","link":"https:\/\/connorgarvey.com\/blog\/?p=184","title":{"rendered":"Python Package Structure with Unit Testing, Injection and Mocking"},"content":{"rendered":"<p><a href=\"http:\/\/www.python.org\/\">Python<\/a> is a language that some people like and use. \u00a0I recently was given a small Python application and had to refactor it so that I could implement unit and functional tests. \u00a0It took me a while to find the resources and learn how to implement them, so I thought I&#8217;d write it up here.<\/p>\n<p><em>* Please note that this is not a unit testing tutorial. \u00a0Those are plentiful and can be found elsewhere. \u00a0This describes setting up a professional Python project with a real package structure, injection and tests.<\/em><\/p>\n<h1>Python Project Structure<\/h1>\n<p>Python was conceived as a scripting language. \u00a0Unlike compiled languages like C or Java, Python files are designed to be directly runnable.<\/p>\n<ul>\n<li><code>RunMe.java -p someargument<\/code> <strong>&lt; <span style=\"color: #800000;\">Not ok!<\/span><\/strong><\/li>\n<li><code>RunMe.py -p someargument<\/code> <strong>&lt; <span style=\"color: #339966;\">Ok!<\/span><\/strong><\/li>\n<\/ul>\n<p>This is convenient for small scripts, but doesn&#8217;t help applications. \u00a0Since you are intended to run <code>.py<\/code> files directly, Python projects should not have source (<code>src<\/code>) or binary (<code>bin<\/code>) folders. \u00a0Having a source folder makes a program difficult to run. \u00a0The root of the Python package structure should be the root of the project.<\/p>\n<p>For the same reason, the <code>test<\/code> folder should not be separate. \u00a0It should be a package in the main project. \u00a0Since the root folder is the root package, you won&#8217;t have much choice.<\/p>\n<h2>Example<\/h2>\n<ul>\n<li><code><span style=\"color: #008080;\">MyProject<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code><span style=\"color: #008080;\">model<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code>user.py<\/code><\/li>\n<\/ul>\n<\/li>\n<li><code><span style=\"color: #008080;\">service<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code>user_service.py<\/code><\/li>\n<li><code>ldap_user_service.py<\/code><\/li>\n<\/ul>\n<\/li>\n<li><code><span style=\"color: #008080;\">test<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code><span style=\"color: #008080;\">unit<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code><span style=\"color: #008080;\">service<\/span><\/code>\n<ul>\n<li><code>__init__.py<\/code><\/li>\n<li><code>user_service_test.py<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<li><code><span style=\"color: #008080;\">functional<\/span><\/code>\n<ul>\n<li><code><span style=\"font-family: monospace;\">__init__.py<\/span><\/code><\/li>\n<li><code><span style=\"color: #008080;\">service<\/span><\/code>\n<ul>\n<li><code><span style=\"font-family: monospace;\">__init__.py<\/span><\/code><\/li>\n<li><code><span style=\"font-family: monospace;\">user_service_test.py<\/span><\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>If you&#8217;re new to Python, please note that those <code>__init__.py<\/code>s are required. \u00a0They tell Python which folders contain Python files. \u00a0This is Python&#8217;s way of allowing you to have other folders, such as config, that don&#8217;t contain source files.<\/p>\n<table style=\"border-collapse: collapse; width: 100%; background-color: #edfcef; border: 1px solid #a1f6a3;\" border=\"1\" cellpadding=\"3\" align=\"right\">\n<tbody>\n<tr>\n<td><span style=\"color: #008000;\"><strong>Note<\/strong><\/span><\/p>\n<p>You should specify exactly one class per file. \u00a0It makes your code easier to find and read.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h1>Injection with snake-guice<\/h1>\n<p>Injection is essential for unit tests. \u00a0In the example above, <code>LDAPUserService<\/code> may connect to an LDAP server to retrieve information, something we don&#8217;t want to happen in unit tests.<\/p>\n<p>I&#8217;ve been using <a href=\"http:\/\/code.google.com\/p\/snake-guice\/\">snake-guice<\/a> and highly recommend it.<\/p>\n<h2>Install snake-guice, Mock and nose<\/h2>\n<p>If you don&#8217;t have it, <a href=\"http:\/\/pypi.python.org\/pypi\/setuptools\">grab a copy of easy install<\/a>. \u00a0Then, <code>$ sudo easy_install.py snake-guice Mock nose<\/code>. \u00a0That installs packages from <a href=\"http:\/\/pypi.python.org\/pypi\">PyPI, the Python Package Index<\/a>. \u00a0For those used to non-scripting languages, this may not seem like a great way to use external libraries. \u00a0Since Python is a scripting language, however, it&#8217;s the only way that makes sense.<\/p>\n<h2>Create a module and use it to instantiate your application<\/h2>\n<div id=\"wpshdo_1\" class=\"wp-synhighlighter-outer\"><div id=\"wpshdt_1\" class=\"wp-synhighlighter-expanded\"><table border=\"0\" width=\"100%\"><tr><td align=\"left\" width=\"80%\"><a name=\"#codesyntax_1\"><\/a><a id=\"wpshat_1\" class=\"wp-synhighlighter-title\" href=\"#codesyntax_1\"  onClick=\"javascript:wpsh_toggleBlock(1)\" title=\"Click to show\/hide code block\">Code block<\/a><\/td><td align=\"right\"><a href=\"#codesyntax_1\" onClick=\"javascript:wpsh_code(1)\" title=\"Show code only\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/code.png\" \/><\/a>&nbsp;<a href=\"#codesyntax_1\" onClick=\"javascript:wpsh_print(1)\" title=\"Print code\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/printer.png\" \/><\/a>&nbsp;<a href=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/About.html\" target=\"_blank\" title=\"Show plugin information\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/info.gif\" \/><\/a>&nbsp;<\/td><\/tr><\/table><\/div><div id=\"wpshdi_1\" class=\"wp-synhighlighter-inner\" style=\"display: block;\"><pre class=\"python\" style=\"font-family:monospace;\"><span class=\"kw1\">class<\/span> ExampleModule:\n  <span class=\"kw1\">def<\/span> configure<span class=\"br0\">&#40;<\/span><span class=\"kw2\">self<\/span>, binder<span class=\"br0\">&#41;<\/span>\n    binder.<span class=\"me1\">bind<\/span><span class=\"br0\">&#40;<\/span>UserService, to=LDAPUserService<span class=\"br0\">&#41;<\/span><\/pre><\/div><\/div>\n<div id=\"wpshdo_2\" class=\"wp-synhighlighter-outer\"><div id=\"wpshdt_2\" class=\"wp-synhighlighter-expanded\"><table border=\"0\" width=\"100%\"><tr><td align=\"left\" width=\"80%\"><a name=\"#codesyntax_2\"><\/a><a id=\"wpshat_2\" class=\"wp-synhighlighter-title\" href=\"#codesyntax_2\"  onClick=\"javascript:wpsh_toggleBlock(2)\" title=\"Click to show\/hide code block\">Code block<\/a><\/td><td align=\"right\"><a href=\"#codesyntax_2\" onClick=\"javascript:wpsh_code(2)\" title=\"Show code only\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/code.png\" \/><\/a>&nbsp;<a href=\"#codesyntax_2\" onClick=\"javascript:wpsh_print(2)\" title=\"Print code\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/printer.png\" \/><\/a>&nbsp;<a href=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/About.html\" target=\"_blank\" title=\"Show plugin information\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/info.gif\" \/><\/a>&nbsp;<\/td><\/tr><\/table><\/div><div id=\"wpshdi_2\" class=\"wp-synhighlighter-inner\" style=\"display: block;\"><pre class=\"python\" style=\"font-family:monospace;\"><span class=\"kw1\">class<\/span> ExampleRunner:\n  user_service = <span class=\"kw2\">None<\/span>\n&nbsp;\n  @inject<span class=\"br0\">&#40;<\/span>user_service=UserService<span class=\"br0\">&#41;<\/span>\n  <span class=\"kw1\">def<\/span> <span class=\"kw4\">__init__<\/span><span class=\"br0\">&#40;<\/span><span class=\"kw2\">self<\/span>, user_service<span class=\"br0\">&#41;<\/span>:\n    <span class=\"kw2\">self<\/span>.<span class=\"me1\">user_service<\/span> = user_service\n&nbsp;\n  <span class=\"kw1\">def<\/span> main<span class=\"br0\">&#40;<\/span>user_service<span class=\"br0\">&#41;<\/span>\n    <span class=\"kw2\">self<\/span>.<span class=\"me1\">user_service<\/span>.<span class=\"me1\">login<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;name&quot;<\/span>, <span class=\"st0\">&quot;password&quot;<\/span><span class=\"br0\">&#41;<\/span><\/pre><\/div><\/div>\n<div id=\"wpshdo_3\" class=\"wp-synhighlighter-outer\"><div id=\"wpshdt_3\" class=\"wp-synhighlighter-expanded\"><table border=\"0\" width=\"100%\"><tr><td align=\"left\" width=\"80%\"><a name=\"#codesyntax_3\"><\/a><a id=\"wpshat_3\" class=\"wp-synhighlighter-title\" href=\"#codesyntax_3\"  onClick=\"javascript:wpsh_toggleBlock(3)\" title=\"Click to show\/hide code block\">Code block<\/a><\/td><td align=\"right\"><a href=\"#codesyntax_3\" onClick=\"javascript:wpsh_code(3)\" title=\"Show code only\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/code.png\" \/><\/a>&nbsp;<a href=\"#codesyntax_3\" onClick=\"javascript:wpsh_print(3)\" title=\"Print code\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/printer.png\" \/><\/a>&nbsp;<a href=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/About.html\" target=\"_blank\" title=\"Show plugin information\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/info.gif\" \/><\/a>&nbsp;<\/td><\/tr><\/table><\/div><div id=\"wpshdi_3\" class=\"wp-synhighlighter-inner\" style=\"display: block;\"><pre class=\"python\" style=\"font-family:monospace;\">injector = Injector<span class=\"br0\">&#40;<\/span>ExampleModule<span class=\"br0\">&#41;<\/span>\nrunner = injector.<span class=\"me1\">get_instance<\/span><span class=\"br0\">&#40;<\/span>ExampleRunner<span class=\"br0\">&#41;<\/span><\/pre><\/div><\/div>\n<p>When <code>get_instance<\/code> is called, <code>injector<\/code> will use the <code>ExampleModule<\/code> to discover bindings. \u00a0Then, it will create an <code>ExampleRunner<\/code>, passing in arguments specified by <code>@inject<\/code>.<\/p>\n<p>The <code>setUp<\/code> method of <a href=\"http:\/\/pyunit.sourceforge.net\/\">PyUnit<\/a> tests should create an injector with a <code>TestExampleModule<\/code>, which should be configured to return mocks.<\/p>\n<div id=\"wpshdo_4\" class=\"wp-synhighlighter-outer\"><div id=\"wpshdt_4\" class=\"wp-synhighlighter-expanded\"><table border=\"0\" width=\"100%\"><tr><td align=\"left\" width=\"80%\"><a name=\"#codesyntax_4\"><\/a><a id=\"wpshat_4\" class=\"wp-synhighlighter-title\" href=\"#codesyntax_4\"  onClick=\"javascript:wpsh_toggleBlock(4)\" title=\"Click to show\/hide code block\">Code block<\/a><\/td><td align=\"right\"><a href=\"#codesyntax_4\" onClick=\"javascript:wpsh_code(4)\" title=\"Show code only\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/code.png\" \/><\/a>&nbsp;<a href=\"#codesyntax_4\" onClick=\"javascript:wpsh_print(4)\" title=\"Print code\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/printer.png\" \/><\/a>&nbsp;<a href=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/About.html\" target=\"_blank\" title=\"Show plugin information\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/info.gif\" \/><\/a>&nbsp;<\/td><\/tr><\/table><\/div><div id=\"wpshdi_4\" class=\"wp-synhighlighter-inner\" style=\"display: block;\"><pre class=\"python\" style=\"font-family:monospace;\"><span class=\"kw1\">class<\/span> TestExampleModule:\n  <span class=\"kw1\">def<\/span> user_service = Mock<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span>\n&nbsp;\n  <span class=\"kw1\">def<\/span> configure<span class=\"br0\">&#40;<\/span><span class=\"kw2\">self<\/span>, binder<span class=\"br0\">&#41;<\/span>\n    binder.<span class=\"me1\">bind<\/span><span class=\"br0\">&#40;<\/span>UserService, to=user_service<span class=\"br0\">&#41;<\/span><\/pre><\/div><\/div>\n<div id=\"wpshdo_5\" class=\"wp-synhighlighter-outer\"><div id=\"wpshdt_5\" class=\"wp-synhighlighter-expanded\"><table border=\"0\" width=\"100%\"><tr><td align=\"left\" width=\"80%\"><a name=\"#codesyntax_5\"><\/a><a id=\"wpshat_5\" class=\"wp-synhighlighter-title\" href=\"#codesyntax_5\"  onClick=\"javascript:wpsh_toggleBlock(5)\" title=\"Click to show\/hide code block\">Code block<\/a><\/td><td align=\"right\"><a href=\"#codesyntax_5\" onClick=\"javascript:wpsh_code(5)\" title=\"Show code only\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/code.png\" \/><\/a>&nbsp;<a href=\"#codesyntax_5\" onClick=\"javascript:wpsh_print(5)\" title=\"Print code\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/printer.png\" \/><\/a>&nbsp;<a href=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/About.html\" target=\"_blank\" title=\"Show plugin information\"><img decoding=\"async\" border=\"0\" style=\"border: 0 none\" src=\"https:\/\/connorgarvey.com\/blog\/wp-content\/plugins\/wp-synhighlight\/themes\/default\/images\/info.gif\" \/><\/a>&nbsp;<\/td><\/tr><\/table><\/div><div id=\"wpshdi_5\" class=\"wp-synhighlighter-inner\" style=\"display: block;\"><pre class=\"python\" style=\"font-family:monospace;\"><span class=\"kw1\">class<\/span> ExampleTest<span class=\"br0\">&#40;<\/span>TestCase<span class=\"br0\">&#41;<\/span>:\n  application = <span class=\"kw2\">None<\/span>\n&nbsp;\n  <span class=\"kw1\">def<\/span> setUp<span class=\"br0\">&#40;<\/span><span class=\"kw2\">self<\/span><span class=\"br0\">&#41;<\/span>:\n    Injector = Injector<span class=\"br0\">&#40;<\/span>TestExampleModule<span class=\"br0\">&#41;<\/span>\n    <span class=\"kw2\">self<\/span>.<span class=\"me1\">application<\/span> = injector.<span class=\"me1\">get_instance<\/span><span class=\"br0\">&#40;<\/span>ExampleApplication<span class=\"br0\">&#41;<\/span>\n&nbsp;\n  <span class=\"kw1\">def<\/span> test_something<span class=\"br0\">&#40;<\/span><span class=\"kw2\">self<\/span><span class=\"br0\">&#41;<\/span>:\n    application.<span class=\"me1\">user_service<\/span>.<span class=\"me1\">search<\/span>.<span class=\"me1\">return_value<\/span> = <span class=\"st0\">&quot;expected&quot;<\/span>\n    actual = application.<span class=\"me1\">user_service<\/span>.<span class=\"me1\">search<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">&quot;any&quot;<\/span><span class=\"br0\">&#41;<\/span>\n    <span class=\"kw2\">self<\/span>.<span class=\"me1\">assertEquals<\/span><span class=\"br0\">&#40;<\/span>expected, actual<span class=\"br0\">&#41;<\/span><\/pre><\/div><\/div>\n<p>Since <code>setUp<\/code> is called before each test, each test method gets a fresh mock that it can configure any way it likes.<\/p>\n<p>Similarly, a functional test module can be created that connects to a local or testing server.<\/p>\n<h1>Mocking with Mock<\/h1>\n<p>I recommend using <a href=\"http:\/\/www.voidspace.org.uk\/python\/mock\/\">Mock<\/a> for mocking, which is why I had you install it earlier. \u00a0Be careful when searching for it because there appear to be two projects called Mock.<\/p>\n<p>The Mock package is well documented. \u00a0Please <a href=\"http:\/\/www.voidspace.org.uk\/python\/mock\/getting-started.html\">go to the Mock site for more information<\/a>.<\/p>\n<h1>Running Tests<\/h1>\n<p>For running tests, I recommend <a href=\"http:\/\/code.google.com\/p\/python-nose\/\">Nose<\/a>. \u00a0To run your tests, open a command line and change to your root project folder. \u00a0Then, nosetests. \u00a0Nose will take care of adding the packages you installed earlier, snake-guice and Mock, to the Python path. \u00a0It will also find all tests, run them and report the results. \u00a0Handy!<\/p>\n<p><code>$ cd ~\/exampleproject<br \/>\n$ nosetests<\/code><\/p>\n<p>To run only unit or functional tests, use the -w argument.<\/p>\n<p><code>$ nosetests -w .\/ .\/test\/unit<br \/>\n$ nosetests -w .\/ .\/test\/functional<\/code><\/p>\n<h1>Is that all?<\/h1>\n<p>To those who have not used injection or mocks for testing, this may seem like a lot of work. \u00a0I hope you&#8217;ll try it, though, because it&#8217;s a one-time setup. \u00a0Once these good practices are in place, you&#8217;ll find that all of your code organization and testing becomes simple and almost automatic. \u00a0If I missed something or if you have any suggestions, please post below.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Python is a language that some people like and use. \u00a0I recently was given a small Python application and had to refactor it so that I could implement unit and functional tests. \u00a0It took me a while to find the resources and learn how to implement them, so I thought I&#8217;d write it up here. [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2}},"categories":[41],"tags":[63,28],"class_list":["post-184","post","type-post","status-publish","format-standard","hentry","category-python","tag-python","tag-test"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1ql2n-2Y","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=184"}],"version-history":[{"count":17,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/184\/revisions"}],"predecessor-version":[{"id":197,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/184\/revisions\/197"}],"wp:attachment":[{"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=184"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=184"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/connorgarvey.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}