• The Week #90

    • After a few months delay I went and got my annual health check. I was able to go to the same center as last time, too. I'm always amazed at the efficientcy of these places as each person is directed from station to station and the nurses/technicians do their thing.

      Despite my best efforts I gained a couple of kilos and my blood pressure was slightly up (with the state of the world how could it not πŸ˜…). It's time to cut out that snacking habit I've gotten into as of late and cut down portions at dinner.
    • As Leo had a "forced" Tuesday off last week, since he wasn't sick, rather than be stressed at the house all day we went out and did our usual Monorail-Enoshima-Aquarium-Burger loop. He played the crane game with his own money once, but he didn't win anything, but he seemed satisfied.

      While we were walking to the Enoden after lunch, Leo decided he wanted some sweets from the coffee shop, so we dropped by Tully's as well. There's a great word in Japanese for exactly this situation, where you have room for dessert despite being full: betsu-bara. Betsu means separate/different and bara being stomach. It's the first time he's had dessert stomach that I can remember.
    • There was a big earthquake last week around 11:30pm. It was certainly the biggest and longest quake I've felt in a while. Thankfully there wasn't any damage around here and it was mostly an annoyance because it kicks off your adrenaline, which is not helpful when you're trying to get back to sleep.
    • I bought tickets for Disney next month during Leo's spring break. I've never been to Tokyo Disneyland, but I've been to Disney Sea twice.

      As a kid, we lived close enough that when we went to Disney it was just for the day, never staying at the hotels. And being honest, they were probably also out of budget, even if it wasn't so close. While we're still close enough here in Yokohama for a day-trip to Disney (trains rule), we're going to stay one night. I'm excited about it, not just because it's a surprise for Leo, but because I can do those family things I couldn't do myself growing up.
    • Unrelated to health check results, but after skipping a week of running not on purpose I got back out and ran 5km. I always feel so much better after running, no matter how long I go out. I need to remind myself when the main obstacle is me changing clothes. Sometimes I can't run in the morning or don't want a tight lunch run. With the days starting to get longer, I wonder if I could do short post-work runs every now and again...
  • I've now added support for OpenGraph meta tags in Tanzawa. It's nice to get a small feature out.

    Open graph data on a blog post

    Thanks to Ricardo for requesting this feature.Β 
  • How to Resolve Overlapping DjangoObjectTypes with Graphene and Graphene Relay

    If your schema has multiple types defined using DjangoObjectType for the same model in a Union, selections that work on the Union won't necessarily work as is on Relay node queries.Β 

    For example, suppose we had a model schema like this:

    class Record(models.Model):
        record_type = models.CharField(max_length=12)
        
        @property
        def is_disco(self) -> bool:
            return self.record_type == "DISCO"
    
        @property
        def is_punk(self) -> bool:
            return self.record_type == "PUNK"
    
    class Disco(models.Model):
        record = models.OneToOneField(Record, related_name="disco_record")
        bpm = models.IntegerField()
    
    
    class Punk(models.Model):
        record = models.OneToOneField(Record, related_name="punk_record")
        max_chords = models.IntegerField()

    Our application cares Records and, depending on the record_type, the type of meta information we want to manage changes. As such we create a new model with a OneToOneField to our record for each type we plan on managing.

    When we query our records we wan to only worry about Records, so define our GraphQL types accordingly.

    class DiscoRecord(graphene_django.DjangoObjectType):
        class Meta:
            model = models.Record
        
        bpm = graphene.IntegerField(required=True)
    
        @classmethod
        def get_node(cls, info, id) -> models.Record:
            # Allow our object to be fetchable as a Relay Node
            return models.Record.objects.get(pk=id)
        
        def resolve_bpm(record: models.Record, **kwargs) -> int:
            return record.disco_record.bpm
    
    class PunkRecord(graphene_django.DjangoObjectType):
        class Meta:
            model = models.Record
        
        max_chords = graphene.IntegerField(required=True)
        
        @classmethod
        def get_node(cls, info, id) -> models.Record:
            # Allow our object to be fetchable as a Relay Node
            return models.Record.objects.get(pk=id)
        
        def resolve_max_chords(record: models.Record, **kwargs) -> int:
            return record.punk_record.max_chords
    
    
    class Record(graphene.Union):
        class Meta:
            types = (DiscoRecord, PunkRecord)
    
        @classmethod
        def resolve_type(
            cls, instance: models.Record, info
        ) -> Union[Type[DiscoRecord], Type[PunkRecord]]:
            # Graphene is unable to accurately determine which type it should resolve without help
            # because the unioned types are all DjangoObjectTypes for the same Record class.
            if instance.is_disco:
                return DiscoRecord
            elif instance.is_punk:
                return PunkRecord
            raise ValueError("Unknown record type")

    Because we have the resolve_type @classmethod defined in our Union, Graphene can correctly determine the record type. Without that we'd get an error any time we tried to resolve values that only exist on the PunkRecord or DiscoRecord type.

    So if we had a records query that returned our Record Union, we could query it as follows without any issues.

    query {
        records {
            ... on DiscoRecord {
                bpm
            }
            ... on PunkRecord {
                maxChords
            }
        }
    }

    But what about the Relay node query? The query looks quite similar to our records query.

    query {
        node(id: "fuga") {
            ... on DiscoRecord {
                bpm
            }
            ... on PunkRecord {
                maxChords
            }
        }
    }

    However, and this is the key difference, node does not return our Union type, but rather our individual DiscoRecord / PunkRecord type. And since both of those types are technically Record types (because of the same Django meta class), any PunkRecords will be resolved asΒ  DiscoRecords and return an error when we try to resolve Punk only fields.

    In order for node to be able to differentiate between the Punk and Disco at the type level we need one more is_type_of classmethod defined on our types.

    class DiscoRecord(graphene_django.DjangoObjectType):
        ...
        @classmethod
        def is_type_of(cls, root, info) -> bool:
            # When a DiscoRecord is resolved as a node it does not use the Union type
            # determine the object's type.
            # Only allow this type to be used with Disco records.
            if isinstance(root, models.Record):
                return root.is_disco
            return False
    
    class PunkRecord(graphene_django.DjangoObjectType):
        ...
        @classmethod
        def is_type_of(cls, root, info) -> bool:
            # When a PunkRecord is resolved as a node it does not use the Union type
            # to determine the object's type.
            # Only allow this type to be used with Punk records.
            if isinstance(root, models.Record):
                return root.is_punk
            return False

    This way, when Graphene is looping through all of our types trying to determine which type to use for a given Record, we can inspect the actual record and prevent an erroneous match.

    This is obvious in retrospect. Although our GraphQL query selectors are exactly the same the root type is different and as such requires a bit more instruction to resolve the appropriate type.
  • Response to How can I improve the typography on my site?

    I have been thinking about the typography on my website for months in the back of my head. Every so often, I ask myself: is the typography as good as it can be? First, I wonder whether I should start using a custom font. If I recall correctly, I am using a web-safe default font right now. Maybe a new font will add more character to my blog?
    You may be interested in On Web Typography (their other books on design are really good, too).

    Your typography for each element should help that element fit its purpose on that given page. i.e. you wouldn't want bold 36px text for your blog posts. Your font sizes for blog post text will probably be different between the main page and the blog post detail page because the purpose of the text is different. Serif fonts, that is fonts with caps on the end are easier to read for longer texts. Give it room to breathe.

    You can also use different colors of text to increase/reduce emphasis of a particular element. I use thisΒ  technique quite a bit in Tanzawa. For example the page I'm using to write this reply looks like this:

    Smaller fonts in light gray de-emphasis less important bits


    Typography and design are deep subjects and are, in many ways, exactly like programming in the following sense: "Give a man a program, frustrate him for a day. Teach a man to pgoram, frustrate him for a lifetime."
  • Checkin to Starbucks

    in Yokohama, Kanagawa, Japan
    Post health check coffee.
Previous 213 of 710 Next