[{"content":"","date":"2025-11-23","externalUrl":null,"permalink":"/posts/","section":"Blog Posts","summary":"","title":"Blog Posts","type":"posts"},{"content":" A Review of Michael Kennedy\u0026rsquo;s book, \u0026ldquo;Talk Python in Production\u0026rdquo; # I recently had the opportunity to read the book Talk Python in Production by Michael Kennedy. He is the host of the \u0026ldquo;Talk Python To Me,\u0026rdquo; and cohost of the \u0026ldquo;Python Bytes\u0026rdquo; podcast. The site he built for the podcasts also hosts his online training courses.\nThis review reflects my thoughts on the book’s content—how it aligns with my own experience as a working developer, and how I expect to use its ideas in my day-to-day work.\nBy way of background, I\u0026rsquo;m a backend-focused software developer building Python-based APIs on AWS. I occasionally do frontend work as well, currently using SvelteKit. I\u0026rsquo;m also the author of The Well-Grounded Python Developer from Manning Publications.\nCutting To The Chase # Much of the book presents ideas and material to support Michael\u0026rsquo;s coining of a new technology term, \u0026ldquo;Stack Native.\u0026rdquo; This is based on his own experience creating the Talk Python To Me sites and services, and their evolution over time. In many ways the choices he made and the path followed echoes The Zen of Python.\nSimple is better than complex\nComplex is better than complicated\nRather than diving head long into what is referred to as \u0026ldquo;Cloud Native,\u0026rdquo; and adopting the many services offered by the big cloud providers, Michael has taken the practical path of recognizing the application stacks he needs to realize his goals, and taking the simpler steps to reach them.\nHe also makes a compelling point: very few of us are Google or Facebook, serving billions of requests per day and needing essentially 100% uptime. This changes the conversation presented by Cloud Native adherents, and the venders supplying it.\nDon\u0026rsquo;t Get Me Wrong # I\u0026rsquo;m not saying I want to go back to a world of on-premisis hardware, networking, power, cooling, no way! That was fun at the time, but that fun has worn out it\u0026rsquo;s welcome! I absolutely want the applications I\u0026rsquo;m working to be hosted in modern data centers with all the bells, whistles, reliability and redundancy that brings with it.\nWhat I do have questions about is buying into all the services cloud providers have and charge for. I\u0026rsquo;ve built systems that use serverless, managed queues, managed databases, routing, API Gateways, step functions, and more. All of that has been interesting and educational\u0026ndash;but I sometimes question if I\u0026rsquo;ve traded the manageable complexity of a server with a set of open-source services, for the sprawling, disparte complexity of infrastructure as code of Cloud Native solutions.\nThe Core Idea: Simplicity Wins # The central thesis of the book is refreshing: you probably don\u0026rsquo;t need the complexity of a massive cloud architecture. Michael argues for what he calls the \u0026ldquo;One Big Server\u0026rdquo; approach. Instead of managing a fleet of tiny, underpowered micro-instances or getting locked into expensive Platform-as-a-Service (PaaS) offerings, you get one robust dedicated server (or a large VPS) and run everything there.\nThis resonated with me, as I’ve been part of projects awash in the complexity of AWS services—S3, Lambda, RDS, VPCs—when a single Linux box could have handled the load without breaking a sweat.\nWhat is meant by this is that a service can scale to almost the full potential of a large server because the collection of services hosted on the box will rarely contend with each other for CPU, memory, database access, etc.\nThis is opposed to having each service running on it\u0026rsquo;s own small linux box in isolation. In this case the service can only scale to the resource limits of the one small linux box.\nDocker is the Key # Of course, dumping everything onto one server can be a mess if you\u0026rsquo;re not careful. That\u0026rsquo;s where Docker comes in. The book details how to use Docker and Docker Compose to isolate applications.\nIsolation: Each app thinks it has the machine to itself. Portability: You can move that \u0026ldquo;One Big Server\u0026rdquo; from DigitalOcean to Hetzner (a move the author recommends for cost savings) without rewriting your code. Simplicity: No Kubernetes. Just Docker Compose. I appreciate this distinction. Kubernetes is amazing technology, but for a small team or a solo developer, it\u0026rsquo;s often overkill. And more than likely dependent on having access to a DevOps team. It adds a layer of operational complexity that takes time away from building the actual software.\nPracticality Over Hype # Michael breaks down the costs clearly. He shows how he runs the entire Talk Python landscape—podcasts, courses, mobile APIs—for roughly $100/month. That’s impressive. It challenges the default assumption that \u0026ldquo;going to the cloud\u0026rdquo; means \u0026ldquo;paying a lot of money for infinite scalability we might never need.\u0026rdquo;\nBeyond costs in terms of money, he presents the work he took on, and what it meant to bring it to reality at each step of his journey. This is accompanied by detailed examples of what he built, how he built it, and why. I found this particularly useful as I fully intend to capitalize on what he\u0026rsquo;s presented in my own work.\nI may not be in a position to fully adopt \u0026ldquo;Stack Native,\u0026rdquo; but it\u0026rsquo;s principles will definitely inform the choices I make going forward as a software developer.\nWho Should Read This? # If you are a Python developer who wants to get your work out into the world without needing a PhD in AWS, this book is for you. It’s a practical, opinionated guide that cuts through the hype.\nClosing Thoughts # Not everything in the book will be valuable in every situation. But, I think you\u0026rsquo;ll find the practical approach taken and presented invaluable to your own goals.\nHighly recommended.\n","date":"2025-11-23","externalUrl":null,"permalink":"/posts/my_review/","section":"Blog Posts","summary":"","title":"Review: Talk Python in Production","type":"posts"},{"content":"Welcome to my site! This is a place where I can write about things that interest me, share links to things I\u0026rsquo;ve written about elsewhere, and other stuff that I think are worth sharing. This is absolutely a work in progress, so things will be shifting around as I figure it out.\nRight now, the things I want to include here are my writing, various projects I\u0026rsquo;m working on, The Adventures of Henry, and art. This last one I\u0026rsquo;m trying to get back into after a long hiatus, and will be mostly about painting.\n","date":"2025-11-23","externalUrl":null,"permalink":"/","section":"Welcome to My Site","summary":"","title":"Welcome to My Site","type":"page"},{"content":"I created a FastAPI example application to use as part of a presentation at work. That application creates a REST API for the Chinook SQLite database from the SQLite Tutorial site, so the data isn\u0026rsquo;t anything proprietary.\nThe bulk of this post is the README.md file from the GitHub repository.\nFastAPI Presentation # Introduction # This repository contains the FastAPI application I created for a presentation I gave at work. My goal for this repository is to make it a useful tool to help developers get up to speed with the FastAPI framework to create APIs.\nFastAPI # What Is FastAPI # FastAPI is a modern, high-performance web framework for building APIs with Python. It is an asynchronous framework that can handle many requests concurrently. It also takes good advantage of type hints to aid with development.\nWhy Use FastAPI # The framework has many attractive features:\nIt has very high performance, comparable to NodeJS and Go Developers gain coding performance improvements Editors and IDEs have great support for FastAPI It\u0026rsquo;s a robust platform for creating production-ready code Based on open standards for APIs, OpenAPI, previously known as Swagger Who Is Using FastAPI # Companies like Microsoft, Uber, Netflix, Expedia Group, and Cisco use FastAPI.\nExample Application # The code making up the bulk of this repository is a FastAPI web application that creates CRUD REST APIs to access a database. Rather than create a database from scratch, I used the Chinook database from the SQLite Tutorial website. This is a great resource to learn SQL and has a download link to the database.\nI used the Chinook SQLite database for a couple of reasons:\nI didn\u0026rsquo;t have to build a database and populate it with data, which was a time-saver. Because it\u0026rsquo;s an \u0026ldquo;in process\u0026rdquo; database engine, it wasn\u0026rsquo;t necessary to set up and configure another database engine, like MySQL, PostgreSQL, or SQL Server. The database has tables with one-to-many, many-to-many, and self-referential hierarchical tables, which I used to display FastAPIs abilities. The ERD (Entity Relationship Diagram) of the database looks like this:\nREST Conventions # REST is more of a convention than a standardized protocol, and I used my conventions to create the REST URL endpoints.\nThe endpoints define a collection of \u0026ldquo;things\u0026rdquo; and access to a single \u0026ldquo;thing.\u0026rdquo; Because they are things, nouns are used as names. I am careful when naming things to avoid awkward plural and singular nouns.\nThe CRUD behaviors are mapped to these HTTP method verbs:\nCRUD Method HTTP Method URL Endpoint Action on a thing Create POST /api/v1/things Create new thing Read GET /api/v1/things Read a collection of things Read GET /api/v1/things/{id} Read singular thing from the collection as a URI (Universal Resource Identifier) Update PUT /api/v1/things/{id} Update entire thing Update PATCH /api/v1/things/{id} Partially update thing Note In this application, there is no Delete functionality. It\u0026rsquo;s generally a bad idea to delete data from a database. I prefer to have something like an active flag that can be True or False to include or exclude the item from the interface. To do this would have meant modifying the Chinook database to add an active flag. Doing that would have made it more difficult to reset the database back to its default state, so I chose not to add delete functionality to the API.\nSQLModels # To reduce code duplication, the SQLModel library interacts with the database and combines SQLAlchemy and Pydantic nicely. Here is the albums table schema as defined by the database:\ncreate table albums ( AlbumId INTEGER not null primary key autoincrement, Title NVARCHAR(160) not null, ArtistId INTEGER not null references artists ); create index IFK_AlbumArtistId on albums (ArtistId); This nicely describes the albums table schema and shows it contains a primary key named AlbumIId, a Title string 160 characters long, and a foreign key called ArtisId that references the primary key of another table called artists. It also creates an index on the ArtistId column to improve performance on join operations.\nThis works fine, but I want to change how my application works with the table without changing the albums table schema.\nI don\u0026rsquo;t care for the naming convention used in the Chinook database for column names, and I\u0026rsquo;ll map them to the naming conventions I prefer. In particular, I like to use just id for the primary key name. The above schema creates a primary key name like thisalbums.AlbumId , which I feel is redundant. I prefer this albums.id. Doing this also lets me recognize foreign keys quickly as they would be in this form for the albums table: albums.artist_id. from typing import Optional, List from functools import partial from sqlalchemy import Column, Integer, Index, ForeignKey from sqlmodel import SQLModel, Field, Relationship from pydantic import ConfigDict from .fields import ValidationConstant, create_string_field ## create a specialized sqlmodel Field class that provides validation for the album title TitleField = partial( create_string_field, \u0026#34;Album Title\u0026#34;, \u0026#34;The title of the album\u0026#34;, ValidationConstant.STRING_160, ) class AlbumBase(SQLModel): \u0026#34;\u0026#34;\u0026#34; This is the base class for the Album model. All fields that are common to any derived classes are defined here. \u0026#34;\u0026#34;\u0026#34; title: str = TitleField(mapped_name=\u0026#34;Title\u0026#34;) artist_id: int = Field( sa_column=Column(\u0026#34;ArtistId\u0026#34;, Integer, ForeignKey(\u0026#34;artists.ArtistId\u0026#34;)), ) class Album(AlbumBase, table=True): \u0026#34;\u0026#34;\u0026#34; This is the Album model class. It represents an album in the database. It inherits from AlbumBase and is mapped to the \u0026#34;albums\u0026#34; table in the database. Notice the id primary key is marked as Optional, this is because the id is not provided by the user, it is generated by the database. Notice too the artist and tracks relationships, these are defined as lists of objects. These values don\u0026#39;t exist in the database but are used to represent the related objects, and are populated by SQLAlchemy when the object is read from the database as part of an ORM operation. \u0026#34;\u0026#34;\u0026#34; __tablename__ = \u0026#34;albums\u0026#34; id: Optional[int] = Field( default=None, sa_column=Column(\u0026#34;AlbumId\u0026#34;, Integer, primary_key=True), description=\u0026#34;The unique identifier for the album\u0026#34;, ) # Define the relationship to Artist artist: \u0026#34;Artist\u0026#34; = Relationship(back_populates=\u0026#34;albums\u0026#34;) # Define the relationship to Tracks tracks: List[\u0026#34;Track\u0026#34;] = Relationship(back_populates=\u0026#34;album\u0026#34;) model_config = ConfigDict(from_attributes=True) # make the model aware of the index on the artist_id column __table_args__ = (Index(\u0026#34;IFK_AlbumArtistId\u0026#34;, \u0026#34;ArtistId\u0026#34;),) ## Create operation class AlbumCreate(AlbumBase): pass ## Read operation class AlbumRead(AlbumBase): id: int model_config = ConfigDict(from_attributes=True) ## Update operation (Put) class AlbumUpdate(AlbumBase): pass ## Patch operation class AlbumPatch(AlbumBase): title: Optional[str] = TitleField(default=None) from .artists import Artist # noqa: E402 from .tracks import Track # noqa: E402 The Other Models # Here is a list of the other sqlmodels in the application:\nalbums - which was covered about. This has all the albums in the database, and is related to artists artists - This has all the artists in the database and is the parent of the albums models. customers - This has all the customers in the system and holds all the customer information; name, address, etc.. It has a parent relationship to invoices. invoices - This has the order invoices for what has been purchased and has a child relationship with customers. invoice_items - This has all the items that make up an invoice and has a child relationship with invoices. It also has a child relationship with tracks. tracks - This has all the track information about a song, like a composer, length, size in bytes, etc.. genres - This contains genre or music type and has a parent relationship with tracks. media_type - This contains information about how the music is encoded, such as MPEG, AAC, etc., and has a parent relationship with tracks. playlists - This contains the name of a playlist and has a many-to-many relationship with tracks playlist_track - This association table creates the many-to-many relationship between playlists and tracks. employees - This is a hierarchal table holding all the employees in the application and the reports-to relationships between them. It also has a parent relationship with customers. The sqlmodel definitions follow patterns similar to those of albums described above. The differences account for the different fields in each table and their relationships.\nURL Endpoint Routes # Creating A Generic Route # Because of the naming conventions I used on the sqlmodel classes and the mapping applied to the database fields, it was possible to create generic route handling for most of the REST functionality I wanted in the application. I\u0026rsquo;ll try to illustrate this by working backward from the generic create_item_route route function to where the the route is added to FastAPI. Here is the code of the create_item_route route function:\ndef create_item_route( router: APIRouter, model: ModuleType, ): \u0026#34;\u0026#34;\u0026#34; Create the generic create item route in the router parameter for the model parameter \u0026#34;\u0026#34;\u0026#34; # takes advantage of the plural/singular naming conventions prefix, prefix_singular, class_name = get_model_names(model) @router.post( \u0026#34;/\u0026#34;, response_model=CombinedResponseCreate[getattr(model, f\u0026#34;{class_name}Read\u0026#34;)], status_code=status.HTTP_201_CREATED, ) async def create_item( data: getattr(model, f\u0026#34;{class_name}Create\u0026#34;), db: AsyncSession = Depends(get_db), ): \u0026#34;\u0026#34;\u0026#34; The generic create item (class_name) for the route :params data: the Create sqlmodel definition :db AsyncSession: the asynchronous database session to use \u0026#34;\u0026#34;\u0026#34; async with db as session: db_item = await crud.create_item( session=session, data=data, model_class=getattr(model, f\u0026#34;{class_name}\u0026#34;), ) if db_item is None: raise HTTPException( status_code=400, detail=f\u0026#34;{class_name} already exists\u0026#34;, ) return CombinedResponseCreate( meta_data=MetaDataCreate(), response=db_item, ) This function initially generates the prefix, prefix_singular, and class name from the model parameter. The get_model_names() function capitalizes on the naming convention to generate these values from the model. The route POST operation (the CRUD Creation HTTP method) decorates the nested create_item handler function mapped to the endpoint URL.\nThe remainder of the function is pretty standard asynchronous code to create an item in the database. It uses the getattr function to get the actual model class from the class_name string. It calls the crud.create_item method to interact with the database and persist the item to the database. The crud.* methods are shown later in this document.\nThe other routes to read a collection of items, read a single item, update an item, and partially update an item all follow the same pattern.\nBy doing this, the application can pass any module containing a sqlmodel definition that follows the naming convention, and a complete CRUD route will be created.\nBuilding A Route # The functionbuild_routes creates the complete set of routes for a model. That function is shown here:\ndef build_routes( model: ModuleType, child_models: List[ModuleType], ) -\u0026gt; APIRouter: \u0026#34;\u0026#34;\u0026#34; This function builds all the CRUD routes for the passed in model (artist, albums, etc.). It creates a router for all the CRUD routes, and passes that and the model to functions to create the different routes. :params ModuleType: the module containing the model definitions :params List[ModuleType]: the list of modules containing child model definitions :returns APIRouter: a populated router FastAPI will handle \u0026#34;\u0026#34;\u0026#34; # takes advantage of the plural/singular naming conventions prefix, _, _ = get_model_names(model) tags = prefix.title().replace(\u0026#34;_\u0026#34;, \u0026#34; \u0026#34;) # create a router for the model router = APIRouter( prefix=f\u0026#34;/{prefix}\u0026#34;, tags=[f\u0026#34;{tags}\u0026#34;], responses={404: {\u0026#34;description\u0026#34;: \u0026#34;Not found\u0026#34;}}, dependencies=[Depends(get_db)], ) # create the endpoint routes params = { \u0026#34;router\u0026#34;: router, \u0026#34;model\u0026#34;: model, } create_item_route(**params) get_items_route(**params) get_item_route(**params) update_item_route(**params) patch_item_route(**params) # add the child modules for the specialized children routes params.update({\u0026#34;child_models\u0026#34;: child_models}) children.get_routes(**params) return router The build_routes function takes in a model ModuleType and any child_models related to the model. It generates an instance of the router all of the CRUD methods will attach to. Then each CRUD creation function is called, in turn, receiving the router and the model to use.\nIt also calls the children.get_routes function to create the children routes. This handles routes like this:\n/api/v1/things/{id}/children_things\nI haven\u0026rsquo;t figured out a way to make these generic because some of the children routes use many-to-many relationships or are built using hierarchical tables. Hopefully, I\u0026rsquo;ll get some time to work this out and update the repository when I do. Until then, oh well.\nServing The Routes # At a high level, a FastAPI application serves the routes it knows about. This application is no different, except the routes are created from a data structure instead of individually. This is handled by the main.py code by two functions, app_factory and get_routes_config. The first, app_factory creates the FastAPI app instance that uvicorn runs to get the application moving. Most of the code is focused on initializing the fastapi_app instance and adding some middleware. The last part is a loop that iterates over what\u0026rsquo;s returned by get_routes_config and uses that to add routes to the fastapi_app instance using the build_routes function shown above.\ndef app_factory(): \u0026#34;\u0026#34;\u0026#34; Creates the FastAPI application object and configures it for CORS :return: FastAPI application object \u0026#34;\u0026#34;\u0026#34; fastapi_app: FastAPI = FastAPI( title=\u0026#34;FastAPI Presentation API\u0026#34;, description=__doc__, version=\u0026#34;1.0.0\u0026#34;, openapi_url=\u0026#34;/openapi.json\u0026#34;, lifespan=lifespan, debug=True, ) # add CORS middleware fastapi_app.add_middleware( CORSMiddleware, allow_origins=[\u0026#34;*\u0026#34;], allow_credentials=True, allow_methods=[\u0026#34;*\u0026#34;], allow_headers=[\u0026#34;*\u0026#34;], ) fastapi_app.add_middleware(BaseHTTPMiddleware, dispatch=log_middleware) fastapi_app.add_middleware(MetadataMiddleware) # add all the endpoint routes for route_config in get_routes_config(): fastapi_app.include_router(build_routes(**route_config), prefix=\u0026#34;/api/v1\u0026#34;) return fastapi_app def get_routes_config() -\u0026gt; Dict: \u0026#34;\u0026#34;\u0026#34; Returns all the routes configuration for the application :return: Dict of router info \u0026#34;\u0026#34;\u0026#34; return [ {\u0026#34;model\u0026#34;: artists, \u0026#34;child_models\u0026#34;: [albums]}, {\u0026#34;model\u0026#34;: albums, \u0026#34;child_models\u0026#34;: [tracks]}, {\u0026#34;model\u0026#34;: tracks, \u0026#34;child_models\u0026#34;: [invoice_items, playlists]}, {\u0026#34;model\u0026#34;: genres, \u0026#34;child_models\u0026#34;: [tracks]}, {\u0026#34;model\u0026#34;: media_types, \u0026#34;child_models\u0026#34;: [tracks]}, {\u0026#34;model\u0026#34;: playlists, \u0026#34;child_models\u0026#34;: [tracks]}, {\u0026#34;model\u0026#34;: invoices, \u0026#34;child_models\u0026#34;: [invoice_items]}, {\u0026#34;model\u0026#34;: invoice_items, \u0026#34;child_models\u0026#34;: []}, {\u0026#34;model\u0026#34;: customers, \u0026#34;child_models\u0026#34;: [invoices]}, {\u0026#34;model\u0026#34;: employees, \u0026#34;child_models\u0026#34;: [customers, employees]}, ] ## Initialize and create the application app = app_factory() The get_routes_config returns a list of dictionaries with the keys model and child_models. The model value is the module\u0026rsquo;s name containing the sqlmodel definitions for the database tables and the mappings applied. For example, the model artists refer to the project/app/models/artists.py Python module. The child_models key value is a list of models associated with the model as children. For example, the artists model has [albums] because of the one-to-many relationship between artists and albums.\nThe loop that iterates over what get_routes_config returns, passes that to build_routes to build the routes dynamically. This means that if more tables were added to the database, so long as the sqlmodel classes defined for that table followed the naming conventions used, that table could be added to the routes by including it as a data structure in get_routes_config.\nCreating A Route # Each route created by the build_routes function is handled by a generic function. The create_item_route function was shown earlier in this text.\nThe create_item_route creates the route using the router passed to it and defines the response it will return. The @router.post decorates the nested create_item function, the Create activity handler function. The parameters to this function are data, which is the intended model\u0026rsquo;s Create class, and db, the asynchronous database connection.\nThe model Create data instance is passed to the crud.create_item function to create the new item and persist it to the database. The db_item is the item in the database updated with the primary_key id value of the newly created item. This is returned in an instance of the CombinedResponseCreate class. The CombinedResponseCreate class, part of how meta_data is attached to the response, will be shown later in this document.\nException Cases # As mentioned, I haven\u0026rsquo;t yet figured out how to make the children routes generic. For those routes, I created individual routes that also included interaction with the database to save some time. For example, I\u0026rsquo;ll show the track to playlist handler, which uses the many-to-many relationship between tracks and playlists.\ndef _child_track_playlist_handler(router: APIRouter): @router.get( path=\u0026#34;/{id}/playlists\u0026#34;, response_model=CombinedResponseReadAll[List[PlaylistRead], int], ) async def read_track_playlists( id: int, offset: int = 0, limit: int = 10, db: AsyncSession = Depends(get_db), ) -\u0026gt; [List[PlaylistRead], int]: \u0026#34;\u0026#34;\u0026#34; Retrieve a Track from the database with a paginated list of associated playlists \u0026#34;\u0026#34;\u0026#34; async with db as session: query = ( select(Playlist) .join( PlaylistTrack, PlaylistTrack.playlist_id == Playlist.id ) # Join Playlist to playlist_track .join( Track, PlaylistTrack.track_id == Track.id ) # Join playlist_track to Track .where(Track.id == id) # Filter by the track ID .order_by(Playlist.id) .offset(offset) .limit(limit) ) # Execute the query result = await session.execute(query) db_playlists = result.scalars().all() # Query for total count of playlists count_query = ( select(func.count(Playlist.id)) .join( PlaylistTrack, PlaylistTrack.playlist_id == Playlist.id ) # Join Playlist to playlist_track .join( Track, PlaylistTrack.track_id == Track.id ) # Join playlist_track to Track .where(Track.id == id) ) total_count = await session.scalar(count_query) playlists = [ PlaylistRead.model_validate(db_playlist) for db_playlist in db_playlists ] return CombinedResponseReadAll( response=playlists, total_count=total_count, ) The _child_track_playlist_handler is passed the router created for tracks. There\u0026rsquo;s nothing generic about this code; it knows specific information about the models and relationships to use to get the data. It retrieves the playlist data using the PlaylistTrack association table.\nIt does another query to get the total_count of playlists related to the track. This is pagination information that is part of the meta_data included in the response.\nNotice the use of offset and limit in the query. This means the resulting endpoint URL:\nap/v1/tracks/{id}/playlists?offset=0\u0026amp;limit=20\napplies the offset and limit to the playlists collection and not the tracks collection.\nCRUD Operations # The database CRUD operations are also handled by generic functions that do the right thing based on the model they are passed. Here is the create_item function. The rest of the functions in the crud.py module follow similar patterns:\nasync def create_item( session: AsyncSession, data: InputType, model_class: Type[InputType], ) -\u0026gt; OutputType: \u0026#34;\u0026#34;\u0026#34; Create a new item in the database. Returns the created item as the same class. \u0026#34;\u0026#34;\u0026#34; if not inspect.isclass(model_class): raise ValueError(\u0026#34;model_class must be class object\u0026#34;) db_item = model_class(**data.model_dump()) session.add(db_item) await session.commit() await session.refresh(db_item) return db_item If you recall from the route function, what\u0026rsquo;s passed to the crud functions is the sqlmodel table class—for example, Artist, not ArtistCreate, ArtistRead, or any other variations. This is because the crud functions only deal with the database items, and that\u0026rsquo;s what they return, or an exception if there\u0026rsquo;s a problem.\nIf we look at this example in the case where InputType is Artists, the data parameter is of type Artist. The line of code:\ndb_item = model_class(**data.model_dump()) will instantiate an Artist model with the data supplied. The db_item is added to the database session, committed, and refreshed to get any database-supplied information. In this case, the auto-incremented primary_key id value.\nSeparation Of Concerns # The routes all return a *Read model or a List[*Read] model, for example, AristRead or List[ArtistRead]in the CombinedResponse* class instance. The crud functions all return database-specific models, like Artist. This is intentional. The routes return a presentation of the data. For this application, that presentation is JSON data. The crud functions return database model instances or collections of the same.\nThe crud functions make no assumptions about how the data will be used. This means other application functionality can call the crud functions and interpret and present it any way those functions see fit. Here\u0026rsquo;s a simplistic example of what I mean:\n## bad functionality def add_two_numbers(a, b): total = a + b print(f\u0026#34;The total is {total}\u0026#34;) ## bad usage, no way to get the result, and output is sent to STDOUT add_two_numbers(10, 12) ## better functionality def add_two_numbers(a, b): return a + b ## better usage, the total is returned, and that can be used in any way desired total = add_two_numbers(10, 12) print(f\u0026#34;The total is {total}\u0026#34;) My intended use case for this distinction between data and presentation is a future project/presentation about HTMX. The FastAPI application will be enhanced to serve HTMX endpoints, creating a SPA (Single Page Application) style of interface for the user.\nMetaData # I like the REST way of thinking about an API. The actions (verbs) are the HTTP methods to access a thing (noun) uniquely identified by the URL. However, there are some places where this gets awkward.\nOne minor one is creating a new thing. When a new thing is created, the response returns the unique id for the newly created thing. This is great and necessary so a user or application accessing the API can access the newly created thing with other API endpoints. But, it might be helpful if the API could return the URL of the newly created thing.\nI think a bigger problem is handling collections of things. An API endpoint like this: api/v1/things returns a collection. In any reasonably sized database, the collection of \u0026ldquo;things\u0026rdquo; could be pretty large. This example application handles that by providing offset and limit query string parameters with default values of 0 and 10, respectively. The response from the endpoint is a list of things constrained by the offset and limit values. However, it doesn\u0026rsquo;t tell you anything about the total size of the collection or how to paginate it in a presentation.\nThis is an opportunity to include metadata about the data in the API response. The metadata about a response would vary depending on the response. For example, the create thing response metadata would include location information about the newly created thing\u0026rsquo;s API URL. The collection of things response metadata would consist of pagination information about the collection: current page number, the total number of pages based on the offset and limit, and the total number of things in the collection.\nThis is where the MetaData* and Combined* class models, as well as custom middleware, come into play in the example application.\nMetaData Classes # The MetaData* model classes create the metadata structure to include with each kind of response, create, read, etc. They are contained in the models/metadata.py file and look like this:\nclass MetaData(BaseModel): status_code: int = Field( default=HTTPStatus.OK.value, description=\u0026#34;HTTP status code\u0026#34;, ) message: str = Field( default=HTTPStatus(HTTPStatus.OK.value).description, description=\u0026#34;HTTP status message description\u0026#34;, ) class MetaDataCreate(MetaData): location: HttpUrl = Field( default=\u0026#34;https://example.com\u0026#34;, description=\u0026#34;Location of the created resource\u0026#34; ) class MetaDataReadAll(MetaData): page: int = Field(default=0, ge=0, description=\u0026#34;Current page number\u0026#34;) page_count: int = Field(default=0, ge=0, description=\u0026#34;Total number of pages\u0026#34;) offset: int = Field(default=0, ge=0, description=\u0026#34;Offset value\u0026#34;) limit: int = Field(default=0, ge=0, description=\u0026#34;Limit value\u0026#34;) total_count: int = Field(default=0, ge=0, description=\u0026#34;Total number of records\u0026#34;) class MetaDataReadOne(MetaData): pass class MetaDataUpdate(MetaData): location: HttpUrl = Field( default=\u0026#34;https://example.com\u0026#34;, description=\u0026#34;Location of the created resource\u0026#34; ) class MetaDataPatch(MetaData): location: Optional[HttpUrl] = Field( default=\u0026#34;https://example.com\u0026#34;, description=\u0026#34;Location of the created resource\u0026#34; ) The MetaData model defines the base class the others derive from so they all get the status_code and message fields. This is probably redundant since the API endpoints return the status code, but what the heck, I thought it might be useful. Plus, it gives us a place to put any other data that is common to the other model definitions.\nThe MetaDataCreate class has a location field. This will have the API URL of newly created things in the database.\nThe MetaDataReadAll class has pagination information useful for APIs that return a collection of things.\nThe MetaData* classes are used to create the metadata alongside an API response. Connecting the metadata to a response is the job of the Combined* models.\nCombined Classes # The Combined* classes connect the MetaData* and API response classes to create a single, unified response. In general, an API endpoint of the example application returns a response structured like this:\n{ \u0026#34;meta_data\u0026#34;: \u0026#34;\u0026lt;meta data class instantiation\u0026gt;\u0026#34;, \u0026#34;response\u0026#34;: \u0026#34;\u0026lt;data the API endpoint returns\u0026gt;\u0026#34; } I used meta_data in the response because FastAPI reserves \u0026ldquo;metadata\u0026rdquo; (no underscore). Since each endpoint can return different a metadata and response structure, the Combined* classes make use of Python generics.\nfrom .metadata import ( MetaDataCreate, MetaDataReadAll, MetaDataReadOne, MetaDataUpdate, MetaDataPatch, ) T = TypeVar(\u0026#34;T\u0026#34;) U = TypeVar(\u0026#34;U\u0026#34;) class CombinedResponseCreate(BaseModel, Generic[T]): meta_data: MetaDataCreate = MetaDataCreate() response: T class CombinedResponseReadAll(BaseModel, Generic[T, U]): meta_data: MetaDataReadAll = MetaDataReadAll() response: T total_count: U class CombinedResponseRead(BaseModel, Generic[T]): meta_data: MetaDataReadOne = MetaDataReadOne() response: T class CombinedResponseUpdate(BaseModel, Generic[T]): meta_data: MetaDataUpdate = MetaDataUpdate() response: T class CombinedResponsePatch(BaseModel, Generic[T]): meta_data: MetaDataPatch = MetaDataPatch() response: T Each of the Combined* classes creates a default MetaData*() instance. The instantiated MetaData* class will be initialized by the middleware code coming up. The response will be whatever type is supplied by the route definition. Here\u0026rsquo;s the read_item route definition to show how this is used:\n@router.get( \u0026#34;/{id}\u0026#34;, response_model=CombinedResponseRead[getattr(model, f\u0026#34;{class_name}Read\u0026#34;)], ) async def read_item( id: int = Path(..., title=f\u0026#34;The ID of the {prefix} to get\u0026#34;), db: AsyncSession = Depends(get_db), ): async with db as session: db_item = await crud.read_item( session=session, id=id, model_class=getattr(model, f\u0026#34;{class_name}\u0026#34;), ) if db_item is None: raise HTTPException( status_code=404, detail=f\u0026#34;{class_name} not found\u0026#34;, ) item_read = getattr(model, f\u0026#34;{class_name}Read\u0026#34;) return CombinedResponseRead(response=item_read.model_validate(db_item)) The route response model is CombinedResponseRead and is passed the model *Read class definition, which is used by the CombinedResponseRead class as the Generic[T] type.\nThe route returns an instance of CombinedResponseRead initialized with a validated instance of the db_item.\nMiddleware # What is returned by the application routes is an instance of one of the Combined* classes, with the response part filled in and the meta_data part present but empty. To initialize the meta_data, the application has custom middleware to intercept the responses and populate the meta_data appropriately. The app/middleware.py module handles this. I won\u0026rsquo;t include the contents of that file here as it\u0026rsquo;s lengthy, and you can see it in the respository.\nThat code has one primary function: to intercept the requests, get the response from the routes, and update the meta_data part of the response with the correct metadata for the type of request and the response structure.\nInstallation # The project uses the Astral uv tool for dependency management. Install the uv tool according to its documentation. Once the repository has been cloned, change to the project directory and run:\nuv sync That will install the dependencies to run the application locally.\nRunning The Application # The easiest way to run the application is to use Docker. Once Docker is installed, change to the project directory and run this command:\ndocker compose -f docker-compose.yml up This will create a container and run the application in terminal mode so the log messages are visible in the terminal window where the command was run. Open a browser and navigate to http://0.0.0.0:8000/docs and you\u0026rsquo;ll see the OpenAPI (Swagger) documentation for the application REST endpoints.\nYou can interact with the endpoints to see how the application performs.\nResources # FastAPI Documentation\nSQLite Tutorial\nuv\n","date":"2024-12-23","externalUrl":null,"permalink":"/posts/fastapi_presentation/","section":"Blog Posts","summary":"","title":"FastAPI Presentation","type":"posts"},{"content":"In this secrets-revealed edition of “The Adventures of Henry,” Pup de León is seen near the “Fountain of Adventure,” which, as it turns out, is the boys’ bathroom sink at the municipal center playground. Henry stops here almost daily for a refresher and does a little dance until I pick him up on the counter.\n","date":"2024-07-26","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-26-2024/","section":"Writing","summary":"","title":"Adventures on July 26, 2024","type":"writing"},{"content":"","date":"2024-07-26","externalUrl":null,"permalink":"/writing/adventures_of_henry/","section":"Writing","summary":"","title":"The Adventures Of Henry","type":"writing"},{"content":"","date":"2024-07-26","externalUrl":null,"permalink":"/writing/","section":"Writing","summary":"","title":"Writing","type":"writing"},{"content":"My thoughts about interviews are focused on soft skills, not technical interviews. There are plenty of resources online to help with whiteboard interviews, take-home tests, or other technical challenges.\nThe interview process can be nerve-wracking, but keep this in mind: once you get past HR, the people you’ll be talking to are probably uncomfortable as well. They don\u0026rsquo;t interview often, and they don’t want to make a mistake either. Here are some things I do that help me during interviews:\nGet Comfortable # If it’s an in-person interview (which is rare today), I sit back in the chair and get comfortable. Even in a virtual interview, try to appear comfortable. This helps the conversation flow naturally, making it more of a back-and-forth discussion rather than just a question-and-answer session.\nKnow When to Stop Talking # Once I’ve answered a question or made a joke, I stop talking and let the interviewer have a chance to speak or move on to the next question. Some interviewers might use the old trick of letting the silence hang, which can create tension and make you continue talking, possibly revealing more than you intended.\nShare Relevant Stories # Sometimes, I share stories about how I would handle a particular situation. If I have an amusing or relevant experience related to the question, I go with it. This isn’t for everyone, but if you’re comfortable with storytelling, it can make your answers more engaging. Evaluate your skill in this area and act accordingly.\nBe Honest About Your Knowledge # When asked how I would handle a technical situation, I do my best to answer truthfully. I expand on the question to the fullest extent of my knowledge, but no more than that. If the interviewer probes deeper into an area beyond my expertise, I say, “I don’t know.” It’s a complete and truthful answer. Pretending to know more than you do will likely be obvious, and if not, it can lead to uncomfortable situations later.\nAvoid Discussing Salary Early On # I try to avoid talking about salary during an interview. I don’t want to price myself out of consideration or lowball my value. I usually say, “I’d rather discuss salary further down the road when we see if this relationship will work for both of us.” If pressed, I mention my current salary and add that more money is always nice, etc.\nPractice # Interviewing is a skill. Like all skills, it takes practice to improve, and it can deteriorate if not used. So, how do you practice when there might be years between interview opportunities? Practicing with friends and family is fine, and you\u0026rsquo;ll see that recommended online almost everywhere. It’s practical and achievable, but nothing beats the real thing.\nWith that in mind, I’ll share something I’ve done that I\u0026rsquo;ve found helpful, though some might find it controversial. I’ve taken interviews for jobs I had no intention of accepting. This practice tells me two things: first, am I still marketable? Second, do I still have my interviewing “chops,” or do I need more practice?\nYou might think this wastes a company’s time and resources, and that’s true—it does. I don’t do this maliciously or with bad intent. However, consider this: you’ll rarely hear back about an interview unless it’s positive. It can be weeks, if ever, before you learn the employer has moved on. And there’s little chance you’ll get feedback about the interview or why they decided not to proceed. I understand this completely; the employer is in a difficult position where sharing negative feedback can have consequences for them.\nSo, they make the best decisions for themselves and their goals. That’s absolutely fair and understandable. Given that, I’m going to do the same for myself.\n","date":"2024-07-24","externalUrl":null,"permalink":"/posts/interviews/","section":"Blog Posts","summary":"","title":"Job Interviews","type":"posts"},{"content":"Here at “The Adventures of Henry” we see our little general practicing the rarely mentioned tactic from Sun Tzu’s “The Art of War” called “on back, legs up.” It’s a subtle, long game strategy.\n","date":"2024-07-22","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-22-2024/","section":"Writing","summary":"","title":"Adventures on July 22, 2024","type":"writing"},{"content":"I wrote a book! It\u0026rsquo;s called \u0026ldquo;The Well-Grounded Python Developer\u0026rdquo;, published by Manning Publications. I think of the book as a way to help Python programmers become Python developers. It doesn\u0026rsquo;t teach Python; there are lots of resources that do that. My hope is that the book will enable you to use the tools you know in Python to build bigger, more complex, and sustainable projects.\nThe metaphor I think about is having a hammer and saw and being able to cut some wood pieces and hammer them together. Where you\u0026rsquo;d like to go is to build some cabinetry.\nThe book is divided into two major sections: \u0026ldquo;Groundwork\u0026rdquo; and \u0026ldquo;Fieldwork\u0026rdquo;. The first section has several chapters that cover topics and techniques useful to Python developers. The second section uses those techniques to build a project over the rest of the chapters in the book.\nThe project uses Flask, a popular web framework for Python, to build a blogging application. Is the resulting blog application the end all, be all, of blogging websites? Absolutely not. But the journey to completing it takes you along a manageable path to create a relatively sophisticated application.\nI\u0026rsquo;ve gotten commentary about the book that it\u0026rsquo;s a Flask book. That\u0026rsquo;s true to a point, you\u0026rsquo;ll learn quite a bit about Flask. I think that\u0026rsquo;s a nice side effect as it\u0026rsquo;s a great web framework. But my goal was to use it as a learning framework to build a project well beyond a single file Python script. The project shows how to effectively use:\nNamespaces to manage Python modules and scope Classes and objects to model the application Installing and using third-party Python libraries Handling errors and exceptions Using SqlAlchemy to abstract the storing and retrieval of database data Authentication and authorization of users Web application form handling If you\u0026rsquo;re interested in Python and want to take your skills to the next level, the book out check it out and see what you think.\n","date":"2024-07-05","externalUrl":null,"permalink":"/posts/my_book/","section":"Blog Posts","summary":"","title":"My Book","type":"posts"},{"content":" My Thoughts on Resume Writing # Throughout my career, I’ve changed jobs quite a bit. Most of the time, it was by choice, but a couple of times, it was due to layoffs. I’ve also survived rounds of layoffs and watched friends and colleagues lose their jobs. This has given me the opportunity to write many resumes and help others with theirs. This post gathers my thoughts on resume writing and its role when you’re job-seeking.\nWhat I’m sharing is based entirely on my background and experience in job hunting. Keep in mind that I’m a software developer, and the roles I was seeking were in that field. When I helped others, the roles they were looking for were either the same or related in some way. I have no idea if an HR professional would agree with me; I only know what has worked for me.\nFrom my perspective, a resume has a single purpose: to get you to the interview stage. I doubt anyone is hired solely based on a resume. The interview is where the deal is closed. This mindset helps me set goals for my resume and strengthen anything that helps me get to the interview.\nWith that in mind, I approach a resume using a newspaper mindset, ensuring I include the 5 W’s as quickly as possible: Who, What, Where, When, and Why.\nWho am I? What do I want? Where am I located or seeking to be? When would I like to take the next step? Why am I the right person for the role? Getting these details into the first part of the resume (or shortly after) informs the reader, grabs their attention, and encourages them to keep reading.\nGrabbing attention and encouraging continued reading is essential. I believe you have around 15 to 30 seconds of an HR person’s time before they decide whether to keep reading or move on. I also think their first inclination is to say “no” rather than risk wasting time for the next person involved in the hiring process. HR personnel might tell me this isn’t true, but my experience has taught me otherwise.\nResume Length # Related to that 15-30 seconds of initial reading time is the total length of a resume. It should absolutely not be longer than two pages. I can’t tell you how many resumes I’ve seen from seasoned professionals that include page after page of job history, dating back to high school internships and summer jobs.\nI understand the intention, it seems important to show that we’ve been steadily employed since entering the workforce, with no gaps. But this is a mistake, and here’s why:\nIt wastes valuable space and will likely break the two-page limit rule. Are you seeking to return to those early jobs or capitalize on those experiences? Anything older than ten years is probably irrelevant to the roles you’re seeking now and should be cut from the resume. Instead, keep those experiences as stories for the interview process, if relevant.\nResume Layout # Here’s the layout I use for my resume:\nContact Information Career Objective Skills Job History And that’s pretty much it.\nContact Information # This section includes your name, address, phone number, email, LinkedIn and GitHub URL (if you have one), and how to contact you. It answers the Who and part of the Where from the 5 W’s.\nCareer Objective # The Career Objective is essentially a marketing tool for yourself. It can feel awkward to write about yourself with a lot of “I did this…” and “I did that…”. One tip is to write the first draft in the third person, as if you’re writing about someone else, and then replace all the third-person references with “I.”\nYour resume is a promotional tool to get you to the interview stage, so make it as positive and enticing as possible.\nHere’s an example of my Career Objective:\n“I have more than 20 years of software development experience across several industries and disciplines: industrial control, embedded systems, retail software, Internet development, and company-wide intranet and web application development. I’ve consistently delivered successful software applications individually and as a leader or member of a team. Additionally, I work cross-discipline with other groups and bring projects to successful and profitable conclusions.\nHaving been in the industry for a while, I fully embrace the concept that ‘change is the only constant,’ and I can take productive advantage of emerging technologies. I also excel at teaching new technologies in corporate environments and as a STEM instructor.”\nWhile this objective focuses mainly on the Why (why someone should hire me), it doesn’t address the What (what I want). This is okay because I always include a link to my LinkedIn profile, which provides a more thorough view of me, my skills, and my experience. Modify this section as needed for your purposes.\nIf you have a GitHub account, include that as well. It\u0026rsquo;s a great way to show what you\u0026rsquo;ve done and reinforces what you say your skills are.\nSkills # The Skills section should be a concise list of your abilities. This helps the reader determine if you meet the technical qualifications for the role. Highlight the skills you feel strongest about, and tailor the list to the job you’re applying for.\nAvoid including base-level skills that are expected, like “good with Windows” or “conversant with Linux.” Also, don’t include skills you’re only marginally familiar with, embellishments will be revealed during an interview and can reflect poorly on you.\nJob History # As mentioned earlier, limit the Job History section to the last ten years, if applicable. This is where you detail where you worked, when, and what you did for each organization.\nThe “what you did” section is not just another list of skills, it’s your opportunity to show how you used your skills and how they benefited the organization. Here are two examples of how to describe the same work:\nPart of a team producing APIs that improved the user experience and assisted customer support. Led the team that created SaaS APIs, revamping the customer experience, improving retention, and reducing support costs. The first example is written in the \u0026lsquo;passive voice\u0026rsquo; and isn\u0026rsquo;t very interesting. The second example is more actively engaging and clearly states the impact of your work.\nReferences # I usually close with something like “References Available on Request.” This can encourage the reader to contact you for references and allows you to choose the most relevant ones. Before listing someone as a reference, check that they’re still comfortable recommending you—especially if you haven’t worked together for a while.\nOther Considerations # Proofread # Always proofread your resume, have others proofread it, and use tools like Grammarly to check for errors. Few things make a resume look more amateurish than misspellings or grammatical mistakes.\nFonts # Stick to one or two fonts, preferably sans-serif for readability. I recommend Tahoma or Helvetica. Avoid using overly stylized fonts like Comic Sans.\nGraphics # If you have strong design skills, a graphical resume can look fantastic, but ensure the essential information is still easy to find. If you’re unsure about your design abilities, save your creativity for other projects.\nHumor # Humor is tricky to pull off, especially in writing. What one person finds funny, another might find offensive. Save humor for the interview, where you can \u0026ldquo;read the room\u0026rdquo; and gauge the reaction in real-time.\nClosing Thoughts # Your resume is a tool, and like any tool, mastering it takes time and practice. Continually tweak and refine your resume to ensure it serves you well.\nRemember, your resume is also a sales tool, it should present you in the best possible light. Put in the effort, and it will pay off.\n","date":"2024-07-05","externalUrl":null,"permalink":"/posts/resume_writing/","section":"Blog Posts","summary":"","title":"Resume Writing","type":"posts"},{"content":"Henry and one of his camp pals in “The Adventures of Henry” want to wish you all a great weekend.\n","date":"2024-06-28","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-28-2024/","section":"Writing","summary":"","title":"Adventures on June 28, 2024","type":"writing"},{"content":"In this episode of “The Adventures of Henry” we see our inventor demonstrating his “Shade Seeking Guidance” system. He’s been in contact with the DOD to patent and share this critical technology.\n","date":"2024-06-26","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-26-2024/","section":"Writing","summary":"","title":"Adventures on June 26, 2024","type":"writing"},{"content":"Today’s “The Adventures of Henry” reveals information well-known to dog lovers. Henry, like all dogs, is an alchemist. When comfortable, he can convert his body from dog to lead.\n","date":"2024-06-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-14-2024/","section":"Writing","summary":"","title":"Adventures on June 14, 2024","type":"writing"},{"content":" Introductory Post # The posts here give me a place to pull together some things I\u0026rsquo;ve written about, will write about and some things others have written.\n","date":"2024-06-01","externalUrl":null,"permalink":"/posts/intro/","section":"Blog Posts","summary":"","title":"Introduction","type":"posts"},{"content":"In this most accusatory edition of “The Adventures of Henry,” our little judge is asking Dad, “Where is Mom, and what have you done to her?” There is no adequate defense to this question, and the jury of one (orange dinosaur) will rule accordingly.\n","date":"2024-05-30","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-30-2024/","section":"Writing","summary":"","title":"Adventures on May 30, 2024","type":"writing"},{"content":"In today’s entry in “The Adventures of Henry” chronicles, we find our hero truly “running with the big boys.” Finally we have a picture of Henry that shows an accurate depiction of how he envisions his own personality!\n","date":"2024-05-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-23-2024/","section":"Writing","summary":"","title":"Adventures on May 23, 2024","type":"writing"},{"content":"Today’s “The Adventures of Henry” displays what many of you might have guessed. That Henry is a true connoisseur of TGIF! He is flying into the weekend full of hopes and dreams of all the wonders it can bring. Imagine his disappointment when he sees Susan and I dragging our broken butts into the weekend and arguing over who gets the last Motrin! ;)\n","date":"2024-05-17","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-17-2024/","section":"Writing","summary":"","title":"Adventures on May 17, 2024","type":"writing"},{"content":"“The Adventures of Henry,” shows our intrepid troupe preparing for an audition to be part of a production of “A Chorus Line.” Based on this picture, they ummmmm might need a coach!\n","date":"2024-05-08","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-08-2024/","section":"Writing","summary":"","title":"Adventures on May 08, 2024","type":"writing"},{"content":"Here at the Adventures of Henry, we fully endorse the concept of zoomies and their implementation. They are part of a healthy lifestyle for pups, along with the FDA approved food pyramid:\ntreat\n/ \\\ntreat treat\n/ \\ / \\\ntreat treat treat treat\n","date":"2024-04-29","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-29-2024/","section":"Writing","summary":"","title":"Adventures on April 29, 2024","type":"writing"},{"content":"“The Adventures of Henry” finds our pup during a casual camp meet-up with some of his friends. This isn’t an undercover op to exchange information as part of any plan to disrupt the world order. No, nothing like that….\n","date":"2024-04-19","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-19-2024/","section":"Writing","summary":"","title":"Adventures on April 19, 2024","type":"writing"},{"content":"In today’s “Robert’s Rules of Order” edition of “The Adventures of Henry,” we find our little parliamentarian making a motion to the chairman to allow everyone on the dais at any time. Henry realizes that would be chaotic, but it looks like fun up there, so come’on!!\n","date":"2024-04-03","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-03-2024/","section":"Writing","summary":"","title":"Adventures on April 03, 2024","type":"writing"},{"content":"A newsworthy announcement from “The Adventures of Henry!” Today marked the first successful test of the “friend-a-pult” at puppy daycare! This is the first step in their long-term goal to achieve “Puppies In Spaaaaaaaaace!” I think we can all agree this is a laudable moment with many far-reaching implications.\n","date":"2024-03-25","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-25-2024/","section":"Writing","summary":"","title":"Adventures on March 25, 2024","type":"writing"},{"content":"Today’s episode of “The Adventures of Henry” shows that our mover and shaker has places to go and people to see. Or is that places to pee and people to go? Clichés are so hard to keep straight.\n","date":"2024-03-19","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-19-2024/","section":"Writing","summary":"","title":"Adventures on March 19, 2024","type":"writing"},{"content":"Is it tongue-out Tuesday already?!? Clearly, Henry and the staff at “The Adventures of Henry” were caught unaware. While Henry appreciates the reminder from his camp friend, he would appreciate a bit of a heads-up in the future. He’s talking about you, Dad!\n","date":"2024-03-12","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-12-2024/","section":"Writing","summary":"","title":"Adventures on March 12, 2024","type":"writing"},{"content":"“I don wanna work, I just wanna bark with my friends all day!” The Adventures of Henry reports this was apparently the chorus at camp yesterday. The staff was quoted as saying, “We don’t mind the singing, but they do need to expand their repertoire.” The editors and I agree based on long-term exposure.\n","date":"2024-03-07","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-07-2024/","section":"Writing","summary":"","title":"Adventures on March 07, 2024","type":"writing"},{"content":"When you’re a Jet,\nYou’re a Jet all the way\nFrom your first nosy vet\nTo your last barkin’ day.\nThis is, of course, a famous verse from the way, way off Dogway production of “Best Stride Story.” We at “The Adventures of Henry” applaud these amateur productions and encourage everyone to get tickets now!\n","date":"2024-02-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-23-2024/","section":"Writing","summary":"","title":"Adventures on February 23, 2024","type":"writing"},{"content":"Today’s edition of “The Adventures of Henry” shows our main character and his friends involved in a round of the classic game “The Grass Is Lava!” This version\u0026rsquo;s rules are nebulous, and the goal is unknown. This differs from the version I play with our grandson, where the rules are fluid, and the goal is for me to get a broken ankle.\n","date":"2024-02-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-14-2024/","section":"Writing","summary":"","title":"Adventures on February 14, 2024","type":"writing"},{"content":"Today’s fun in the sun episode of “The Adventures of Henry” finds our little calendar denier jumping the gun to spring so he can play outside on a regular basis. No amount of conversation about the existence of the rest of February and the coming March will dissuade him. He’s already put in his membership application to the “Flat-Earth Society.”\n","date":"2024-02-08","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-08-2024/","section":"Writing","summary":"","title":"Adventures on February 08, 2024","type":"writing"},{"content":"Today’s dapper edition of “The Adventures of Henry” finds our well-dressed pup sporting his family tartan at camp. His friend wonders why he doesn’t have a cool vest like Henry and who in his family will have their slippers violated because of it.\n","date":"2024-02-06","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-06-2024/","section":"Writing","summary":"","title":"Adventures on February 06, 2024","type":"writing"},{"content":"In this nobility of dogs edition of “The Adventures of Henry,” we find our bold arctic explorer on the Tundra near the polar ice cap. By near, we mean, of course, about 3000 miles north. Mr. Henry is light and breezy but ready for adventure, sporting this delightful, multi-purpose vest in his family tartan. It comes in many combinations of patterns and is available at major retailers now!\n","date":"2024-01-24","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-24-2024/","section":"Writing","summary":"","title":"Adventures on January 24, 2024","type":"writing"},{"content":"It’s puppy’s on parade day here at “The Adventures of Henry.” Our ringleader, umm, I mean Drum Major, is seen here trying to get the color guard in shape for the Puppy Bowl half-time show. The All Camp Puppy Marching Band is the opening act for Tailor Swipes.\n","date":"2024-01-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-23-2024/","section":"Writing","summary":"","title":"Adventures on January 23, 2024","type":"writing"},{"content":"Today’s “The Adventures of Henry” edition is all about transformation. Here we see the classic “before and after” pictures of Henry’s visit to the groomers. Susan and I both think he looks cute either way. However, Henry is disappointed the haircut precludes him being an understudy in the dogway production of “Hair.”\n","date":"2024-01-17","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-17-2024/","section":"Writing","summary":"","title":"Adventures on January 17, 2024","type":"writing"},{"content":"Today’s highly fashion-conscious edition of “The Adventures of Henry” finds our well-dressed pup asking, “Does this jacket make my butt look big?” Consider your answer carefully, as Henry is very sensitive, and I might have to filter the responses!\n","date":"2024-01-16","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-16-2024/","section":"Writing","summary":"","title":"Adventures on January 16, 2024","type":"writing"},{"content":"Today’s puppy and the gang edition of “The Adventures of Henry” finds our little ringleader rallying the troops. For what? we don’t know. It could be part of their long-term plan to topple “Big Donut” and liberate baked goods nationwide. Concerned? So are we; tune in tomorrow: Same Dog Time, Same Dog Channel!!\n","date":"2024-01-12","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-12-2024/","section":"Writing","summary":"","title":"Adventures on January 12, 2024","type":"writing"},{"content":"The abominable snow puppy edition of “The Adventures of Henry” finds our little trickster engaged in a hide-and-seek game. In this version, he hides the ball he just begged us to throw, and then we have to go seek where he dropped it. Wash, rinse, repeat, and fun ensues, or so I’m told.\n","date":"2024-01-10","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-10-2024/","section":"Writing","summary":"","title":"Adventures on January 10, 2024","type":"writing"},{"content":"Today’s edition of “The Adventures of Henry” finds our little cutie out for a walk with his mom. You might be lulled into thinking, “Awwww, adorable,” which was my first impression. However, later investigation revealed that Henry was asking, “Mom, can I roll in that, and if so, will I need a bath afterward?” To which the answer was, “No,” and “Ewww, yes!”\n","date":"2024-01-05","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-05-2024/","section":"Writing","summary":"","title":"Adventures on January 05, 2024","type":"writing"},{"content":"Greetings from “The Adventures of Henry.” Welcome to 2024!! As you can see, Henry and the gang from puppy camp have high expectations, so you better bring your A-Game! The judges aren’t impartial and the rules are fluid. Much like Calvinball!\n","date":"2024-01-02","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-02-2024/","section":"Writing","summary":"","title":"Adventures on January 02, 2024","type":"writing"},{"content":"Today’s action shot from “The Adventures of Henry” finds our sprinter in high-speed denial, “I DID NOT steal your treat! The crumbs in my mustache have been there for days!” You know, as well as I do, this will ultimately end up with DNA testing.\n","date":"2023-12-20","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-20-2023/","section":"Writing","summary":"","title":"Adventures on December 20, 2023","type":"writing"},{"content":"In today’s sad puppy edition of “The Adventures of Henry,” we see our hero dejectedly sitting on the couch after getting a bath for rolling in something disgusting while on a walk. He’s already lodged an official complaint with the International Red Cross, but so far, that has fallen on deaf ears.\n","date":"2023-12-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-14-2023/","section":"Writing","summary":"","title":"Adventures on December 14, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” finds our industrious pup hard at work, ensuring that gravity still works and that his under-the-desk bed won’t spontaneously start floating around the room. You’re welcome.\n","date":"2023-12-12","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-12-2023/","section":"Writing","summary":"","title":"Adventures on December 12, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” finds the puppy camp debate team engaged in a hotly contested topic testing the bounds of Roberts Rules of Order. From the picture, it can’t be determined who is using logical fallacies and who is using circular reasoning, but the referee seems to be moments away from calling time.\n","date":"2023-12-04","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-04-2023/","section":"Writing","summary":"","title":"Adventures on December 04, 2023","type":"writing"},{"content":"This outdoorsy edition of “The Adventures of Henry” finds our intrepid explorer on the ridge-line tracking his elusive prey in the wild country, fraught with danger, exposed to the elements, and dependent on his wits and will for survival. His only tool?, a sporty purple vest in which he cuts a dashing figure! His prey, well, cheese, of course. Oh, and snuggles.\n","date":"2023-12-01","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-01-2023/","section":"Writing","summary":"","title":"Adventures on December 01, 2023","type":"writing"},{"content":"All of us here at “The Adventures of Henry” want to wish you a wonderful Thanksgiving! And we hope you get to spend it with friends, family, and all the people in your life that bring you joy.\n","date":"2023-11-22","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-22-2023/","section":"Writing","summary":"","title":"Adventures on November 22, 2023","type":"writing"},{"content":"In this adorable edition of “The Adventures of Henry,” we find our hero enjoying some valuable mom time. It’s a special bond between a puppy and his mom, one overloaded with sincere feelings of closeness and love. A time of emotional and physical healing, necessary for both after the shared adventures. Or, or, dare I say, it could be a puppy vying for that last chocolate biscotti. It’s a question for historians.\n","date":"2023-11-16","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-16-2023/","section":"Writing","summary":"","title":"Adventures on November 16, 2023","type":"writing"},{"content":"It’s tongue-out-tuesday today on “The Adventures of Henry,” and on an actual Tuesday. Imagine that! What’s not so obvious is that Henry and the crew are nurturing Feng Shui at camp by facing north while posing. There’s no telling what will come of this!\n","date":"2023-11-07","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-07-2023/","section":"Writing","summary":"","title":"Adventures on November 07, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” chronicles show our hero’s floof while he’s being chased in hot pursuit by one of his campmates. It’s impossible to tell if this is just fun and games or is the result of a stolen treat. The editorial board is going with the former, me, I’m going with the latter.\n","date":"2023-11-06","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-06-2023/","section":"Writing","summary":"","title":"Adventures on November 06, 2023","type":"writing"},{"content":"“The Adventures of Henry” actually has an adventure picture! This was taken at a local park during a hike. This particular image was taken during a range experiment. We tried to silently open a string cheese and get a nibble at a distance of about 50 yards. As you can see, this was well within Henry’s cheese-a-phone hearing.\n","date":"2023-10-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-october-23-2023/","section":"Writing","summary":"","title":"Adventures on October 23, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” presents “The Look”. This is a studied and considered expression of the many ways that I’m not giving Henry what he wants and clearly deserves, in addition to how far short I’m falling in terms of answering those requests in a timely manner. I think you’ll agree “The Look,” says it all, and that I’m a monster….\n","date":"2023-10-16","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-october-16-2023/","section":"Writing","summary":"","title":"Adventures on October 16, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” shows two members of the Puppy Tabernacle Choir practicing for the slightly lesser-known work, “Dogspell.” Henry’s rendition of “Treat by Treat” will bring you to tears.\n","date":"2023-10-05","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-october-05-2023/","section":"Writing","summary":"","title":"Adventures on October 05, 2023","type":"writing"},{"content":"In today’s edition of “The Adventures of Henry,” we find the three mushkateers putting their best paw forward at camp. “One treat for all and all treats for me!,” or something to that effect, ringing out to those passing by. This trio of adventurer’s must be readying an attack on the evil Catdinal Richelieu!\n","date":"2023-10-02","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-october-02-2023/","section":"Writing","summary":"","title":"Adventures on October 02, 2023","type":"writing"},{"content":"In this edition of the non-“The Adventures of Henry,” we find he is performing his best impression of the rarely photographed couch-slug. This rare version of Henry is a direct result of his mom and dad being essentially inert while dueling with “The VIDS”. We’re both coming out of it, but Henry is lobbying the Red Cross for 250 cc’s of excitement, STAT!!!\n","date":"2023-09-27","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-september-27-2023/","section":"Writing","summary":"","title":"Adventures on September 27, 2023","type":"writing"},{"content":"In this magnanimous edition of “The Adventures of Henry,” we find our spotlight hog giving his understudy a chance to show his stuff during an early rehearsal of “The Puppies of Penzance.” Henry is there for moral support and to undermine the understudy if necessary. A puppy’s got to do what a puppy’s got to do.\n","date":"2023-09-22","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-september-22-2023/","section":"Writing","summary":"","title":"Adventures on September 22, 2023","type":"writing"},{"content":"The “The Adventures of Henry” journal was honored to be invited to a meeting of Pupsa. Here we see the board of directors agreeing they’ve solved nuclear fusion but aren’t going to share that information until they figure out the mysteries of how to open the fridge meat draw. It’s all about priorities.\n","date":"2023-09-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-september-14-2023/","section":"Writing","summary":"","title":"Adventures on September 14, 2023","type":"writing"},{"content":"“The Adventures of Henry” shows Henry and a friend at camp preparing to visit the Ministry of Silly Walks to put in a request for funding. Granted, their silly walk isn’t currently very silly, but they both believe that with the proper support and encouragement, they can develop their walks to the point of extreme silliness. Having taken Henry for many walks, I can assure you that he is, in fact, very silly. @awesomedoodles\n","date":"2023-09-01","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-september-01-2023/","section":"Writing","summary":"","title":"Adventures on September 01, 2023","type":"writing"},{"content":"In this thespian edition of “The Adventures of Henry,” we see our boy mid-tryout for the lead role in the puppy daycare stage production of Disney’s “Dumbo.” The early reviews are going to press as I write this, and they are calling this the “The must-see attraction of the year! Simply breathtaking!”\n","date":"2023-08-31","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-august-31-2023/","section":"Writing","summary":"","title":"Adventures on August 31, 2023","type":"writing"},{"content":"Today’s weekend R\u0026amp;R “The Adventures of Henry” post finds our tired puppy on a ride home from playing with Buffy (our daughter’s Frenchie) all day Sunday. This was the final fatigue in an exhausting week that saw terrorist (or so says Henry) dog attacks, completely unnecessary (according to Henry) trips to the 24-hour vet, uncalled for (says indignant Henry) baths, and any number of countless slings and arrows of outrageous fortune! The whole thing has been a Greek tragedy from a particular drama queen’s point of view!\n","date":"2023-08-28","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-august-28-2023/","section":"Writing","summary":"","title":"Adventures on August 28, 2023","type":"writing"},{"content":"In today’s episode of “The Adventures of Henry,” we have a public service announcement. Speaking for all puppy’s everywhere, he would like to share with you that “You And Only You Can Prevent Bored Puppies.” That is all. Please pet responsibly.\n","date":"2023-08-04","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-august-04-2023/","section":"Writing","summary":"","title":"Adventures on August 04, 2023","type":"writing"},{"content":"In this back-from-vacation post of “The Adventures of Henry” we see that our little hero missed his mom a lot while we were gone. We got a full report from overnight camp that he had lots of fun and did fine, but there’s no reason not to get started early on the guilt-guns for the next trip.\n","date":"2023-07-27","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-27-2023/","section":"Writing","summary":"","title":"Adventures on July 27, 2023","type":"writing"},{"content":"In this TGIF edition of “The Adventures of Henry” we find our intrepid arctic exploring, sock-stealing extraordinaire, incomparable ball-chasing knucklehead has had enough of this week. Henry has left the building!\n","date":"2023-07-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-14-2023/","section":"Writing","summary":"","title":"Adventures on July 14, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” shows our favorite debater involved in a deep, wide ranging conversation about the widely accepted benefits of biting each other on the head. This picks up where Aristotle left off.\n","date":"2023-07-12","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-12-2023/","section":"Writing","summary":"","title":"Adventures on July 12, 2023","type":"writing"},{"content":"In this amateur production edition of “The Adventures of Henry,” we see our drama student warming up with his co-stars for the Jets vs. Sharks dance fight number of the “Puppy Playhouse Production of West Side Story!” Theater scouts and movie agents will be in the audience, so please, no flash photography.\n","date":"2023-07-03","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-july-03-2023/","section":"Writing","summary":"","title":"Adventures on July 03, 2023","type":"writing"},{"content":"Today’s post-“The Adventures of Henry” shows are story’s hero on his way home in the back of the car after a full day of chasing that little rascal Buffy (our daughter’s Frenchie) around the house and yard. While in repose in the picture, I assure you he’s ready to spring into action at a moment’s notice…. any moment now…. couple more seconds…..\n","date":"2023-06-26","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-26-2023/","section":"Writing","summary":"","title":"Adventures on June 26, 2023","type":"writing"},{"content":"Today’s lazy posting in “The Adventures of Henry” shows Henry making the most of this day off from camp. He does love his bed under my desk, but he’s also multitasking by putting a fine point on his proposition that I’m the most boring Dad in the world. Despite my best efforts, I really can’t deny it.\n","date":"2023-06-09","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-09-2023/","section":"Writing","summary":"","title":"Adventures on June 09, 2023","type":"writing"},{"content":"Today’s edition of “The Adventures of Henry” shows The Puppy Inspector General surveying the garage/woodshop after a project to see if it still comes up to code on stretch area. I’m hopeful his final report is positive, as I don’t want to start over again.\n","date":"2023-06-06","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-june-06-2023/","section":"Writing","summary":"","title":"Adventures on June 06, 2023","type":"writing"},{"content":"“The Adventures of Henry” presents our favorite pup waiting not so patiently for a “pup cup” at Ferris Creamery this past weekend. Even after getting his ice cream and finishing it, ours became the object of his not-so-patient attention!\n","date":"2023-05-31","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-31-2023/","section":"Writing","summary":"","title":"Adventures on May 31, 2023","type":"writing"},{"content":"In today’s edition of “The Adventures of Henry” we see Henry and his bestie Buffy doing role-playing and re-enacting the current debt ceiling negotiations. Clearly, both sides have valid, reasonable points of view for holding the toy hostage.\n","date":"2023-05-25","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-25-2023/","section":"Writing","summary":"","title":"Adventures on May 25, 2023","type":"writing"},{"content":"“The Adventures of Henry” was produced on location at our daughter’s house with her pup Buffy and Henry sharing a quiet moment on the couch after a long day of wrestling. Is this cute?, yes, yes it is. However, it’s only a temporary moment between treating each other’s heads like rawhide chews!!\n","date":"2023-05-17","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-17-2023/","section":"Writing","summary":"","title":"Adventures on May 17, 2023","type":"writing"},{"content":"“The Adventures of Henry” shows a meeting of the WPO (World Puppy Organization) breaking up after Henry brought proposal , subsection A, to the table and added it to the agenda for the next conclave. The proposal addresses the general concern about the hooman’s not understanding directed staring, even with an accompanying head tilt, and how it relates to forking over the treats. Treats that are inconveniently in drawers, cabinets, and on high counters out of reach of the WPO membership.\n","date":"2023-05-10","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-10-2023/","section":"Writing","summary":"","title":"Adventures on May 10, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” post honors the long-held “tongue out Tuesday” tradition, renowned in song and legend. Our hero, being essentially a traditionalist, is happy to oblige.\n","date":"2023-05-02","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-may-02-2023/","section":"Writing","summary":"","title":"Adventures on May 02, 2023","type":"writing"},{"content":"Today’s “Adventures of Henry” finds our noble pup posing for the nationally syndicated magazine, “Noble Pup.” Either that or he just spotted a squirrel at camp and is deciding whether or not to give that squirrel the pointless chasing of its life!\n","date":"2023-04-24","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-24-2023/","section":"Writing","summary":"","title":"Adventures on April 24, 2023","type":"writing"},{"content":"In this not so adventurous edition of “The Adventures of Henry,” our little bed slug is seen here using his wiles and encouraging dad to NOT make the bed and stop all this foolishness about going to work. All my comments about who’s going to keep him in treats have caused eye rolls and exclamations of, “pish posh”.\n","date":"2023-04-17","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-17-2023/","section":"Writing","summary":"","title":"Adventures on April 17, 2023","type":"writing"},{"content":"In more outtakes from “The Adventures of Henry,” the movie, we see some of the cast in a candid shot, with Henry’s stunt double in the background. Publicly Henry claims to do all his own stunts, but his only true 100% coverage is for the treats!\n","date":"2023-04-06","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-06-2023/","section":"Writing","summary":"","title":"Adventures on April 06, 2023","type":"writing"},{"content":"In honor of Tongue Out Tuesday and “The Adventures of Henry” we find, well, what can I say, a tongue joyfully out! This is the newly expanded play area at camp getting a test run by Henry and his posse. It’s early for a full review, but I think it’s safe to say it’s a hit!\n","date":"2023-03-28","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-28-2023/","section":"Writing","summary":"","title":"Adventures on March 28, 2023","type":"writing"},{"content":"This angry edition of “The Adventures of Henry” presents our resentful, plotting against-me boy after getting an unexpected, mid-week midday bath. Oh the indignity, the nerve, the how-could-you of it all! What those eyes aren’t telling you is all about the surprise rolling on something nasty during our walk! Do we mention that, no! Do we hate our dad, yes!\n","date":"2023-03-24","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-24-2023/","section":"Writing","summary":"","title":"Adventures on March 24, 2023","type":"writing"},{"content":"This plea for help edition of “The Adventures of Henry” captures our hero just before he issued an urgent telegram to the American Red Cross for an emergency excitement intervention! As you can see from the picture, I clearly remain boring beyond belief!\n","date":"2023-03-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-23-2023/","section":"Writing","summary":"","title":"Adventures on March 23, 2023","type":"writing"},{"content":"This athletic edition of “The Adventures of Henry,” presents our little track and field start taking part in the new Olympic Exhibition Sport, 20 Yard Puppy Hurdles! This exhilarating event is being made even more so by Henry playing to the crowd!\n","date":"2023-03-07","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-07-2023/","section":"Writing","summary":"","title":"Adventures on March 07, 2023","type":"writing"},{"content":"In this titanic episode of “The Adventures of Henry” we find our poser making a request, “paint me like one of your french girls, dad”. Besides not knowing any french girls, it’s clear to me that this particular puppy has no shame!\n","date":"2023-03-02","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-02-2023/","section":"Writing","summary":"","title":"Adventures on March 02, 2023","type":"writing"},{"content":"This tactical edition of “The Adventures of Henry” finds our own Gen. Pupton taking the high ground AND the ball at training camp. He’s getting a letter of commendation in his file.\n","date":"2023-02-28","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-28-2023/","section":"Writing","summary":"","title":"Adventures on February 28, 2023","type":"writing"},{"content":"Here at “The Adventures of Henry,” we celebrate the majesty and nobility of dogs. In particular, when they steal their mom’s spot in bed the minute she leaves the house for work. Oh the majesty! Oh the nobility! Oh the slug likeness! It brings tears to this reporters eyes.\n","date":"2023-02-27","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-27-2023/","section":"Writing","summary":"","title":"Adventures on February 27, 2023","type":"writing"},{"content":"Here at “The Adventures of Henry,” we recognize those dogs who’ve made a difference in the world. Yukon Henry is one of those dogs; noble, adventurous, sock-stealing to a fault, and never misses a photo-op. Rock on sir, rock on!\n","date":"2023-02-22","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-22-2023/","section":"Writing","summary":"","title":"Adventures on February 22, 2023","type":"writing"},{"content":"Today’s “The Adventures of Henry” presents the “Four Horsemen of the Apupocalypse!” This isn’t a biblical level event, but any treats you have on hand could be at risk!\n","date":"2023-02-14","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-14-2023/","section":"Writing","summary":"","title":"Adventures on February 14, 2023","type":"writing"},{"content":"The “Nature Channel” division of “The Adventures of Henry” brings us this picture of a boy and his sheep. They want to remind us that Henry is, after all, half Australian shepherd. What they FAIL to mention are the tremendous whuppins’ his pal the sheep is subject to. Oh the humanity!\n","date":"2023-02-13","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-13-2023/","section":"Writing","summary":"","title":"Adventures on February 13, 2023","type":"writing"},{"content":"From the side-eye chapter of “The Adventures of Henry” comes this question, “Dad, let me ask you something, and I’m quite serious about this, so I’d like a serious answer. Why are you so boring?” Despite being asked this very question in many situations, sadly, I didn’t have a satisfactory answer.\n","date":"2023-02-02","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-02-2023/","section":"Writing","summary":"","title":"Adventures on February 02, 2023","type":"writing"},{"content":"In this contemplative edition of “The Adventures of Henry,” we find Henry taking stock of things, “Two roads diverged in the wood, and I — I took the one less peed upon, and that has made all the difference” @aussiedoodle\n","date":"2023-01-30","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-30-2023/","section":"Writing","summary":"","title":"Adventures on January 30, 2023","type":"writing"},{"content":"“Look for me not in the setting sun but in the glorious dawn. It is there that hopes springs and joy embarks.” - “The Adventures of Henry.”\nHenry is taking a parks and recs class in over-dramatics; the staff believes this has been money well spent. At least until Bagelman opens.\n","date":"2023-01-20","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-20-2023/","section":"Writing","summary":"","title":"Adventures on January 20, 2023","type":"writing"},{"content":"Today’s slip, slidin’ away issue of “The Adventures of Henry” presents our boy on the slide at the playground wondering where all his playmates are while at the same time ignoring the bone-chilling cold that might answer that question. Neither rain nor wind nor adorable pink jacket shall stay this courier from his appointed rounds of fun!\n","date":"2023-01-17","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-17-2023/","section":"Writing","summary":"","title":"Adventures on January 17, 2023","type":"writing"},{"content":"Today’s not-so-adventurous edition of “The Adventures of Henry” finds our tired hero snuggled into his bed under my desk. He had an anxious day at the groomers the other day, followed up by a busy day at camp the next day. This picture makes me go “awwwww”, and, of course, want to bug him immediately!!!\n","date":"2023-01-13","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-13-2023/","section":"Writing","summary":"","title":"Adventures on January 13, 2023","type":"writing"},{"content":"In “The Adventures of Henry,” today we see Fire Marshall Henry rallying the troops in a preparedness fire drill at camp! In case of a real fire, you would have been directed where to go to save all the treats, which the Fire Marshall would hold onto for safekeeping. Mmmmmmm, safety is soooo tasty!!\n","date":"2023-01-10","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-10-2023/","section":"Writing","summary":"","title":"Adventures on January 10, 2023","type":"writing"},{"content":"In this colorful \u0026ldquo;The Adventures of Henry\u0026rdquo; edition, we find our leading man out for a walk in his radiant winter vest. It even has pockets, which will garner attention when he walks the walk on Project Runway!\n","date":"2023-01-07","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-07-2023/","section":"Writing","summary":"","title":"Adventures on January 07, 2023","type":"writing"},{"content":"In today’s moist edition of “The Adventures of Henry,” we find our rather soggy pup on the bridge at the Brookfield Greenway looking at dad with that, “I was promised snow. Where is it?!?!” look that the staff here know so well. Apparently, there’s a special chair in hell for me if snow is not forthcoming!\n","date":"2023-01-03","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-03-2023/","section":"Writing","summary":"","title":"Adventures on January 03, 2023","type":"writing"},{"content":"Henry and the staff at “The Adventures of Henry” want to wish you all a very wonderful holiday season and that you’re all looking forward to a glorious new year! Henry also wants to add that his stocking isn’t full yet, and essentially, “what the heck?!?”\n","date":"2022-12-23","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-23-2022/","section":"Writing","summary":"","title":"Adventures on December 23, 2022","type":"writing"},{"content":"In this edition of “The Adventures of Henry,” we see Henry having his picture taken with his doodleganger. Knowing our boy, it will be no surprise to me when he starts blaming his own misdeeds on his look alike, “it wasn’t me, Dad, it was that other guy who looks like me!!” The cookie crumbs in his mustache were planted!!\n","date":"2022-12-20","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-20-2022/","section":"Writing","summary":"","title":"Adventures on December 20, 2022","type":"writing"},{"content":"Today’s episode of “The Adventures of Henry” is the classic, “how could you Dad?” Here we see Henry vowing to tell Mom when she gets home about the indignities he’s suffered while on a simple romp at the dog park. Not that it will make any difference to Dad’s fate, but I’ll try to share with her the river swim and rolls in the sand and the additional rolls and squirming in the mud that followed.\n","date":"2022-12-12","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-12-2022/","section":"Writing","summary":"","title":"Adventures on December 12, 2022","type":"writing"},{"content":"In today’s recriminating looks edition of “The Adventures of Henry” we find our little trooper in his bed after a HORRIFYING VISIT TO THE VETS! Where he was tortured with his shots and the usual completely insulting poking and prodding. He spent most of the time leaning at 45 degrees against the nurse in a vain attempt to make a break for it or at least let her know he was not a fan. I’m now on a mission to win back his trust and dodge any further cold-shoulder interactions. (edited)\n","date":"2022-12-09","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-09-2022/","section":"Writing","summary":"","title":"Adventures on December 09, 2022","type":"writing"},{"content":"In this throwback episode of “The Adventures of Henry,” here we are, meeting Henry for the first time at LaGuardia airport. Susan and I were very excited. Henry, however, was in the “we’ll see” camp.\n","date":"2022-12-05","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-december-05-2022/","section":"Writing","summary":"","title":"Adventures on December 05, 2022","type":"writing"},{"content":"Today’s “The Adventures of Henry” finds him creating a new movie poster, “Two Doodles and a Little Rosie.” This is a screenplay treatment Henry is shopping around to get some backers. He’s lobbying for Tom Cruise to be his barkover voice actor.\n","date":"2022-11-22","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-22-2022/","section":"Writing","summary":"","title":"Adventures on November 22, 2022","type":"writing"},{"content":"In today’s edition of “The Adventures of Henry,” we find our hero hanging with one of his besties from camp, Rose. Henry is promoting running around like a lunatic, while Rose is more of the sunbathing way of thought.\n","date":"2022-11-21","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-november-21-2022/","section":"Writing","summary":"","title":"Adventures on November 21, 2022","type":"writing"},{"content":"“The Adventures of Henry” continue on the site o “Lord of The Rings”. Or so Henry thinks, it’s actually a park near us, but a pup can dream!\n","date":"2022-04-04","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-april-04-2022/","section":"Writing","summary":"","title":"Adventures on April 04, 2022","type":"writing"},{"content":"“The Adventures of Henry” for today is a Question and no Answer session. Henry’s question is why is the kitchen changing dad?!?\n","date":"2022-03-15","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-march-15-2022/","section":"Writing","summary":"","title":"Adventures on March 15, 2022","type":"writing"},{"content":"“The Adventures of Henry” finds our hero exploring the ruins of an old iron furnace. And just to be clear, Henry didn’t ruin them!\n","date":"2022-02-21","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-february-21-2022/","section":"Writing","summary":"","title":"Adventures on February 21, 2022","type":"writing"},{"content":"“The Adventures of Henry” continues at the wilds of Happy Landings in Brookfield.\n","date":"2022-01-19","externalUrl":null,"permalink":"/writing/adventures_of_henry/adventures-on-january-19-2022/","section":"Writing","summary":"","title":"Adventures on January 19, 2022","type":"writing"},{"content":"","externalUrl":null,"permalink":"/art/","section":"Art","summary":"","title":"Art","type":"art"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"Welcome to Happy Canvas and Code! My name is Doug Farrell, and I\u0026rsquo;m using this site as a kind of \u0026lsquo;branding\u0026rsquo; for myself, and to talk about things that I think are interesting. This is kind of experimental, so we\u0026rsquo;ll see how this goes and evolve over time.\nYou can find a lot of information about me from my LinkedIn profile link in the sidebar, but here\u0026rsquo;s a brief overview. Professionally I\u0026rsquo;m a software developer who\u0026rsquo;s worked in quite a few industries in quite a few programming languages. Currently, my sweet-spot language of choice is Python. I like its brevity of syntax, clarity of intent and just how well it fits how I think about developing software.\nPersonally, I have far too many interests and have to regularly whittle the list down to actually do anything with one of them. I\u0026rsquo;ve tried to keep the list to getting back into art, painting in particular, after many years of not being creative in that way. I\u0026rsquo;m also trying to learn how to play the ukulele as it seems like a manageable instrument for someone who is not very musically inclined. I also like creating various projects with the Raspberry Pi and Pico boards that interact with the real world.\nI\u0026rsquo;m the author of \u0026ldquo;The Well-Grounded Python Developer\u0026rdquo;, by Manning Publishing. I\u0026rsquo;ve also written a few articles for the Real Python site, which is a great resource for information about Python.\n","externalUrl":null,"permalink":"/about/","section":"Welcome to My Site","summary":"","title":"Happy Canvas and Code","type":"page"},{"content":"","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","externalUrl":null,"permalink":"/writing/stuff/","section":"Writing","summary":"","title":"Stuff","type":"writing"},{"content":"","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","externalUrl":null,"permalink":"/writing/country_store/","section":"Writing","summary":"","title":"The Country Store","type":"writing"}]